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); } });