Skip to content

Commit

Permalink
Test construction by reflection
Browse files Browse the repository at this point in the history
This is to make sure future spec changes don't break code
that is using this pattern.
  • Loading branch information
greggman committed Dec 13, 2023
1 parent 7a6ef73 commit 666950d
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 70 deletions.
293 changes: 223 additions & 70 deletions src/webgpu/api/operation/reflection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,38 @@ import { GPUTest } from '../../gpu_test.js';

export const g = makeTestGroup(GPUTest);

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', [
{ 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: 32,
usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.MAP_WRITE,
invalid: true,
},
] as const)
)
.paramsSubcasesOnly(u => u.combine('descriptor', kBufferSubcases))
.fn(t => {
const { descriptor } = t.params;

Expand All @@ -39,53 +51,102 @@ g.test('buffer_reflection_attributes')
}, descriptor.invalid === true);
});

g.test('texture_reflection_attributes')
.desc(`For every texture attribute, the corresponding descriptor value is carried over.`)
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', [
{
size: { width: 4, height: 4 },
format: 'rgba8unorm',
usage: GPUConst.TextureUsage.TEXTURE_BINDING,
},
{
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: [16, 16, 16],
format: 'rgba8unorm',
usage: GPUConst.TextureUsage.TEXTURE_BINDING,
dimension: '3d',
},
{
size: [32],
format: 'rgba8unorm',
usage: GPUConst.TextureUsage.TEXTURE_BINDING,
dimension: '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)
u.combine('descriptor', kBufferSubcases).filter(p => !p.descriptor.invalid)
)

.fn(t => {
const { descriptor } = t.params;

const buffer = t.device.createBuffer(descriptor);
t.trackForCleanup(buffer);
const buffer2 = t.device.createBuffer(buffer);
t.trackForCleanup(buffer2);

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;
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: [16, 16, 16],
format: 'rgba8unorm',
usage: GPUConst.TextureUsage.TEXTURE_BINDING,
dimension: '3d',
},
{
size: [32],
format: 'rgba8unorm',
usage: GPUConst.TextureUsage.TEXTURE_BINDING,
dimension: '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;

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;

Expand Down Expand Up @@ -116,22 +177,114 @@ g.test('texture_reflection_attributes')
}, descriptor.invalid === true);
});

g.test('query_set_reflection_attributes')
.desc(`For every queue attribute, the corresponding descriptor value is carried over.`)
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', [
{ type: 'occlusion', count: 4 },
{ type: 'occlusion', count: 16 },
{ type: 'occlusion', count: 8193, invalid: true },
] as const)
u.combine('descriptor', kTextureSubcases).filter(p => !p.descriptor.invalid)
)
.fn(t => {
const { descriptor } = t.params;

const texture = t.device.createTexture(descriptor);
t.trackForCleanup(texture);
const textureWithSize = texture as TextureWithSize;
textureWithSize.size = [texture.width, texture.height, texture.depthOrArrayLayers];
const texture2 = t.device.createTexture(textureWithSize);
t.trackForCleanup(texture2);

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.device.createTexture({
// ...texture,
// size: [texture.width, texture.height, texture.depthOrArrayLayers],
// });
//
// and this
//
// texture3 = t.device.createTexture({
// 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.device.createQuerySet(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.device.createQuerySet(descriptor);
t.trackForCleanup(querySet);
const querySet2 = t.device.createQuerySet(querySet);
t.trackForCleanup(querySet2);

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);
}
});
3 changes: 3 additions & 0 deletions src/webgpu/listing_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,11 @@
"webgpu:api,operation,pipeline,default_layout:layout:*": { "subcaseMS": 11.500 },
"webgpu:api,operation,queue,writeBuffer:array_types:*": { "subcaseMS": 12.032 },
"webgpu:api,operation,queue,writeBuffer:multiple_writes_at_different_offsets_and_sizes:*": { "subcaseMS": 2.087 },
"webgpu:api,operation,reflection:buffer_creation_from_reflection:*": { "subcaseMS": 0.800 },
"webgpu:api,operation,reflection:buffer_reflection_attributes:*": { "subcaseMS": 0.800 },
"webgpu:api,operation,reflection:query_set_creation_from_reflection:*": { "subcaseMS": 0.634 },
"webgpu:api,operation,reflection:query_set_reflection_attributes:*": { "subcaseMS": 0.634 },
"webgpu:api,operation,reflection:texture_creation_from_reflection:*": { "subcaseMS": 1.829 },
"webgpu:api,operation,reflection:texture_reflection_attributes:*": { "subcaseMS": 1.829 },
"webgpu:api,operation,render_pass,clear_value:layout:*": { "subcaseMS": 1.401 },
"webgpu:api,operation,render_pass,clear_value:loaded:*": { "subcaseMS": 14.300 },
Expand Down

0 comments on commit 666950d

Please sign in to comment.