| export const description = ` |
| Tests that object attributes which reflect the object's creation properties are properly set. |
| `; |
| |
| import { makeTestGroup } from '../../../common/framework/test_group.js'; |
| import { GPUConst } from '../../constants.js'; |
| import { AllFeaturesMaxLimitsGPUTest, GPUTest } from '../../gpu_test.js'; |
| import { reifyExtent3D } from '../../util/unions.js'; |
| |
| export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest); |
| |
| function* extractValuePropertyKeys(obj: { [k: string]: unknown }) { |
| for (const key in obj) { |
| if (typeof obj[key] !== 'function') { |
| yield key; |
| } |
| } |
| } |
| |
| const kBufferSubcases: readonly { |
| size: number; |
| usage: GPUBufferUsageFlags; |
| label?: string; |
| invalid?: boolean; |
| }[] = [ |
| { size: 4, usage: GPUConst.BufferUsage.VERTEX }, |
| { |
| size: 16, |
| usage: |
| GPUConst.BufferUsage.STORAGE | GPUConst.BufferUsage.COPY_SRC | GPUConst.BufferUsage.UNIFORM, |
| }, |
| { size: 32, usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.COPY_DST }, |
| { size: 40, usage: GPUConst.BufferUsage.INDEX, label: 'some label' }, |
| { |
| size: 32, |
| usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.MAP_WRITE, |
| invalid: true, |
| }, |
| ] as const; |
| |
| g.test('buffer_reflection_attributes') |
| .desc(`For every buffer attribute, the corresponding descriptor value is carried over.`) |
| .paramsSubcasesOnly(u => u.combine('descriptor', kBufferSubcases)) |
| .fn(t => { |
| const { descriptor } = t.params; |
| |
| t.expectValidationError(() => { |
| const buffer = t.createBufferTracked(descriptor); |
| |
| t.expect(buffer.size === descriptor.size); |
| t.expect(buffer.usage === descriptor.usage); |
| }, descriptor.invalid === true); |
| }); |
| |
| g.test('buffer_creation_from_reflection') |
| .desc( |
| ` |
| Check that you can create a buffer from a buffer's reflection. |
| This check is to insure that as WebGPU develops this path doesn't |
| suddenly break because of new reflection. |
| ` |
| ) |
| .paramsSubcasesOnly(u => |
| u.combine('descriptor', kBufferSubcases).filter(p => !p.descriptor.invalid) |
| ) |
| |
| .fn(t => { |
| const { descriptor } = t.params; |
| |
| const buffer = t.createBufferTracked(descriptor); |
| const buffer2 = t.createBufferTracked(buffer); |
| |
| const bufferAsObject = buffer as unknown as { [k: string]: unknown }; |
| const buffer2AsObject = buffer2 as unknown as { [k: string]: unknown }; |
| const keys = [...extractValuePropertyKeys(bufferAsObject)]; |
| |
| // Sanity check |
| t.expect(keys.includes('size')); |
| t.expect(keys.includes('usage')); |
| t.expect(keys.includes('label')); |
| |
| for (const key of keys) { |
| t.expect(bufferAsObject[key] === buffer2AsObject[key], key); |
| } |
| }); |
| |
| const kTextureSubcases: readonly { |
| size: GPUExtent3D; |
| format: GPUTextureFormat; |
| usage: GPUTextureUsageFlags; |
| mipLevelCount?: number; |
| label?: string; |
| dimension?: GPUTextureDimension; |
| textureBindingViewDimension?: GPUTextureViewDimension; |
| sampleCount?: number; |
| invalid?: boolean; |
| }[] = [ |
| { |
| size: { width: 4, height: 4 }, |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| }, |
| { |
| size: { width: 4, height: 4 }, |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| label: 'some label', |
| }, |
| { |
| size: { width: 8, height: 8, depthOrArrayLayers: 8 }, |
| format: 'bgra8unorm', |
| usage: GPUConst.TextureUsage.RENDER_ATTACHMENT | GPUConst.TextureUsage.COPY_SRC, |
| }, |
| { |
| size: [4, 4], |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| mipLevelCount: 2, |
| }, |
| { |
| size: [4, 4], |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| mipLevelCount: 2, |
| textureBindingViewDimension: '2d', |
| }, |
| { |
| size: [4, 4], |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| mipLevelCount: 2, |
| textureBindingViewDimension: '2d-array', |
| }, |
| { |
| size: [4, 4, 4], |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| mipLevelCount: 2, |
| }, |
| { |
| size: [16, 16, 16], |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| dimension: '3d', |
| }, |
| { |
| size: [16, 16, 16], |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| dimension: '3d', |
| textureBindingViewDimension: '3d', |
| }, |
| { |
| size: [16, 16, 6], |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| dimension: '2d', |
| textureBindingViewDimension: 'cube', |
| }, |
| { |
| size: [32], |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| dimension: '1d', |
| }, |
| { |
| size: [32], |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| dimension: '1d', |
| textureBindingViewDimension: '1d', |
| }, |
| { |
| size: { width: 4, height: 4 }, |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.RENDER_ATTACHMENT, |
| sampleCount: 4, |
| }, |
| { |
| size: { width: 4, height: 4 }, |
| format: 'rgba8unorm', |
| usage: GPUConst.TextureUsage.TEXTURE_BINDING, |
| sampleCount: 4, |
| invalid: true, |
| }, |
| ] as const; |
| |
| function getExpectedTextureBindingViewDimension( |
| t: GPUTest, |
| descriptor: GPUTextureDescriptor |
| ): GPUTextureViewDimension | undefined { |
| if (t.isCompatibility) { |
| if (descriptor.textureBindingViewDimension) { |
| return descriptor.textureBindingViewDimension; |
| } |
| switch (descriptor.dimension) { |
| case '1d': |
| return '1d'; |
| case '2d': |
| case undefined: |
| return reifyExtent3D(descriptor.size).depthOrArrayLayers > 1 ? '2d-array' : '2d'; |
| case '3d': |
| return '3d'; |
| } |
| } else { |
| return undefined; |
| } |
| } |
| |
| g.test('texture_reflection_attributes') |
| .desc(`For every texture attribute, the corresponding descriptor value is carried over.`) |
| .paramsSubcasesOnly(u => u.combine('descriptor', kTextureSubcases)) |
| .fn(t => { |
| const { descriptor } = t.params; |
| |
| let width: number; |
| let height: number; |
| let depthOrArrayLayers: number; |
| if (Array.isArray(descriptor.size)) { |
| width = descriptor.size[0]; |
| height = descriptor.size[1] || 1; |
| depthOrArrayLayers = descriptor.size[2] || 1; |
| } else { |
| width = (descriptor.size as GPUExtent3DDict).width; |
| height = (descriptor.size as GPUExtent3DDict).height || 1; |
| depthOrArrayLayers = (descriptor.size as GPUExtent3DDict).depthOrArrayLayers || 1; |
| } |
| |
| t.expectValidationError(() => { |
| const texture = t.createTextureTracked(descriptor); |
| |
| t.expect(texture.width === width); |
| t.expect(texture.height === height); |
| t.expect(texture.depthOrArrayLayers === depthOrArrayLayers); |
| t.expect(texture.format === descriptor.format); |
| t.expect(texture.usage === descriptor.usage); |
| t.expect(texture.dimension === (descriptor.dimension || '2d')); |
| t.expect(texture.mipLevelCount === (descriptor.mipLevelCount || 1)); |
| t.expect(texture.sampleCount === (descriptor.sampleCount || 1)); |
| t.expect( |
| texture.textureBindingViewDimension === |
| getExpectedTextureBindingViewDimension(t, descriptor) |
| ); |
| }, descriptor.invalid === true); |
| }); |
| |
| interface TextureWithSize extends GPUTexture { |
| size: GPUExtent3D; |
| } |
| |
| g.test('texture_creation_from_reflection') |
| .desc( |
| ` |
| Check that you can create a texture from a texture's reflection. |
| This check is to insure that as WebGPU develops this path doesn't |
| suddenly break because of new reflection. |
| ` |
| ) |
| .paramsSubcasesOnly(u => |
| u.combine('descriptor', kTextureSubcases).filter(p => !p.descriptor.invalid) |
| ) |
| .fn(t => { |
| const { descriptor } = t.params; |
| |
| const texture = t.createTextureTracked(descriptor); |
| const textureWithSize = texture as TextureWithSize; |
| textureWithSize.size = [texture.width, texture.height, texture.depthOrArrayLayers]; |
| const texture2 = t.createTextureTracked(textureWithSize); |
| |
| const textureAsObject = texture as unknown as { [k: string]: unknown }; |
| const texture2AsObject = texture2 as unknown as { [k: string]: unknown }; |
| const keys = [...extractValuePropertyKeys(textureAsObject)].filter(k => k !== 'size'); |
| |
| // Sanity check |
| t.expect(keys.includes('format')); |
| t.expect(keys.includes('usage')); |
| t.expect(keys.includes('label')); |
| |
| for (const key of keys) { |
| t.expect(textureAsObject[key] === texture2AsObject[key], key); |
| } |
| |
| // MAINTENANCE_TODO: Check this if it is made possible by a spec change. |
| // |
| // texture3 = t.createTextureTracked({ |
| // ...texture, |
| // size: [texture.width, texture.height, texture.depthOrArrayLayers], |
| // }); |
| // |
| // and this |
| // |
| // texture3 = t.createTextureTracked({ |
| // size: [texture.width, texture.height, texture.depthOrArrayLayers], |
| // ...texture, |
| // }); |
| }); |
| |
| const kQuerySetSubcases: readonly { |
| type: GPUQueryType; |
| count: number; |
| label?: string; |
| invalid?: boolean; |
| }[] = [ |
| { type: 'occlusion', count: 4 }, |
| { type: 'occlusion', count: 16 }, |
| { type: 'occlusion', count: 32, label: 'some label' }, |
| { type: 'occlusion', count: 8193, invalid: true }, |
| ] as const; |
| |
| g.test('query_set_reflection_attributes') |
| .desc(`For every queue attribute, the corresponding descriptor value is carried over.`) |
| .paramsSubcasesOnly(u => u.combine('descriptor', kQuerySetSubcases)) |
| .fn(t => { |
| const { descriptor } = t.params; |
| |
| t.expectValidationError(() => { |
| const querySet = t.createQuerySetTracked(descriptor); |
| |
| t.expect(querySet.type === descriptor.type); |
| t.expect(querySet.count === descriptor.count); |
| }, descriptor.invalid === true); |
| }); |
| |
| g.test('query_set_creation_from_reflection') |
| .desc( |
| ` |
| Check that you can create a queryset from a queryset's reflection. |
| This check is to insure that as WebGPU develops this path doesn't |
| suddenly break because of new reflection. |
| ` |
| ) |
| .paramsSubcasesOnly(u => |
| u.combine('descriptor', kQuerySetSubcases).filter(p => !p.descriptor.invalid) |
| ) |
| .fn(t => { |
| const { descriptor } = t.params; |
| |
| const querySet = t.createQuerySetTracked(descriptor); |
| const querySet2 = t.createQuerySetTracked(querySet); |
| |
| const querySetAsObject = querySet as unknown as { [k: string]: unknown }; |
| const querySet2AsObject = querySet2 as unknown as { [k: string]: unknown }; |
| const keys = [...extractValuePropertyKeys(querySetAsObject)]; |
| |
| // Sanity check |
| t.expect(keys.includes('type')); |
| t.expect(keys.includes('count')); |
| t.expect(keys.includes('label')); |
| |
| for (const key of keys) { |
| t.expect(querySetAsObject[key] === querySet2AsObject[key], key); |
| } |
| }); |