Sort same-type bindings in the BGL by binding number
This fixes a bug where dynamic offsets were applied to the wrong bindings. Dynamic offsets are applied in increasing order of binding number. Bug: dawn:408 Change-Id: I3de6ee1bfd6e00239ddc46f820c3f81ba82815cb Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/21620 Reviewed-by: Corentin Wallez <cwallez@chromium.org> Reviewed-by: Stephen White <senorblanco@chromium.org> Commit-Queue: Austin Eng <enga@chromium.org>
This commit is contained in:
parent
b6eeee0aa3
commit
fce44b5fe1
|
@ -214,6 +214,12 @@ namespace dawn_native {
|
||||||
// Buffers have smaller type enums. They should be placed first.
|
// Buffers have smaller type enums. They should be placed first.
|
||||||
return a.type < b.type;
|
return a.type < b.type;
|
||||||
}
|
}
|
||||||
|
if (a.binding != b.binding) {
|
||||||
|
// Above we ensure that dynamic buffers are first. Now, ensure that bindings are in
|
||||||
|
// increasing order. This is because dynamic buffer offsets are applied in
|
||||||
|
// increasing order of binding number.
|
||||||
|
return a.binding < b.binding;
|
||||||
|
}
|
||||||
if (a.visibility != b.visibility) {
|
if (a.visibility != b.visibility) {
|
||||||
return a.visibility < b.visibility;
|
return a.visibility < b.visibility;
|
||||||
}
|
}
|
||||||
|
|
|
@ -745,6 +745,89 @@ TEST_P(BindGroupTests, DrawThenChangePipelineAndBindGroup) {
|
||||||
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max);
|
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderPass.color, max, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test for crbug.com/dawn/408 where dynamic offsets were applied in the wrong order.
|
||||||
|
// Dynamic offsets should be applied in increasing order of binding number.
|
||||||
|
TEST_P(BindGroupTests, DynamicOffsetOrder) {
|
||||||
|
// We will put the following values and the respective offsets into a buffer.
|
||||||
|
// The test will ensure that the correct dynamic offset is applied to each buffer by reading the
|
||||||
|
// value from an offset binding.
|
||||||
|
std::array<uint32_t, 3> offsets = {3 * kMinDynamicBufferOffsetAlignment,
|
||||||
|
1 * kMinDynamicBufferOffsetAlignment,
|
||||||
|
2 * kMinDynamicBufferOffsetAlignment};
|
||||||
|
std::array<uint32_t, 3> values = {21, 67, 32};
|
||||||
|
|
||||||
|
// Create three buffers large enough to by offset by the largest offset.
|
||||||
|
wgpu::BufferDescriptor bufferDescriptor;
|
||||||
|
bufferDescriptor.size = 3 * kMinDynamicBufferOffsetAlignment + sizeof(uint32_t);
|
||||||
|
bufferDescriptor.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
|
||||||
|
|
||||||
|
wgpu::Buffer buffer0 = device.CreateBuffer(&bufferDescriptor);
|
||||||
|
wgpu::Buffer buffer2 = device.CreateBuffer(&bufferDescriptor);
|
||||||
|
wgpu::Buffer buffer3 = device.CreateBuffer(&bufferDescriptor);
|
||||||
|
|
||||||
|
// Populate the values
|
||||||
|
buffer0.SetSubData(offsets[0], sizeof(uint32_t), &values[0]);
|
||||||
|
buffer2.SetSubData(offsets[1], sizeof(uint32_t), &values[1]);
|
||||||
|
buffer3.SetSubData(offsets[2], sizeof(uint32_t), &values[2]);
|
||||||
|
|
||||||
|
wgpu::Buffer outputBuffer = utils::CreateBufferFromData(
|
||||||
|
device, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::Storage, {0, 0, 0});
|
||||||
|
|
||||||
|
// Create the bind group and bind group layout.
|
||||||
|
// Note: The order of the binding numbers are intentionally different and not in increasing
|
||||||
|
// order.
|
||||||
|
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
|
||||||
|
device, {
|
||||||
|
{3, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer, true},
|
||||||
|
{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer, true},
|
||||||
|
{2, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer, true},
|
||||||
|
{4, wgpu::ShaderStage::Compute, wgpu::BindingType::StorageBuffer},
|
||||||
|
});
|
||||||
|
wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl,
|
||||||
|
{
|
||||||
|
{0, buffer0, 0, sizeof(uint32_t)},
|
||||||
|
{3, buffer3, 0, sizeof(uint32_t)},
|
||||||
|
{2, buffer2, 0, sizeof(uint32_t)},
|
||||||
|
{4, outputBuffer, 0, 3 * sizeof(uint32_t)},
|
||||||
|
});
|
||||||
|
|
||||||
|
wgpu::ComputePipelineDescriptor pipelineDescriptor;
|
||||||
|
pipelineDescriptor.computeStage.module =
|
||||||
|
utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
|
||||||
|
#version 450
|
||||||
|
layout(std430, set = 0, binding = 2) readonly buffer Buffer2 {
|
||||||
|
uint value2;
|
||||||
|
};
|
||||||
|
layout(std430, set = 0, binding = 3) readonly buffer Buffer3 {
|
||||||
|
uint value3;
|
||||||
|
};
|
||||||
|
layout(std430, set = 0, binding = 0) readonly buffer Buffer0 {
|
||||||
|
uint value0;
|
||||||
|
};
|
||||||
|
layout(std430, set = 0, binding = 4) buffer OutputBuffer {
|
||||||
|
uvec3 outputBuffer;
|
||||||
|
};
|
||||||
|
void main() {
|
||||||
|
outputBuffer = uvec3(value0, value2, value3);
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
pipelineDescriptor.computeStage.entryPoint = "main";
|
||||||
|
pipelineDescriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl);
|
||||||
|
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDescriptor);
|
||||||
|
|
||||||
|
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
|
||||||
|
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
|
||||||
|
computePassEncoder.SetPipeline(pipeline);
|
||||||
|
computePassEncoder.SetBindGroup(0, bindGroup, offsets.size(), offsets.data());
|
||||||
|
computePassEncoder.Dispatch(1);
|
||||||
|
computePassEncoder.EndPass();
|
||||||
|
|
||||||
|
wgpu::CommandBuffer commands = commandEncoder.Finish();
|
||||||
|
queue.Submit(1, &commands);
|
||||||
|
|
||||||
|
EXPECT_BUFFER_U32_RANGE_EQ(values.data(), outputBuffer, 0, values.size());
|
||||||
|
}
|
||||||
|
|
||||||
// Test that visibility of bindings in BindGroupLayout can be none
|
// Test that visibility of bindings in BindGroupLayout can be none
|
||||||
// This test passes by not asserting or crashing.
|
// This test passes by not asserting or crashing.
|
||||||
TEST_P(BindGroupTests, BindGroupLayoutVisibilityCanBeNone) {
|
TEST_P(BindGroupTests, BindGroupLayoutVisibilityCanBeNone) {
|
||||||
|
|
|
@ -1009,6 +1009,97 @@ TEST_F(SetBindGroupValidationTest, BindingSizeOutOfBoundDynamicStorageBuffer) {
|
||||||
TestComputePassBindGroup(bindGroup, offsets.data(), 3, false);
|
TestComputePassBindGroup(bindGroup, offsets.data(), 3, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test for crbug.com/dawn/408 where dynamic offsets were applied in the wrong order.
|
||||||
|
// Dynamic offsets should be applied in increasing order of binding number.
|
||||||
|
TEST_F(SetBindGroupValidationTest, DynamicOffsetOrder) {
|
||||||
|
// Note: The order of the binding numbers of the bind group and bind group layout are
|
||||||
|
// intentionally different and not in increasing order.
|
||||||
|
wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
|
||||||
|
device, {
|
||||||
|
{3, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer, true},
|
||||||
|
{0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer, true},
|
||||||
|
{2, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageBuffer, true},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create buffers which are 3x, 2x, and 1x the size of the minimum buffer offset, plus 4 bytes
|
||||||
|
// to spare (to avoid zero-sized bindings). We will offset the bindings so they reach the very
|
||||||
|
// end of the buffer. Any mismatch applying too-large of an offset to a smaller buffer will hit the
|
||||||
|
// out-of-bounds condition during validation.
|
||||||
|
wgpu::Buffer buffer3x =
|
||||||
|
CreateBuffer(3 * kMinDynamicBufferOffsetAlignment + 4, wgpu::BufferUsage::Storage);
|
||||||
|
wgpu::Buffer buffer2x =
|
||||||
|
CreateBuffer(2 * kMinDynamicBufferOffsetAlignment + 4, wgpu::BufferUsage::Storage);
|
||||||
|
wgpu::Buffer buffer1x =
|
||||||
|
CreateBuffer(1 * kMinDynamicBufferOffsetAlignment + 4, wgpu::BufferUsage::Storage);
|
||||||
|
wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, bgl,
|
||||||
|
{
|
||||||
|
{0, buffer3x, 0, 4},
|
||||||
|
{3, buffer2x, 0, 4},
|
||||||
|
{2, buffer1x, 0, 4},
|
||||||
|
});
|
||||||
|
|
||||||
|
std::array<uint32_t, 3> offsets;
|
||||||
|
{
|
||||||
|
// Base case works.
|
||||||
|
offsets = {/* binding 0 */ 0,
|
||||||
|
/* binding 2 */ 0,
|
||||||
|
/* binding 3 */ 0};
|
||||||
|
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
|
||||||
|
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
|
||||||
|
computePassEncoder.SetBindGroup(0, bindGroup, offsets.size(), offsets.data());
|
||||||
|
computePassEncoder.EndPass();
|
||||||
|
commandEncoder.Finish();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Offset the first binding to touch the end of the buffer. Should succeed.
|
||||||
|
// Will fail if the offset is applied to the first or second bindings since their buffers
|
||||||
|
// are too small.
|
||||||
|
offsets = {/* binding 0 */ 3 * kMinDynamicBufferOffsetAlignment,
|
||||||
|
/* binding 2 */ 0,
|
||||||
|
/* binding 3 */ 0};
|
||||||
|
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
|
||||||
|
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
|
||||||
|
computePassEncoder.SetBindGroup(0, bindGroup, offsets.size(), offsets.data());
|
||||||
|
computePassEncoder.EndPass();
|
||||||
|
commandEncoder.Finish();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Offset the second binding to touch the end of the buffer. Should succeed.
|
||||||
|
offsets = {/* binding 0 */ 0,
|
||||||
|
/* binding 2 */ 1 * kMinDynamicBufferOffsetAlignment,
|
||||||
|
/* binding 3 */ 0};
|
||||||
|
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
|
||||||
|
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
|
||||||
|
computePassEncoder.SetBindGroup(0, bindGroup, offsets.size(), offsets.data());
|
||||||
|
computePassEncoder.EndPass();
|
||||||
|
commandEncoder.Finish();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Offset the third binding to touch the end of the buffer. Should succeed.
|
||||||
|
// Will fail if the offset is applied to the second binding since its buffer
|
||||||
|
// is too small.
|
||||||
|
offsets = {/* binding 0 */ 0,
|
||||||
|
/* binding 2 */ 0,
|
||||||
|
/* binding 3 */ 2 * kMinDynamicBufferOffsetAlignment};
|
||||||
|
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
|
||||||
|
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
|
||||||
|
computePassEncoder.SetBindGroup(0, bindGroup, offsets.size(), offsets.data());
|
||||||
|
computePassEncoder.EndPass();
|
||||||
|
commandEncoder.Finish();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Offset each binding to touch the end of their buffer. Should succeed.
|
||||||
|
offsets = {/* binding 0 */ 3 * kMinDynamicBufferOffsetAlignment,
|
||||||
|
/* binding 2 */ 1 * kMinDynamicBufferOffsetAlignment,
|
||||||
|
/* binding 3 */ 2 * kMinDynamicBufferOffsetAlignment};
|
||||||
|
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
|
||||||
|
wgpu::ComputePassEncoder computePassEncoder = commandEncoder.BeginComputePass();
|
||||||
|
computePassEncoder.SetBindGroup(0, bindGroup, offsets.size(), offsets.data());
|
||||||
|
computePassEncoder.EndPass();
|
||||||
|
commandEncoder.Finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test that an error is produced (and no ASSERTs fired) when using an error bindgroup in
|
// Test that an error is produced (and no ASSERTs fired) when using an error bindgroup in
|
||||||
// SetBindGroup
|
// SetBindGroup
|
||||||
TEST_F(SetBindGroupValidationTest, ErrorBindGroup) {
|
TEST_F(SetBindGroupValidationTest, ErrorBindGroup) {
|
||||||
|
|
Loading…
Reference in New Issue