diff --git a/index.html b/index.html index ed7116e..fb27f5b 100644 --- a/index.html +++ b/index.html @@ -168,6 +168,13 @@ padding-left: 2em; } +.bracketed-link { + font-size: x-small; +} +.bracketed-link a { + text-decoration: none; +} + #download { padding-left: 1em; cursor: pointer; diff --git a/index.js b/index.js index 20ff16b..f58c795 100644 --- a/index.js +++ b/index.js @@ -14,46 +14,46 @@ export const kMaxUnsignedLongValue = 4294967295; export const kMaxUnsignedLongLongValue = Number.MAX_SAFE_INTEGER; /** Info for each entry of GPUSupportedLimits */ -const kLimitInfo = /* prettier-ignore */ makeTable( - [ 'class','type' , 'default', 'compat' , 'maximumValue'], - [ 'maximum', , , , kMaxUnsignedLongValue], { - 'maxTextureDimension1D': [ , 'size' , 8192, 4096, ], - 'maxTextureDimension2D': [ , 'size' , 8192, 4096, ], - 'maxTextureDimension3D': [ , 'size' , 2048, 1024, ], - 'maxTextureArrayLayers': [ , 'size' , 256, 256, ], - - 'maxBindGroups': [ , 'count' , 4, 4, ], - 'maxBindGroupsPlusVertexBuffers': [ , 'count' , 24, 24, ], - 'maxBindingsPerBindGroup': [ , 'count' , 1000, 1000, ], - 'maxDynamicUniformBuffersPerPipelineLayout': [ , 'count' , 8, 8, ], - 'maxDynamicStorageBuffersPerPipelineLayout': [ , 'count' , 4, 4, ], - 'maxSampledTexturesPerShaderStage': [ , 'count' , 16, 16, ], - 'maxSamplersPerShaderStage': [ , 'count' , 16, 16, ], - 'maxStorageBuffersPerShaderStage': [ , 'count' , 8, 4, ], - 'maxStorageTexturesPerShaderStage': [ , 'count' , 4, 4, ], - 'maxUniformBuffersPerShaderStage': [ , 'count' , 12, 12, ], - - 'maxUniformBufferBindingSize': [ , 'mem' , 65536, 16384, kMaxUnsignedLongLongValue], - 'maxStorageBufferBindingSize': [ , 'mem' , 134217728, 134217728, kMaxUnsignedLongLongValue], - 'minUniformBufferOffsetAlignment': ['alignment', 'mem' , 256, 256, ], - 'minStorageBufferOffsetAlignment': ['alignment', 'mem' , 256, 256, ], - - 'maxVertexBuffers': [ , 'count' , 8, 8, ], - 'maxBufferSize': [ , 'mem' , 268435456, 268435456, kMaxUnsignedLongLongValue], - 'maxVertexAttributes': [ , 'count' , 16, 16, ], - 'maxVertexBufferArrayStride': [ , 'mem' , 2048, 2048, ], - 'maxInterStageShaderComponents': [ , 'count' , 60, 60, ], - 'maxInterStageShaderVariables': [ , 'count' , 16, 16, ], - - 'maxColorAttachments': [ , 'count' , 8, 4, ], - 'maxColorAttachmentBytesPerSample': [ , 'mem' , 32, 32, ], - - 'maxComputeWorkgroupStorageSize': [ , 'mem' , 16384, 16384, ], - 'maxComputeInvocationsPerWorkgroup': [ , 'count' , 256, 128, ], - 'maxComputeWorkgroupSizeX': [ , 'size' , 256, 128, ], - 'maxComputeWorkgroupSizeY': [ , 'size' , 256, 128, ], - 'maxComputeWorkgroupSizeZ': [ , 'size' , 64, 64, ], - 'maxComputeWorkgroupsPerDimension': [ , 'mem' , 65535, 65535, ], +const kLimitInfo = makeTable( + [ 'class','type' , 'maximumValue'], + [ 'maximum', , kMaxUnsignedLongValue], { + 'maxTextureDimension1D': [ , 'size' , ], + 'maxTextureDimension2D': [ , 'size' , ], + 'maxTextureDimension3D': [ , 'size' , ], + 'maxTextureArrayLayers': [ , 'count' , ], + + 'maxBindGroups': [ , 'count' , ], + 'maxBindGroupsPlusVertexBuffers': [ , 'count' , ], + 'maxBindingsPerBindGroup': [ , 'count' , ], + 'maxDynamicUniformBuffersPerPipelineLayout': [ , 'count' , ], + 'maxDynamicStorageBuffersPerPipelineLayout': [ , 'count' , ], + 'maxSampledTexturesPerShaderStage': [ , 'count' , ], + 'maxSamplersPerShaderStage': [ , 'count' , ], + 'maxStorageBuffersPerShaderStage': [ , 'count' , ], + 'maxStorageTexturesPerShaderStage': [ , 'count' , ], + 'maxUniformBuffersPerShaderStage': [ , 'count' , ], + + 'maxUniformBufferBindingSize': [ , 'mem' , kMaxUnsignedLongLongValue], + 'maxStorageBufferBindingSize': [ , 'mem' , kMaxUnsignedLongLongValue], + 'minUniformBufferOffsetAlignment': ['alignment', 'mem' , ], + 'minStorageBufferOffsetAlignment': ['alignment', 'mem' , ], + + 'maxVertexBuffers': [ , 'count' , ], + 'maxBufferSize': [ , 'mem' , kMaxUnsignedLongLongValue], + 'maxVertexAttributes': [ , 'count' , ], + 'maxVertexBufferArrayStride': [ , 'mem' , ], + 'maxInterStageShaderComponents': [ , 'count' , ], + 'maxInterStageShaderVariables': [ , 'count' , ], + + 'maxColorAttachments': [ , 'count' , ], + 'maxColorAttachmentBytesPerSample': [ , 'mem' , ], + + 'maxComputeWorkgroupStorageSize': [ , 'mem' , ], + 'maxComputeInvocationsPerWorkgroup': [ , 'count' , ], + 'maxComputeWorkgroupSizeX': [ , 'size' , ], + 'maxComputeWorkgroupSizeY': [ , 'size' , ], + 'maxComputeWorkgroupSizeZ': [ , 'size' , ], + 'maxComputeWorkgroupsPerDimension': [ , 'size' , ], }); function createElem(tag, attrs = {}, children = []) { @@ -221,43 +221,52 @@ function mapLikeToTableRows(values, sort = true) { : [el('tr', {}, [el('td', {colSpan: 2, textContent: 'not yet implemented by this browser'})])]; } +function makeBracketedLink(href, textContent, brackets = '()') { + return [ + createElem('span', {className: 'bracketed-link'}, [ + createElem('span', `${brackets[0]} `), + createElem('a', { target: '_blank', href, textContent }), + createElem('span', ` ${brackets[1]}`), + ]), + ]; +} + function log(...args) { const elem = document.createElement('pre'); elem.textContent = args.join(' '); addElemToDocument(elem); } -function differenceWorse(info, v) { - switch (info.class) { - case 'alignment': - return v > info.default; - case 'maximum': - return v < info.default; - default: - throw new Error(`unknown className: ${info.class}`) +function differenceWorse(name, defaultLimit, v) { + if (name.startsWith('min')) { + return v > defaultLimit; + } else if (name.startsWith('max')) { + return v < defaultLimit; + } else { + throw new Error(`unknown limit type: ${name}`) } } -function markDifferencesInLimits(adapter) { +function markDifferencesInLimits(adapter, device) { + const defaultLimits = device?.limits ?? {}; return Object.fromEntries( mapLikeToKeyValueArray(adapter.limits) .map(([k, v]) => { + const defaultLimit = defaultLimits[k]; const info = kLimitInfo[k]; - const isDiff = info && info.default !== v; - const diffClass = info + const isDiff = defaultLimit !== undefined && defaultLimit !== v; + const diffClass = defaultLimit !== undefined ? (isDiff - ? differenceWorse(info, v) ? 'different-worse' : 'different-better' + ? differenceWorse(k, defaultLimit, v) ? 'different-worse' : 'different-better' : '') : 'unknown'; - const value = v > 1024 && info ? `${v} (${shortSizeByType(v, info.type)})` : v; + const shortSize = shortSizeByType(v, info?.type ?? 'count'); + const value = v > 1024 ? `${v} (${shortSize})` : shortSize; return [ k, isDiff - ? [value, {className: `${diffClass} nowrap`, title: `default${adapter.isCompatibilityMode ? ' in compat' : ''}: ${shortSizeByType(adapter.isCompatibilityMode ? info.compat : info.default, info.type)}`}] - : info - ? [value, {className: 'nowrap', title: 'same as default'}] - : [value, {className: 'unknown nowrap', title: 'unknown limit (new?)'}] - + ? [value, {className: `${diffClass} nowrap`, title: `default: ${shortSize}`}] + : [value, {className: 'nowrap', title: 'same as default'}] ]; }) ); @@ -299,9 +308,15 @@ async function adapterToElements(adapter) { } // UGH! const adapterInfo = adapter.info || await (adapter.requestAdapterInfo ? adapter.requestAdapterInfo() : undefined); + const device = await adapter.requestDevice() || {} const limitsSectionElem = el('tr', {className: 'section'}, [ - el('td', {colSpan: 2}, [createHeading('div', '-', 'limits:')]), + el('td', {colSpan: 2}, [ + createHeading('div', '-', {}, [ + createElem('span', {textContent: 'limits: '}), + ...makeBracketedLink('https://webgpufundamentals.org/webgpu/lessons/webgpu-limits-and-features.html', 'must be requested'), + ]), + ]), ]); return el('table', {}, [ @@ -315,9 +330,14 @@ async function adapterToElements(adapter) { ]), ...mapLikeToTableRows(parseAdapterFlags(adapter)), limitsSectionElem, - ...mapLikeToTableRows(markDifferencesInLimits(adapter)), + ...mapLikeToTableRows(markDifferencesInLimits(adapter, device), true), el('tr', {className: 'section'}, [ - el('td', {colSpan: 2}, [createHeading('div', '-', 'features:')]), + el('td', {colSpan: 2}, [ + createHeading('div', '-', {}, [ + createElem('span', {textContent: 'features: '}), + ...makeBracketedLink('https://webgpufundamentals.org/webgpu/lessons/webgpu-limits-and-features.html', 'must be requested'), + ]), + ]), ]), ...setLikeToTableRows(adapter.features), ]), @@ -605,7 +625,6 @@ async function main() { } const haveFallback = [...adapterIds].findIndex(([, desc]) => desc.fallback) >= 0; - const numUniqueGPUs = adapterIds.size - (haveFallback ? 1 : 0) const actualAdaptersIds = [...adapterIds].filter(([, {elem}]) => !!elem); if (actualAdaptersIds.length === 0) {