blob: 61ff346027ced07f1929a6ff9d3edf20a339456c [file] [log] [blame] [edit]
export const description = `
createBindGroup validation tests.
`;
import { poptions, params } from '../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { unreachable } from '../../../common/framework/util/util.js';
import {
kBindingTypes,
kBindingTypeInfo,
kBindableResources,
kTextureUsages,
kTextureBindingTypes,
kTextureBindingTypeInfo,
} from '../../capability_info.js';
import { ValidationTest } from './validation_test.js';
function clone<T extends GPUTextureDescriptor>(descriptor: T): T {
return JSON.parse(JSON.stringify(descriptor));
}
export const g = makeTestGroup(ValidationTest);
g.test('binding_count_mismatch').fn(async t => {
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
});
const goodDescriptor = {
entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }],
layout: bindGroupLayout,
};
// Control case
t.device.createBindGroup(goodDescriptor);
// Another binding is not expected.
const badDescriptor = {
entries: [
{ binding: 0, resource: { buffer: t.getStorageBuffer() } },
// Another binding is added.
{ binding: 1, resource: { buffer: t.getStorageBuffer() } },
],
layout: bindGroupLayout,
};
t.expectValidationError(() => {
t.device.createBindGroup(badDescriptor);
});
});
g.test('binding_must_be_present_in_layout').fn(async t => {
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
});
const goodDescriptor = {
entries: [{ binding: 0, resource: { buffer: t.getStorageBuffer() } }],
layout: bindGroupLayout,
};
// Control case
t.device.createBindGroup(goodDescriptor);
// Binding index 0 must be present.
const badDescriptor = {
entries: [{ binding: 1, resource: { buffer: t.getStorageBuffer() } }],
layout: bindGroupLayout,
};
t.expectValidationError(() => {
t.device.createBindGroup(badDescriptor);
});
});
g.test('buffer_binding_must_contain_exactly_one_buffer_of_its_type')
.params(
params()
.combine(poptions('bindingType', kBindingTypes))
.combine(poptions('resourceType', kBindableResources))
)
.fn(t => {
const { bindingType, resourceType } = t.params;
const info = kBindingTypeInfo[bindingType];
const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
const layout = t.device.createBindGroupLayout({
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: bindingType, storageTextureFormat },
],
});
const resource = t.getBindingResource(resourceType);
const resourceBindingMatches = info.resource === resourceType;
t.expectValidationError(() => {
t.device.createBindGroup({ layout, entries: [{ binding: 0, resource }] });
}, !resourceBindingMatches);
});
g.test('texture_binding_must_have_correct_usage')
.params(
params()
.combine(poptions('type', kTextureBindingTypes))
.combine(poptions('usage', kTextureUsages))
)
.fn(async t => {
const { type, usage } = t.params;
const info = kTextureBindingTypeInfo[type];
const storageTextureFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type, storageTextureFormat }],
});
const descriptor = {
size: { width: 16, height: 16, depth: 1 },
format: 'rgba8unorm' as const,
usage,
};
const shouldError = usage !== info.usage;
t.expectValidationError(() => {
t.device.createBindGroup({
entries: [{ binding: 0, resource: t.device.createTexture(descriptor).createView() }],
layout: bindGroupLayout,
});
}, shouldError);
});
g.test('texture_must_have_correct_component_type')
.params(poptions('textureComponentType', ['float', 'sint', 'uint'] as const))
.fn(async t => {
const { textureComponentType } = t.params;
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
type: 'sampled-texture',
textureComponentType,
},
],
});
// TODO: Test more texture component types.
let format: GPUTextureFormat;
if (textureComponentType === 'float') {
format = 'r8unorm';
} else if (textureComponentType === 'sint') {
format = 'r8sint';
} else if (textureComponentType === 'uint') {
format = 'r8uint';
} else {
unreachable('Unexpected texture component type');
}
const goodDescriptor = {
size: { width: 16, height: 16, depth: 1 },
format,
usage: GPUTextureUsage.SAMPLED,
};
// Control case
t.device.createBindGroup({
entries: [
{
binding: 0,
resource: t.device.createTexture(goodDescriptor).createView(),
},
],
layout: bindGroupLayout,
});
function* mismatchedTextureFormats(): Iterable<GPUTextureFormat> {
if (textureComponentType !== 'float') {
yield 'r8unorm';
}
if (textureComponentType !== 'sint') {
yield 'r8sint';
}
if (textureComponentType !== 'uint') {
yield 'r8uint';
}
}
// Mismatched texture binding formats are not valid.
for (const mismatchedTextureFormat of mismatchedTextureFormats()) {
const badDescriptor: GPUTextureDescriptor = clone(goodDescriptor);
badDescriptor.format = mismatchedTextureFormat;
t.expectValidationError(() => {
t.device.createBindGroup({
entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }],
layout: bindGroupLayout,
});
});
}
});
// TODO: Write test for all dimensions.
g.test('texture_must_have_correct_dimension').fn(async t => {
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
type: 'sampled-texture',
viewDimension: '2d',
},
],
});
const goodDescriptor = {
size: { width: 16, height: 16, depth: 1 },
format: 'rgba8unorm' as const,
usage: GPUTextureUsage.SAMPLED,
};
// Control case
t.device.createBindGroup({
entries: [{ binding: 0, resource: t.device.createTexture(goodDescriptor).createView() }],
layout: bindGroupLayout,
});
// Mismatched texture binding formats are not valid.
const badDescriptor = clone(goodDescriptor);
badDescriptor.size.depth = 2;
t.expectValidationError(() => {
t.device.createBindGroup({
entries: [{ binding: 0, resource: t.device.createTexture(badDescriptor).createView() }],
layout: bindGroupLayout,
});
});
});
g.test('buffer_offset_and_size_for_bind_groups_match')
.params([
{ offset: 0, size: 512, _success: true }, // offset 0 is valid
{ offset: 256, size: 256, _success: true }, // offset 256 (aligned) is valid
// Touching the end of the buffer
{ offset: 0, size: 1024, _success: true },
{ offset: 0, size: undefined, _success: true },
{ offset: 256 * 3, size: 256, _success: true },
{ offset: 256 * 3, size: undefined, _success: true },
// Zero-sized bindings
{ offset: 0, size: 0, _success: true },
{ offset: 256, size: 0, _success: true },
{ offset: 1024, size: 0, _success: true },
{ offset: 1024, size: undefined, _success: true },
// Unaligned buffer offset is invalid
{ offset: 1, size: 256, _success: false },
{ offset: 1, size: undefined, _success: false },
{ offset: 128, size: 256, _success: false },
{ offset: 255, size: 256, _success: false },
// Out-of-bounds
{ offset: 256 * 5, size: 0, _success: false }, // offset is OOB
{ offset: 0, size: 256 * 5, _success: false }, // size is OOB
{ offset: 1024, size: 1, _success: false }, // offset+size is OOB
])
.fn(async t => {
const { offset, size, _success } = t.params;
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, type: 'storage-buffer' }],
});
const buffer = t.device.createBuffer({
size: 1024,
usage: GPUBufferUsage.STORAGE,
});
const descriptor = {
entries: [
{
binding: 0,
resource: { buffer, offset, size },
},
],
layout: bindGroupLayout,
};
if (_success) {
// Control case
t.device.createBindGroup(descriptor);
} else {
// Buffer offset and/or size don't match in bind groups.
t.expectValidationError(() => {
t.device.createBindGroup(descriptor);
});
}
});