Skip to content
73 changes: 73 additions & 0 deletions src/webgpu/api/operation/compute/basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,76 @@ g.test('large_dispatch')

dst.destroy();
});

g.test('buffer_binding_resource')
.desc(
`Validate the correctness of the buffer binding resource by filling the buffer with
testable data, clearing buffer in shader, and verifying the content of the whole buffer:
- covers the whole buffer
- covers the beginning of the buffer
- covers the end of the buffer
- covers neither the beginning nor the end of the buffer`
)
.paramsSubcasesOnly(u =>
u //
.combine('bindBufferResource', [false, true] as const)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could avoid generating the subcases with offset / size when bindBufferResource is true. Also bufferSize could be extraBufferSize: [0, 8] and the computation done in the test.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

.combine('offset', [0, 256, undefined])
.combine('size', [4, 8, undefined])
.expand('bufferSize', p => [
(p.offset ?? 0) + (p.size ?? 16),
(p.offset ?? 0) + (p.size ?? 16) + 8,
])
)
.fn(t => {
const { offset, size, bufferSize, bindBufferResource } = t.params;

const bufferData = new Uint8Array(bufferSize);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const bufferData = new Uint8Array(bufferSize);
const bufferData = new Uint32Array(bufferSize / 4);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional nit

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This forces me to add / 4 in other places so if that's okay, I'll leave it as is.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

for (let i = 0; i < bufferSize; ++i) {
bufferData[i] = i + 1;
}

const buffer = t.makeBufferWithContents(
bufferData,
GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
);

const pipeline = t.device.createComputePipeline({
layout: 'auto',
compute: {
module: t.device.createShaderModule({
code: `
@group(0) @binding(0) var<storage, read_write> buffer : array<u32>;

@compute @workgroup_size(1) fn main() {
for (var i = 0u; i < arrayLength(&buffer); i = i + 1u) {
buffer[i] = 0;
}
return;
}`,
}),
},
});

const resource = bindBufferResource ? buffer : { buffer, offset, size };
const bg = t.device.createBindGroup({
entries: [{ binding: 0, resource }],
layout: pipeline.getBindGroupLayout(0),
});

const encoder = t.device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bg);
pass.dispatchWorkgroups(1);
pass.end();
t.device.queue.submit([encoder.finish()]);

const expectOffset = bindBufferResource ? 0 : offset ?? 0;
const expectSize = bindBufferResource ? bufferSize : size ?? bufferSize - expectOffset;

for (let i = 0; i < expectSize; ++i) {
bufferData[expectOffset + i] = 0;
}

t.expectGPUBufferValuesEqual(buffer, bufferData);
});
39 changes: 34 additions & 5 deletions src/webgpu/api/operation/resource_init/buffer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,32 @@ class F extends AllFeaturesMaxLimitsGPUTest {
buffer: GPUBuffer,
bufferOffset: number,
boundBufferSize: number
): void {
this.TestBufferZeroInitInBindGroupInternal(
computeShaderModule,
buffer,
false,
bufferOffset,
boundBufferSize
);
const bindBufferResource = bufferOffset === 0 && boundBufferSize === buffer.size;
if (bindBufferResource) {
this.TestBufferZeroInitInBindGroupInternal(
computeShaderModule,
buffer,
true,
bufferOffset,
boundBufferSize
);
}
}

TestBufferZeroInitInBindGroupInternal(
computeShaderModule: GPUShaderModule,
buffer: GPUBuffer,
bindBufferResource: boolean,
bufferOffset: number,
boundBufferSize: number
): void {
const computePipeline = this.device.createComputePipeline({
layout: 'auto',
Expand All @@ -61,16 +87,19 @@ class F extends AllFeaturesMaxLimitsGPUTest {
size: [1, 1, 1],
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.STORAGE_BINDING,
});
const resource = bindBufferResource
? buffer
: {
buffer,
offset: bufferOffset,
size: boundBufferSize,
};
const bindGroup = this.device.createBindGroup({
layout: computePipeline.getBindGroupLayout(0),
entries: [
{
binding: 0,
resource: {
buffer,
offset: bufferOffset,
size: boundBufferSize,
},
resource,
},
{
binding: 1,
Expand Down
14 changes: 8 additions & 6 deletions src/webgpu/api/validation/createBindGroup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ g.test('buffer_offset_and_size_for_bind_groups_match')
.paramsSubcasesOnly([
{ offset: 0, size: 512, _success: true }, // offset 0 is valid
{ offset: 256, size: 256, _success: true }, // offset 256 (aligned) is valid
{ bindBufferResource: true, _success: true }, // full buffer is valid

// Touching the end of the buffer
{ offset: 0, size: 1024, _success: true },
Expand All @@ -471,7 +472,7 @@ g.test('buffer_offset_and_size_for_bind_groups_match')
{ offset: 1024, size: 1, _success: false }, // offset+size is OOB
])
.fn(t => {
const { offset, size, _success } = t.params;
const { bindBufferResource, offset, size, _success } = t.params;

const bindGroupLayout = t.device.createBindGroupLayout({
entries: [{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } }],
Expand All @@ -482,11 +483,12 @@ g.test('buffer_offset_and_size_for_bind_groups_match')
usage: GPUBufferUsage.STORAGE,
});

const resource = bindBufferResource ? buffer : { buffer, offset, size };
const descriptor = {
entries: [
{
binding: 0,
resource: { buffer, offset, size },
resource,
},
],
layout: bindGroupLayout,
Expand Down Expand Up @@ -561,9 +563,10 @@ g.test('buffer,resource_state')
.combine('state', kResourceStates)
.combine('entry', bufferBindingEntries(true))
.combine('visibilityMask', [kAllShaderStages, GPUConst.ShaderStage.COMPUTE] as const)
.combine('bindBufferResource', [false, true] as const)
)
.fn(t => {
const { state, entry, visibilityMask } = t.params;
const { state, entry, visibilityMask, bindBufferResource } = t.params;

assert(entry.buffer !== undefined);
const info = bufferBindingTypeInfo(entry.buffer);
Expand All @@ -586,15 +589,14 @@ g.test('buffer,resource_state')
size: 4,
});

const resource = bindBufferResource ? buffer : { buffer };
t.expectValidationError(() => {
t.device.createBindGroup({
layout: bgl,
entries: [
{
binding: 0,
resource: {
buffer,
},
resource,
},
],
});
Expand Down
Loading