// MAINTENANCE_TODO: The generated Typedoc for this file is hard to navigate because it's // alphabetized. Consider using namespaces or renames to fix this? /* eslint-disable no-sparse-arrays */ import { globalTestConfig } from '../common/framework/test_config.js'; import { keysOf, makeTable, makeTableRenameAndFilter, numericKeysOf, valueof, } from '../common/util/data_tables.js'; import { assertTypeTrue, TypeEqual } from '../common/util/types.js'; import { assert, combinationsOfOneOrTwoUsages, hasFeature, unreachable, } from '../common/util/util.js'; import { GPUConst, kMaxUnsignedLongValue, kMaxUnsignedLongLongValue } from './constants.js'; // Base device limits can be found in constants.ts. // Queries /** Maximum number of queries in GPUQuerySet, by spec. */ export const kMaxQueryCount = 4096; /** Per-GPUQueryType info. */ export type QueryTypeInfo = { /** Optional feature required to use this GPUQueryType. */ readonly feature: GPUFeatureName | undefined; // Add fields as needed }; export const kQueryTypeInfo: { readonly [k in GPUQueryType]: QueryTypeInfo; } = /* prettier-ignore */ { 'occlusion': { feature: undefined }, 'timestamp': { feature: 'timestamp-query' }, }; /** List of all GPUQueryType values. */ export const kQueryTypes = keysOf(kQueryTypeInfo); // Buffers /** Required alignment of a GPUBuffer size, by spec. */ export const kBufferSizeAlignment = 4; /** Per-GPUBufferUsage copy info. */ export const kBufferUsageCopyInfo: { readonly [name: string]: GPUBufferUsageFlags; } = /* prettier-ignore */ { 'COPY_NONE': 0, 'COPY_SRC': GPUConst.BufferUsage.COPY_SRC, 'COPY_DST': GPUConst.BufferUsage.COPY_DST, 'COPY_SRC_DST': GPUConst.BufferUsage.COPY_SRC | GPUConst.BufferUsage.COPY_DST, }; /** List of all GPUBufferUsage copy values. */ export const kBufferUsageCopy = keysOf(kBufferUsageCopyInfo); /** Per-GPUBufferUsage keys and info. */ type BufferUsageKey = keyof typeof GPUConst.BufferUsage; export const kBufferUsageKeys = keysOf(GPUConst.BufferUsage); export const kBufferUsageInfo: { readonly [k in BufferUsageKey]: GPUBufferUsageFlags; } = { ...GPUConst.BufferUsage, }; /** List of all GPUBufferUsage values. */ export const kBufferUsages = Object.values(GPUConst.BufferUsage); export const kAllBufferUsageBits = kBufferUsages.reduce( (previousSet, currentUsage) => previousSet | currentUsage, 0 ); /** An arbitrary invalid buffer usage bit. */ export const kSomeBogusBufferUsage: GPUBufferUsageFlags = 0x4000_0000; assert((kSomeBogusBufferUsage & kAllBufferUsageBits) === 0); // Errors /** Per-GPUErrorFilter info. */ export const kErrorScopeFilterInfo: { readonly [k in GPUErrorFilter]: { generatable: boolean; }; } = /* prettier-ignore */ { 'internal': { generatable: false }, 'out-of-memory': { generatable: true }, 'validation': { generatable: true }, }; /** List of all GPUErrorFilter values. */ export const kErrorScopeFilters = keysOf(kErrorScopeFilterInfo); export const kGeneratableErrorScopeFilters = kErrorScopeFilters.filter( e => kErrorScopeFilterInfo[e].generatable ); // Canvases // The formats of GPUTextureFormat for canvas context. export const kCanvasTextureFormats = ['bgra8unorm', 'rgba8unorm', 'rgba16float'] as const; // The alpha mode for canvas context. export const kCanvasAlphaModesInfo: { readonly [k in GPUCanvasAlphaMode]: {}; } = /* prettier-ignore */ { 'opaque': {}, 'premultiplied': {}, }; export const kCanvasAlphaModes = keysOf(kCanvasAlphaModesInfo); // The color spaces for canvas context export const kCanvasColorSpacesInfo: { readonly [k in PredefinedColorSpace]: {}; } = /* prettier-ignore */ { 'srgb': {}, 'display-p3': {}, }; export const kCanvasColorSpaces = keysOf(kCanvasColorSpacesInfo); // Textures (except for texture format info) /** Per-GPUTextureDimension info. */ export const kTextureDimensionInfo: { readonly [k in GPUTextureDimension]: {}; } = /* prettier-ignore */ { '1d': {}, '2d': {}, '3d': {}, }; /** List of all GPUTextureDimension values. */ export const kTextureDimensions = keysOf(kTextureDimensionInfo); /** Per-GPUTextureAspect info. */ export const kTextureAspectInfo: { readonly [k in GPUTextureAspect]: {}; } = /* prettier-ignore */ { 'all': {}, 'depth-only': {}, 'stencil-only': {}, }; /** List of all GPUTextureAspect values. */ export const kTextureAspects = keysOf(kTextureAspectInfo); // Misc /** Per-GPUCompareFunction info. */ export const kCompareFunctionInfo: { readonly [k in GPUCompareFunction]: {}; } = /* prettier-ignore */ { 'never': {}, 'less': {}, 'equal': {}, 'less-equal': {}, 'greater': {}, 'not-equal': {}, 'greater-equal': {}, 'always': {}, }; /** List of all GPUCompareFunction values. */ export const kCompareFunctions = keysOf(kCompareFunctionInfo); /** Per-GPUStencilOperation info. */ export const kStencilOperationInfo: { readonly [k in GPUStencilOperation]: {}; } = /* prettier-ignore */ { 'keep': {}, 'zero': {}, 'replace': {}, 'invert': {}, 'increment-clamp': {}, 'decrement-clamp': {}, 'increment-wrap': {}, 'decrement-wrap': {}, }; /** List of all GPUStencilOperation values. */ export const kStencilOperations = keysOf(kStencilOperationInfo); // More textures (except for texture format info) /** Per-GPUTextureUsage type info. */ export const kTextureUsageTypeInfo: { readonly [name: string]: number; } = /* prettier-ignore */ { 'texture': Number(GPUConst.TextureUsage.TEXTURE_BINDING), 'storage': Number(GPUConst.TextureUsage.STORAGE_BINDING), 'render': Number(GPUConst.TextureUsage.RENDER_ATTACHMENT), }; /** List of all GPUTextureUsage type values. */ export const kTextureUsageType = keysOf(kTextureUsageTypeInfo); /** Per-GPUTextureUsage copy info. */ export const kTextureUsageCopyInfo: { readonly [name: string]: number; } = /* prettier-ignore */ { 'none': 0, 'src': Number(GPUConst.TextureUsage.COPY_SRC), 'dst': Number(GPUConst.TextureUsage.COPY_DST), 'src-dest': Number(GPUConst.TextureUsage.COPY_SRC) | Number(GPUConst.TextureUsage.COPY_DST), }; /** List of all GPUTextureUsage copy values. */ export const kTextureUsageCopy = keysOf(kTextureUsageCopyInfo); type TextureUsageSingleBit = valueof; /** Per-GPUTextureUsage info. */ const kTextureUsageInfo: { readonly [k in TextureUsageSingleBit]: { /** If true, the usage should cause configure() to TypeError (not just validation error). */ typeErrorForConfigure: boolean; }; } = /* prettier-ignore */ { [GPUConst.TextureUsage.COPY_SRC]: { typeErrorForConfigure: false }, [GPUConst.TextureUsage.COPY_DST]: { typeErrorForConfigure: false }, [GPUConst.TextureUsage.TEXTURE_BINDING]: { typeErrorForConfigure: false }, [GPUConst.TextureUsage.STORAGE_BINDING]: { typeErrorForConfigure: false }, [GPUConst.TextureUsage.RENDER_ATTACHMENT]: { typeErrorForConfigure: false }, [GPUConst.TextureUsage.TRANSIENT_ATTACHMENT]: { typeErrorForConfigure: true }, }; /** List of all GPUTextureUsage values. */ export const kTextureUsages = numericKeysOf(kTextureUsageInfo); /** Bitmask of all known texture usages. */ export const kAllTextureUsages = kTextureUsages.reduce((acc, usage) => acc | usage, 0); /** An arbitrary invalid texture usage bit. */ export const kSomeBogusTextureUsage: GPUTextureUsageFlags = 0x4000_0000; assert((kSomeBogusTextureUsage & kAllTextureUsages) === 0); /** * Check usage is valid for createTexture(): is non-zero, has only defined bits, * and follows rules for TRANSIENT_ATTACHMENT. */ export function isValidTextureUsageCombination(usage: GPUTextureUsageFlags): boolean { if (usage === 0) return false; if (usage & GPUConst.TextureUsage.TRANSIENT_ATTACHMENT) { return ( usage === (GPUConst.TextureUsage.TRANSIENT_ATTACHMENT | GPUConst.TextureUsage.RENDER_ATTACHMENT) ); } return (usage & ~kAllTextureUsages) === 0; } /** Check if usage contains a bit that is supposed to cause configure() to TypeError. */ export function usageIsTypeErrorForConfigure(usage: GPUTextureUsageFlags): boolean { for (const bit of kTextureUsages) { if ((usage & bit) !== 0 && kTextureUsageInfo[bit].typeErrorForConfigure) { return true; } } return false; } /** List of all combinations of 1-2 known texture usages, that are valid for createTexture(). */ export const kValidCombinationsOfOneOrTwoTextureUsages = combinationsOfOneOrTwoUsages( kTextureUsages ).filter(isValidTextureUsageCombination); // Texture View /** Per-GPUTextureViewDimension info. */ export type TextureViewDimensionInfo = { /** Whether a storage texture view can have this view dimension. */ readonly storage: boolean; // Add fields as needed }; /** Per-GPUTextureViewDimension info. */ export const kTextureViewDimensionInfo: { readonly [k in GPUTextureViewDimension]: TextureViewDimensionInfo; } = /* prettier-ignore */ { '1d': { storage: true }, '2d': { storage: true }, '2d-array': { storage: true }, 'cube': { storage: false }, 'cube-array': { storage: false }, '3d': { storage: true }, }; /** List of all GPUTextureDimension values. */ export const kTextureViewDimensions = keysOf(kTextureViewDimensionInfo); // Vertex formats /** Per-GPUVertexFormat info. */ // Exists just for documentation. Otherwise could be inferred by `makeTable`. export type VertexFormatInfo = { /** Number of bytes in each component. */ readonly bytesPerComponent: 1 | 2 | 4 | 'packed'; /** The data encoding (float, normalized, or integer) for each component. */ readonly type: 'float' | 'unorm' | 'snorm' | 'uint' | 'sint'; /** Number of components. */ readonly componentCount: 1 | 2 | 3 | 4; /** Size in bytes. */ readonly byteSize: 1 | 2 | 4 | 8 | 12 | 16; /** The completely matching WGSL type for vertex format */ readonly wgslType: | 'f32' | 'vec2' | 'vec3' | 'vec4' | 'u32' | 'vec2' | 'vec3' | 'vec4' | 'i32' | 'vec2' | 'vec3' | 'vec4'; // Add fields as needed }; /** Per-GPUVertexFormat info. */ export const kVertexFormatInfo: { readonly [k in GPUVertexFormat]: VertexFormatInfo; } = /* prettier-ignore */ makeTable( ['bytesPerComponent', 'type', 'componentCount', 'byteSize', 'wgslType'] as const, [ , , , , ] as const, { // 8 bit components 'uint8': [ 1, 'uint', 1, 1, 'u32'], 'uint8x2': [ 1, 'uint', 2, 2, 'vec2'], 'uint8x4': [ 1, 'uint', 4, 4, 'vec4'], 'sint8': [ 1, 'sint', 1, 1, 'i32'], 'sint8x2': [ 1, 'sint', 2, 2, 'vec2'], 'sint8x4': [ 1, 'sint', 4, 4, 'vec4'], 'unorm8': [ 1, 'unorm', 1, 1, 'f32'], 'unorm8x2': [ 1, 'unorm', 2, 2, 'vec2'], 'unorm8x4': [ 1, 'unorm', 4, 4, 'vec4'], 'snorm8': [ 1, 'snorm', 1, 1, 'f32'], 'snorm8x2': [ 1, 'snorm', 2, 2, 'vec2'], 'snorm8x4': [ 1, 'snorm', 4, 4, 'vec4'], // 16 bit components 'uint16': [ 2, 'uint', 1, 2, 'u32'], 'uint16x2': [ 2, 'uint', 2, 4, 'vec2'], 'uint16x4': [ 2, 'uint', 4, 8, 'vec4'], 'sint16': [ 2, 'sint', 1, 2, 'i32'], 'sint16x2': [ 2, 'sint', 2, 4, 'vec2'], 'sint16x4': [ 2, 'sint', 4, 8, 'vec4'], 'unorm16': [ 2, 'unorm', 1, 2, 'f32'], 'unorm16x2': [ 2, 'unorm', 2, 4, 'vec2'], 'unorm16x4': [ 2, 'unorm', 4, 8, 'vec4'], 'snorm16': [ 2, 'snorm', 1, 2, 'f32'], 'snorm16x2': [ 2, 'snorm', 2, 4, 'vec2'], 'snorm16x4': [ 2, 'snorm', 4, 8, 'vec4'], 'float16': [ 2, 'float', 1, 2, 'f32'], 'float16x2': [ 2, 'float', 2, 4, 'vec2'], 'float16x4': [ 2, 'float', 4, 8, 'vec4'], // 32 bit components 'float32': [ 4, 'float', 1, 4, 'f32'], 'float32x2': [ 4, 'float', 2, 8, 'vec2'], 'float32x3': [ 4, 'float', 3, 12, 'vec3'], 'float32x4': [ 4, 'float', 4, 16, 'vec4'], 'uint32': [ 4, 'uint', 1, 4, 'u32'], 'uint32x2': [ 4, 'uint', 2, 8, 'vec2'], 'uint32x3': [ 4, 'uint', 3, 12, 'vec3'], 'uint32x4': [ 4, 'uint', 4, 16, 'vec4'], 'sint32': [ 4, 'sint', 1, 4, 'i32'], 'sint32x2': [ 4, 'sint', 2, 8, 'vec2'], 'sint32x3': [ 4, 'sint', 3, 12, 'vec3'], 'sint32x4': [ 4, 'sint', 4, 16, 'vec4'], // 32 bit packed 'unorm10-10-10-2': [ 'packed', 'unorm', 4, 4, 'vec4'], 'unorm8x4-bgra': [ 'packed', 'unorm', 4, 4, 'vec4'], } as const); /** List of all GPUVertexFormat values. */ export const kVertexFormats = keysOf(kVertexFormatInfo); // Typedefs for bindings /** * Classes of `PerShaderStage` binding limits. Two bindings with the same class * count toward the same `PerShaderStage` limit(s) in the spec (if any). */ export type PerStageBindingLimitClass = | 'uniformBuf' | 'storageBuf' | 'sampler' | 'sampledTex' | 'readonlyStorageTex' | 'writeonlyStorageTex' | 'readwriteStorageTex'; /** * Classes of `PerPipelineLayout` binding limits. Two bindings with the same class * count toward the same `PerPipelineLayout` limit(s) in the spec (if any). */ export type PerPipelineBindingLimitClass = PerStageBindingLimitClass; export type ValidBindableResource = | 'uniformBuf' | 'storageBuf' | 'filtSamp' | 'nonFiltSamp' | 'compareSamp' | 'sampledTex' | 'sampledTexMS' | 'readonlyStorageTex' | 'writeonlyStorageTex' | 'readwriteStorageTex'; type ErrorBindableResource = 'errorBuf' | 'errorSamp' | 'errorTex'; /** * Types of resource binding which have distinct binding rules, by spec * (e.g. filtering vs non-filtering sampler, multisample vs non-multisample texture). */ export type BindableResource = ValidBindableResource | ErrorBindableResource; export const kBindableResources = [ 'uniformBuf', 'storageBuf', 'filtSamp', 'nonFiltSamp', 'compareSamp', 'sampledTex', 'sampledTexMS', 'readonlyStorageTex', 'writeonlyStorageTex', 'readwriteStorageTex', 'errorBuf', 'errorSamp', 'errorTex', ] as const; assertTypeTrue>(); // Bindings /** Dynamic buffer offsets require offset to be divisible by 256, by spec. */ export const kMinDynamicBufferOffsetAlignment = 256; /** Default `PerShaderStage` binding limits, by spec. */ export const kPerStageBindingLimits: { readonly [k in PerStageBindingLimitClass]: { /** Which `PerShaderStage` binding limit class. */ readonly class: k; /** Maximum number of allowed bindings in that class. */ readonly maxLimits: { [key in ShaderStageKey]: (typeof kPossibleLimits)[number] }; // Add fields as needed }; } = /* prettier-ignore */ { 'uniformBuf': { class: 'uniformBuf', maxLimits: { COMPUTE: 'maxUniformBuffersPerShaderStage', FRAGMENT: 'maxUniformBuffersPerShaderStage', VERTEX: 'maxUniformBuffersPerShaderStage' } }, 'storageBuf': { class: 'storageBuf', maxLimits: { COMPUTE: 'maxStorageBuffersPerShaderStage', FRAGMENT: 'maxStorageBuffersInFragmentStage', VERTEX: 'maxStorageBuffersInVertexStage' } }, 'sampler': { class: 'sampler', maxLimits: { COMPUTE: 'maxSamplersPerShaderStage', FRAGMENT: 'maxSamplersPerShaderStage', VERTEX: 'maxSamplersPerShaderStage' } }, 'sampledTex': { class: 'sampledTex', maxLimits: { COMPUTE: 'maxSampledTexturesPerShaderStage', FRAGMENT: 'maxSampledTexturesPerShaderStage', VERTEX: 'maxSampledTexturesPerShaderStage' } }, 'readonlyStorageTex': { class: 'readonlyStorageTex', maxLimits: { COMPUTE: 'maxStorageTexturesPerShaderStage', FRAGMENT: 'maxStorageTexturesInFragmentStage', VERTEX: 'maxStorageTexturesInVertexStage' } }, 'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxLimits: { COMPUTE: 'maxStorageTexturesPerShaderStage', FRAGMENT: 'maxStorageTexturesInFragmentStage', VERTEX: 'maxStorageTexturesInVertexStage' } }, 'readwriteStorageTex': { class: 'readwriteStorageTex', maxLimits: { COMPUTE: 'maxStorageTexturesPerShaderStage', FRAGMENT: 'maxStorageTexturesInFragmentStage', VERTEX: 'maxStorageTexturesInVertexStage'} }, }; /** * Default `PerPipelineLayout` binding limits, by spec. */ export const kPerPipelineBindingLimits: { readonly [k in PerPipelineBindingLimitClass]: { /** Which `PerPipelineLayout` binding limit class. */ readonly class: k; /** * The name of the limit for the maximum number of allowed bindings with `hasDynamicOffset: true` in that class. */ readonly maxDynamicLimit: (typeof kPossibleLimits)[number] | ''; // Add fields as needed }; } = /* prettier-ignore */ { 'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout', }, 'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout', }, 'sampler': { class: 'sampler', maxDynamicLimit: '', }, 'sampledTex': { class: 'sampledTex', maxDynamicLimit: '', }, 'readonlyStorageTex': { class: 'readonlyStorageTex', maxDynamicLimit: '', }, 'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxDynamicLimit: '', }, 'readwriteStorageTex': { class: 'readwriteStorageTex', maxDynamicLimit: '', }, }; interface BindingKindInfo { readonly resource: ValidBindableResource; readonly perStageLimitClass: (typeof kPerStageBindingLimits)[PerStageBindingLimitClass]; readonly perPipelineLimitClass: (typeof kPerPipelineBindingLimits)[PerPipelineBindingLimitClass]; // Add fields as needed } const kBindingKind: { readonly [k in ValidBindableResource]: BindingKindInfo; } = /* prettier-ignore */ { uniformBuf: { resource: 'uniformBuf', perStageLimitClass: kPerStageBindingLimits.uniformBuf, perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf, }, storageBuf: { resource: 'storageBuf', perStageLimitClass: kPerStageBindingLimits.storageBuf, perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf, }, filtSamp: { resource: 'filtSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, }, nonFiltSamp: { resource: 'nonFiltSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, }, compareSamp: { resource: 'compareSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, }, sampledTex: { resource: 'sampledTex', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, }, sampledTexMS: { resource: 'sampledTexMS', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, }, readonlyStorageTex: { resource: 'readonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.readonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readonlyStorageTex, }, writeonlyStorageTex: { resource: 'writeonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.writeonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.writeonlyStorageTex, }, readwriteStorageTex: { resource: 'readwriteStorageTex', perStageLimitClass: kPerStageBindingLimits.readwriteStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readwriteStorageTex, }, }; // Binding type info const kValidStagesAll = { validStages: GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE, } as const; const kValidStagesStorageWrite = { validStages: GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE, } as const; /** Binding type info (including class limits) for the specified GPUBufferBindingLayout. */ export function bufferBindingTypeInfo(d: GPUBufferBindingLayout) { /* prettier-ignore */ switch (d.type ?? 'uniform') { case 'uniform': return { usage: GPUConst.BufferUsage.UNIFORM, ...kBindingKind.uniformBuf, ...kValidStagesAll, }; case 'storage': return { usage: GPUConst.BufferUsage.STORAGE, ...kBindingKind.storageBuf, ...kValidStagesStorageWrite, }; case 'read-only-storage': return { usage: GPUConst.BufferUsage.STORAGE, ...kBindingKind.storageBuf, ...kValidStagesAll, }; } } /** List of all GPUBufferBindingType values. */ export const kBufferBindingTypes = ['uniform', 'storage', 'read-only-storage'] as const; assertTypeTrue>(); /** Binding type info (including class limits) for the specified GPUSamplerBindingLayout. */ export function samplerBindingTypeInfo(d: GPUSamplerBindingLayout) { /* prettier-ignore */ switch (d.type ?? 'filtering') { case 'filtering': return { ...kBindingKind.filtSamp, ...kValidStagesAll, }; case 'non-filtering': return { ...kBindingKind.nonFiltSamp, ...kValidStagesAll, }; case 'comparison': return { ...kBindingKind.compareSamp, ...kValidStagesAll, }; } } /** List of all GPUSamplerBindingType values. */ export const kSamplerBindingTypes = ['filtering', 'non-filtering', 'comparison'] as const; assertTypeTrue>(); /** Binding type info (including class limits) for the specified GPUTextureBindingLayout. */ export function sampledTextureBindingTypeInfo(d: GPUTextureBindingLayout) { /* prettier-ignore */ if (d.multisampled) { return { usage: GPUConst.TextureUsage.TEXTURE_BINDING, ...kBindingKind.sampledTexMS, ...kValidStagesAll, }; } else { return { usage: GPUConst.TextureUsage.TEXTURE_BINDING, ...kBindingKind.sampledTex, ...kValidStagesAll, }; } } /** List of all GPUTextureSampleType values. */ export const kTextureSampleTypes = [ 'float', 'unfilterable-float', 'depth', 'sint', 'uint', ] as const; assertTypeTrue>(); /** Binding type info (including class limits) for the specified GPUStorageTextureAccess. */ export function storageTextureBindingTypeInfo(d: { access?: GPUStorageTextureAccess | undefined }) { switch (d.access) { case undefined: case 'write-only': return { wgslAccess: 'write', usage: GPUConst.TextureUsage.STORAGE_BINDING, ...kBindingKind.writeonlyStorageTex, ...kValidStagesStorageWrite, }; case 'read-only': return { wgslAccess: 'read', usage: GPUConst.TextureUsage.STORAGE_BINDING, ...kBindingKind.readonlyStorageTex, ...kValidStagesAll, }; case 'read-write': return { wgslAccess: 'read_write', usage: GPUConst.TextureUsage.STORAGE_BINDING, ...kBindingKind.readwriteStorageTex, ...kValidStagesStorageWrite, }; } } /** List of all GPUStorageTextureAccess values. */ export const kStorageTextureAccessValues = ['read-only', 'read-write', 'write-only'] as const; assertTypeTrue>(); /** GPUBindGroupLayoutEntry, but only the "union" fields, not the common fields. */ export type BGLEntry = Omit; /** Binding type info (including class limits) for the specified BGLEntry. */ export function texBindingTypeInfo(e: BGLEntry) { if (e.texture !== undefined) return sampledTextureBindingTypeInfo(e.texture); if (e.storageTexture !== undefined) return storageTextureBindingTypeInfo(e.storageTexture); unreachable(); } /** BindingTypeInfo (including class limits) for the specified BGLEntry. */ export function bindingTypeInfo(e: BGLEntry) { if (e.buffer !== undefined) return bufferBindingTypeInfo(e.buffer); if (e.texture !== undefined) return sampledTextureBindingTypeInfo(e.texture); if (e.sampler !== undefined) return samplerBindingTypeInfo(e.sampler); if (e.storageTexture !== undefined) return storageTextureBindingTypeInfo(e.storageTexture); unreachable('GPUBindGroupLayoutEntry has no BindingLayout'); } /** * Generate a list of possible buffer-typed BGLEntry values. * * Note: Generates different `type` options, but not `hasDynamicOffset` options. */ export function bufferBindingEntries(includeUndefined: boolean): readonly BGLEntry[] { return [ ...(includeUndefined ? [{ buffer: { type: undefined } }] : []), { buffer: { type: 'uniform' } }, { buffer: { type: 'storage' } }, { buffer: { type: 'read-only-storage' } }, ] as const; } /** Generate a list of possible sampler-typed BGLEntry values. */ export function samplerBindingEntries(includeUndefined: boolean): readonly BGLEntry[] { return [ ...(includeUndefined ? [{ sampler: { type: undefined } }] : []), { sampler: { type: 'comparison' } }, { sampler: { type: 'filtering' } }, { sampler: { type: 'non-filtering' } }, ] as const; } /** * Generate a list of possible texture-typed BGLEntry values. * * Note: Generates different `multisampled` options, but not `sampleType` or `viewDimension` options. */ export function textureBindingEntries(includeUndefined: boolean): readonly BGLEntry[] { return [ ...(includeUndefined ? [{ texture: { multisampled: undefined, sampleType: 'unfilterable-float' } } as const] : []), { texture: { multisampled: false, sampleType: 'unfilterable-float' } }, { texture: { multisampled: true, sampleType: 'unfilterable-float' } }, ] as const; } /** * Generate a list of possible storageTexture-typed BGLEntry values. * * Note: Generates different `access` options, but not `format` or `viewDimension` options. */ export function storageTextureBindingEntries(format: GPUTextureFormat): readonly BGLEntry[] { return [ { storageTexture: { access: 'write-only', format } }, { storageTexture: { access: 'read-only', format } }, { storageTexture: { access: 'read-write', format } }, ] as const; } /** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */ export function sampledAndStorageBindingEntries( includeUndefined: boolean, format: GPUTextureFormat = 'r32float' ): readonly BGLEntry[] { return [ ...textureBindingEntries(includeUndefined), ...storageTextureBindingEntries(format), ] as const; } /** * Generate a list of possible BGLEntry values of every type, but not variants with different: * - buffer.hasDynamicOffset * - texture.sampleType * - texture.viewDimension * - storageTexture.viewDimension */ export function allBindingEntries( includeUndefined: boolean, format: GPUTextureFormat = 'r32float' ): readonly BGLEntry[] { return [ ...bufferBindingEntries(includeUndefined), ...samplerBindingEntries(includeUndefined), ...sampledAndStorageBindingEntries(includeUndefined, format), ] as const; } // Shader stages /** List of all GPUShaderStage values. */ export type ShaderStageKey = keyof typeof GPUConst.ShaderStage; export const kShaderStageKeys = Object.keys(GPUConst.ShaderStage) as ShaderStageKey[]; export const kShaderStages: readonly GPUShaderStageFlags[] = [ GPUConst.ShaderStage.VERTEX, GPUConst.ShaderStage.FRAGMENT, GPUConst.ShaderStage.COMPUTE, ]; /** List of all possible combinations of GPUShaderStage values. */ export const kShaderStageCombinations: readonly GPUShaderStageFlags[] = [0, 1, 2, 3, 4, 5, 6, 7]; export const kShaderStageCombinationsWithStage: readonly GPUShaderStageFlags[] = [ 1, 2, 3, 4, 5, 6, 7, ]; /** * List of all possible texture sampleCount values. * * MAINTENANCE_TODO: Switch existing tests to use kTextureSampleCounts */ export const kTextureSampleCounts = [1, 4] as const; // Sampler info /** List of all mipmap filter modes. */ export const kMipmapFilterModes: readonly GPUMipmapFilterMode[] = ['nearest', 'linear']; assertTypeTrue>(); /** List of address modes. */ export const kAddressModes: readonly GPUAddressMode[] = [ 'clamp-to-edge', 'repeat', 'mirror-repeat', ]; assertTypeTrue>(); // Blend factors and Blend components /** List of all GPUBlendFactor values. */ export const kBlendFactors: readonly GPUBlendFactor[] = [ 'zero', 'one', 'src', 'one-minus-src', 'src-alpha', 'one-minus-src-alpha', 'dst', 'one-minus-dst', 'dst-alpha', 'one-minus-dst-alpha', 'src-alpha-saturated', 'constant', 'one-minus-constant', 'src1', 'one-minus-src1', 'src1-alpha', 'one-minus-src1-alpha', ]; /** Check if `blendFactor` belongs to the blend factors in the extension "dual-source-blending". */ export function IsDualSourceBlendingFactor(blendFactor?: GPUBlendFactor): boolean { switch (blendFactor) { case 'src1': case 'one-minus-src1': case 'src1-alpha': case 'one-minus-src1-alpha': return true; default: return false; } } /** List of all GPUBlendOperation values. */ export const kBlendOperations: readonly GPUBlendOperation[] = [ 'add', 'subtract', 'reverse-subtract', 'min', 'max', ]; // Primitive topologies export const kPrimitiveTopology: readonly GPUPrimitiveTopology[] = [ 'point-list', 'line-list', 'line-strip', 'triangle-list', 'triangle-strip', ]; assertTypeTrue>(); export const kIndexFormat: readonly GPUIndexFormat[] = ['uint16', 'uint32']; assertTypeTrue>(); /** Info for each entry of GPUSupportedLimits */ const [kLimitInfoKeys, kLimitInfoDefaults, kLimitInfoData] = /* prettier-ignore */ [ [ 'class', 'core', 'compatibility', 'maximumValue'] as const, [ 'maximum', , , kMaxUnsignedLongValue] as const, { 'maxTextureDimension1D': [ , 8192, 4096, ], 'maxTextureDimension2D': [ , 8192, 4096, ], 'maxTextureDimension3D': [ , 2048, 2048, ], 'maxTextureArrayLayers': [ , 256, 256, ], 'maxBindGroups': [ , 4, 4, ], 'maxBindGroupsPlusVertexBuffers': [ , 24, 24, ], 'maxBindingsPerBindGroup': [ , 1000, 1000, ], 'maxDynamicUniformBuffersPerPipelineLayout': [ , 8, 8, ], 'maxDynamicStorageBuffersPerPipelineLayout': [ , 4, 4, ], 'maxSampledTexturesPerShaderStage': [ , 16, 16, ], 'maxSamplersPerShaderStage': [ , 16, 16, ], 'maxStorageBuffersInFragmentStage': [ , 8, 4, ], 'maxStorageBuffersInVertexStage': [ , 8, 0, ], 'maxStorageBuffersPerShaderStage': [ , 8, 8, ], 'maxStorageTexturesInFragmentStage': [ , 4, 4, ], 'maxStorageTexturesInVertexStage': [ , 4, 0, ], 'maxStorageTexturesPerShaderStage': [ , 4, 4, ], 'maxUniformBuffersPerShaderStage': [ , 12, 12, ], 'maxUniformBufferBindingSize': [ , 65536, 16384, kMaxUnsignedLongLongValue], 'maxStorageBufferBindingSize': [ , 134217728, 134217728, kMaxUnsignedLongLongValue], 'minUniformBufferOffsetAlignment': ['alignment', 256, 256, ], 'minStorageBufferOffsetAlignment': ['alignment', 256, 256, ], 'maxVertexBuffers': [ , 8, 8, ], 'maxBufferSize': [ , 268435456, 268435456, kMaxUnsignedLongLongValue], 'maxVertexAttributes': [ , 16, 16, ], 'maxVertexBufferArrayStride': [ , 2048, 2048, ], 'maxInterStageShaderVariables': [ , 16, 15, ], 'maxColorAttachments': [ , 8, 4, ], 'maxColorAttachmentBytesPerSample': [ , 32, 32, ], 'maxComputeWorkgroupStorageSize': [ , 16384, 16384, ], 'maxComputeInvocationsPerWorkgroup': [ , 256, 128, ], 'maxComputeWorkgroupSizeX': [ , 256, 128, ], 'maxComputeWorkgroupSizeY': [ , 256, 128, ], 'maxComputeWorkgroupSizeZ': [ , 64, 64, ], 'maxComputeWorkgroupsPerDimension': [ , 65535, 65535, ], // MAINTENANCE_TODO(4535): Consider allowing optional non-conforming limits. Currently they are not allowed. // Any limit here is immediately required by all implementations. // Also, consider having this table statically check that all limits listed in @webgpu/types exist in // this table. } as const]; /** * Feature levels corresponding to core WebGPU and WebGPU * in compatibility mode. They can be passed to * getDefaultLimits though if you have access to an adapter * it's preferred to use getDefaultLimits or getDefaultLimitsForCTS */ export const kFeatureLevels = ['core', 'compatibility'] as const; export type FeatureLevel = (typeof kFeatureLevels)[number]; const kLimitKeys = ['class', 'default', 'maximumValue'] as const; const kLimitInfoCore = makeTableRenameAndFilter( { default: 'core' }, kLimitKeys, kLimitInfoKeys, kLimitInfoDefaults, kLimitInfoData ); const kLimitInfoCompatibility = makeTableRenameAndFilter( { default: 'compatibility' }, kLimitKeys, kLimitInfoKeys, kLimitInfoDefaults, kLimitInfoData ); const kLimitInfos = { core: kLimitInfoCore, compatibility: kLimitInfoCompatibility, } as const; export const kLimitClasses = Object.fromEntries( Object.entries(kLimitInfoCore).map(([k, { class: c }]) => [k, c]) ); export function getDefaultLimits(featureLevel: FeatureLevel) { return kLimitInfos[featureLevel]; } /** * The CTS is generally designed to run in a single feature level. * Use this function get the default limits for the CTS's feature level * This is needed if you can not use the device limits as you have not yet * created a device. An adapter can not tell you if it supports compatibility * mode. The only way to know is to request a device without `core-features-and-limits`. * If the device you get back doesn't have `core-features-and-limits` then it's * a compatibility device. */ export function getDefaultLimitsForCTS() { return getDefaultLimits(globalTestConfig.compatibility ? 'compatibility' : 'core'); } export function getDefaultLimitsForDevice(device: GPUDevice) { const featureLevel = hasFeature(device.features, 'core-features-and-limits') ? 'core' : 'compatibility'; return getDefaultLimits(featureLevel); } const kEachStage = [ GPUConst.ShaderStage.COMPUTE, GPUConst.ShaderStage.FRAGMENT, GPUConst.ShaderStage.VERTEX, ]; function shaderStageFlagToStageName(stage: GPUShaderStageFlags) { switch (stage) { case GPUConst.ShaderStage.COMPUTE: return 'COMPUTE'; case GPUConst.ShaderStage.FRAGMENT: return 'FRAGMENT'; case GPUConst.ShaderStage.VERTEX: return 'VERTEX'; default: unreachable(); } } /** * Get the limit of the number of things you can bind for * a given BGLEntry given the specified visibility. This is * the minimum across stages for the given visibility. */ export function getBindingLimitForBindingType( device: GPUDevice, visibility: GPUShaderStageFlags, e: BGLEntry ) { const info = bindingTypeInfo(e); const maxLimits = info.perStageLimitClass.maxLimits; const limits = kEachStage .filter(stage => stage & visibility) .map(stage => device.limits[maxLimits[shaderStageFlagToStageName(stage)]]!); return limits.length > 0 ? Math.min(...limits) : 0; } /** List of all possible entries of GPUSupportedLimits. */ export const kPossibleLimits = keysOf(kLimitInfoCore); /** * The number of color attachments to test. * The CTS needs to generate a consistent list of tests. * We can't use any default limits since they different from core to compat mode * So, tests should use this value and filter out any values that are out of * range for the current device. * * The test in maxColorAttachments.spec.ts tests that kMaxColorAttachmentsToTest * is large enough to cover all devices tested. */ export const kMaxColorAttachmentsToTest = 32; /** The size of indirect draw parameters in the indirectBuffer of drawIndirect */ export const kDrawIndirectParametersSize = 4; /** The size of indirect drawIndexed parameters in the indirectBuffer of drawIndexedIndirect */ export const kDrawIndexedIndirectParametersSize = 5; /** Per-GPUFeatureName info. */ export const kFeatureNameInfo: { readonly [k in GPUFeatureName]: {}; } = /* prettier-ignore */ { 'bgra8unorm-storage': {}, 'depth-clip-control': {}, 'depth32float-stencil8': {}, 'texture-compression-bc': {}, 'texture-compression-bc-sliced-3d': {}, 'texture-compression-etc2': {}, 'texture-compression-astc': {}, 'texture-compression-astc-sliced-3d': {}, 'timestamp-query': {}, 'indirect-first-instance': {}, 'shader-f16': {}, 'rg11b10ufloat-renderable': {}, 'float32-filterable': {}, 'float32-blendable': {}, 'clip-distances': {}, 'dual-source-blending': {}, 'subgroups': {}, 'core-features-and-limits': {}, 'texture-formats-tier1': {}, 'texture-formats-tier2': {}, 'primitive-index': {}, 'texture-component-swizzle': {}, 'subgroup-size-control': {}, ['atomic-vec2u-min-max' as GPUFeatureName]: {}, ['texture-compression-unaligned' as GPUFeatureName]: {}, }; /** List of all GPUFeatureName values. */ export const kFeatureNames = keysOf(kFeatureNameInfo); /** List of all known WGSL language features */ export const kKnownWGSLLanguageFeatures = [ 'readonly_and_readwrite_storage_textures', 'packed_4x8_integer_dot_product', 'unrestricted_pointer_parameters', 'pointer_composite_access', 'uniform_buffer_standard_layout', 'texture_and_sampler_let', 'subgroup_id', 'subgroup_uniformity', 'swizzle_assignment', 'linear_indexing', 'texture_formats_tier1', 'immediate_address_space', 'fragment_depth', 'buffer_view', ] as const; export type WGSLLanguageFeature = (typeof kKnownWGSLLanguageFeatures)[number];