Implement readonly storage buffer - validation at shader side
This patch adds validation code for shader side for readonly storage
buffer. It also adds unit tests for validation.
BUG=dawn:180
Change-Id: Ib5789381d41d77867dd6e6aa1f1ccbc8fa43a382
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/12941
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Yunchao He <yunchao.he@intel.com>
diff --git a/src/dawn_native/ShaderModule.cpp b/src/dawn_native/ShaderModule.cpp
index acb591f..dbe2372 100644
--- a/src/dawn_native/ShaderModule.cpp
+++ b/src/dawn_native/ShaderModule.cpp
@@ -154,12 +154,27 @@
info.used = true;
info.id = resource.id;
info.base_type_id = resource.base_type_id;
- info.type = bindingType;
- if (info.type == wgpu::BindingType::SampledTexture) {
- spirv_cross::SPIRType::BaseType textureComponentType =
- compiler.get_type(compiler.get_type(info.base_type_id).image.type).basetype;
- info.textureComponentType =
- SpirvCrossBaseTypeToFormatType(textureComponentType);
+ switch (bindingType) {
+ case wgpu::BindingType::SampledTexture: {
+ spirv_cross::SPIRType::BaseType textureComponentType =
+ compiler.get_type(compiler.get_type(info.base_type_id).image.type)
+ .basetype;
+ info.textureComponentType =
+ SpirvCrossBaseTypeToFormatType(textureComponentType);
+ info.type = bindingType;
+ } break;
+ case wgpu::BindingType::StorageBuffer: {
+ // Differentiate between readonly storage bindings and writable ones based
+ // on the NonWritable decoration
+ spirv_cross::Bitset flags = compiler.get_buffer_block_flags(resource.id);
+ if (flags.get(spv::DecorationNonWritable)) {
+ info.type = wgpu::BindingType::ReadonlyStorageBuffer;
+ } else {
+ info.type = wgpu::BindingType::StorageBuffer;
+ }
+ } break;
+ default:
+ info.type = bindingType;
}
}
};
@@ -285,7 +300,16 @@
}
if (layoutBindingType != moduleInfo.type) {
- return false;
+ // Binding mismatch between shader and bind group is invalid. For example, a
+ // writable binding in the shader with a readonly storage buffer in the bind group
+ // layout is invalid. However, a readonly binding in the shader with a writable
+ // storage buffer in the bind group layout is valid.
+ bool validBindingConversion =
+ layoutBindingType == wgpu::BindingType::StorageBuffer &&
+ moduleInfo.type == wgpu::BindingType::ReadonlyStorageBuffer;
+ if (!validBindingConversion) {
+ return false;
+ }
}
if ((layoutInfo.visibilities[i] & StageBit(mExecutionModel)) == 0) {
diff --git a/src/tests/unittests/validation/BindGroupValidationTests.cpp b/src/tests/unittests/validation/BindGroupValidationTests.cpp
index 32b342d..1e7ce5e 100644
--- a/src/tests/unittests/validation/BindGroupValidationTests.cpp
+++ b/src/tests/unittests/validation/BindGroupValidationTests.cpp
@@ -671,7 +671,7 @@
}
}
-constexpr uint64_t kBufferSize = 2 * kMinDynamicBufferOffsetAlignment + 8;
+constexpr uint64_t kBufferSize = 3 * kMinDynamicBufferOffsetAlignment + 8;
constexpr uint32_t kBindingSize = 9;
class SetBindGroupValidationTest : public ValidationTest {
@@ -681,7 +681,9 @@
device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
wgpu::BindingType::UniformBuffer, true},
{1, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
- wgpu::BindingType::StorageBuffer, true}});
+ wgpu::BindingType::StorageBuffer, true},
+ {2, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
+ wgpu::BindingType::ReadonlyStorageBuffer, true}});
}
wgpu::Buffer CreateBuffer(uint64_t bufferSize, wgpu::BufferUsage usage) {
@@ -710,6 +712,9 @@
layout(std140, set = 0, binding = 1) buffer SBuffer {
vec2 value2;
} sBuffer;
+ layout(std140, set = 0, binding = 2) readonly buffer RBuffer {
+ vec2 value3;
+ } rBuffer;
layout(location = 0) out vec4 fragColor;
void main() {
})");
@@ -737,9 +742,11 @@
layout(std140, set = 0, binding = 1) buffer SBuffer {
float value2;
} dst;
-
- void main() {
- })");
+ layout(std140, set = 0, binding = 2) readonly buffer RBuffer {
+ readonly float value3;
+ } rdst;
+ void main() {
+ })");
wgpu::PipelineLayout pipelineLayout =
utils::MakeBasicPipelineLayout(device, &mBindGroupLayout);
@@ -797,15 +804,17 @@
// Set up the bind group.
wgpu::Buffer uniformBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Uniform);
wgpu::Buffer storageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
- wgpu::BindGroup bindGroup = utils::MakeBindGroup(
- device, mBindGroupLayout,
- {{0, uniformBuffer, 0, kBindingSize}, {1, storageBuffer, 0, kBindingSize}});
+ wgpu::Buffer readonlyStorageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
+ wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, mBindGroupLayout,
+ {{0, uniformBuffer, 0, kBindingSize},
+ {1, storageBuffer, 0, kBindingSize},
+ {2, readonlyStorageBuffer, 0, kBindingSize}});
- std::array<uint32_t, 2> offsets = {256, 0};
+ std::array<uint32_t, 3> offsets = {512, 256, 0};
- TestRenderPassBindGroup(bindGroup, offsets.data(), 2, true);
+ TestRenderPassBindGroup(bindGroup, offsets.data(), 3, true);
- TestComputePassBindGroup(bindGroup, offsets.data(), 2, true);
+ TestComputePassBindGroup(bindGroup, offsets.data(), 3, true);
}
// Test cases that test dynamic offsets count mismatch with bind group layout.
@@ -813,16 +822,22 @@
// Set up bind group.
wgpu::Buffer uniformBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Uniform);
wgpu::Buffer storageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
- wgpu::BindGroup bindGroup = utils::MakeBindGroup(
- device, mBindGroupLayout,
- {{0, uniformBuffer, 0, kBindingSize}, {1, storageBuffer, 0, kBindingSize}});
+ wgpu::Buffer readonlyStorageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
+ wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, mBindGroupLayout,
+ {{0, uniformBuffer, 0, kBindingSize},
+ {1, storageBuffer, 0, kBindingSize},
+ {2, readonlyStorageBuffer, 0, kBindingSize}});
// Number of offsets mismatch.
- std::array<uint32_t, 1> mismatchOffsets = {0};
+ std::array<uint32_t, 4> mismatchOffsets = {768, 512, 256, 0};
TestRenderPassBindGroup(bindGroup, mismatchOffsets.data(), 1, false);
+ TestRenderPassBindGroup(bindGroup, mismatchOffsets.data(), 2, false);
+ TestRenderPassBindGroup(bindGroup, mismatchOffsets.data(), 4, false);
TestComputePassBindGroup(bindGroup, mismatchOffsets.data(), 1, false);
+ TestComputePassBindGroup(bindGroup, mismatchOffsets.data(), 2, false);
+ TestComputePassBindGroup(bindGroup, mismatchOffsets.data(), 4, false);
}
// Test cases that test dynamic offsets not aligned
@@ -830,16 +845,18 @@
// Set up bind group.
wgpu::Buffer uniformBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Uniform);
wgpu::Buffer storageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
- wgpu::BindGroup bindGroup = utils::MakeBindGroup(
- device, mBindGroupLayout,
- {{0, uniformBuffer, 0, kBindingSize}, {1, storageBuffer, 0, kBindingSize}});
+ wgpu::Buffer readonlyStorageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
+ wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, mBindGroupLayout,
+ {{0, uniformBuffer, 0, kBindingSize},
+ {1, storageBuffer, 0, kBindingSize},
+ {2, readonlyStorageBuffer, 0, kBindingSize}});
// Dynamic offsets are not aligned.
- std::array<uint32_t, 2> notAlignedOffsets = {1, 2};
+ std::array<uint32_t, 3> notAlignedOffsets = {512, 128, 0};
- TestRenderPassBindGroup(bindGroup, notAlignedOffsets.data(), 2, false);
+ TestRenderPassBindGroup(bindGroup, notAlignedOffsets.data(), 3, false);
- TestComputePassBindGroup(bindGroup, notAlignedOffsets.data(), 2, false);
+ TestComputePassBindGroup(bindGroup, notAlignedOffsets.data(), 3, false);
}
// Test cases that test dynamic uniform buffer out of bound situation.
@@ -847,16 +864,18 @@
// Set up bind group.
wgpu::Buffer uniformBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Uniform);
wgpu::Buffer storageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
- wgpu::BindGroup bindGroup = utils::MakeBindGroup(
- device, mBindGroupLayout,
- {{0, uniformBuffer, 0, kBindingSize}, {1, storageBuffer, 0, kBindingSize}});
+ wgpu::Buffer readonlyStorageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
+ wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, mBindGroupLayout,
+ {{0, uniformBuffer, 0, kBindingSize},
+ {1, storageBuffer, 0, kBindingSize},
+ {2, readonlyStorageBuffer, 0, kBindingSize}});
// Dynamic offset + offset is larger than buffer size.
- std::array<uint32_t, 2> overFlowOffsets = {1024, 0};
+ std::array<uint32_t, 3> overFlowOffsets = {1024, 256, 0};
- TestRenderPassBindGroup(bindGroup, overFlowOffsets.data(), 2, false);
+ TestRenderPassBindGroup(bindGroup, overFlowOffsets.data(), 3, false);
- TestComputePassBindGroup(bindGroup, overFlowOffsets.data(), 2, false);
+ TestComputePassBindGroup(bindGroup, overFlowOffsets.data(), 3, false);
}
// Test cases that test dynamic storage buffer out of bound situation.
@@ -864,16 +883,18 @@
// Set up bind group.
wgpu::Buffer uniformBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Uniform);
wgpu::Buffer storageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
- wgpu::BindGroup bindGroup = utils::MakeBindGroup(
- device, mBindGroupLayout,
- {{0, uniformBuffer, 0, kBindingSize}, {1, storageBuffer, 0, kBindingSize}});
+ wgpu::Buffer readonlyStorageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
+ wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, mBindGroupLayout,
+ {{0, uniformBuffer, 0, kBindingSize},
+ {1, storageBuffer, 0, kBindingSize},
+ {2, readonlyStorageBuffer, 0, kBindingSize}});
// Dynamic offset + offset is larger than buffer size.
- std::array<uint32_t, 2> overFlowOffsets = {0, 1024};
+ std::array<uint32_t, 3> overFlowOffsets = {0, 256, 1024};
- TestRenderPassBindGroup(bindGroup, overFlowOffsets.data(), 2, false);
+ TestRenderPassBindGroup(bindGroup, overFlowOffsets.data(), 3, false);
- TestComputePassBindGroup(bindGroup, overFlowOffsets.data(), 2, false);
+ TestComputePassBindGroup(bindGroup, overFlowOffsets.data(), 3, false);
}
// Test cases that test dynamic uniform buffer out of bound situation because of binding size.
@@ -881,33 +902,37 @@
// Set up bind group, but binding size is larger than
wgpu::Buffer uniformBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Uniform);
wgpu::Buffer storageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
- wgpu::BindGroup bindGroup = utils::MakeBindGroup(
- device, mBindGroupLayout,
- {{0, uniformBuffer, 0, kBindingSize}, {1, storageBuffer, 0, kBindingSize}});
+ wgpu::Buffer readonlyStorageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
+ wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, mBindGroupLayout,
+ {{0, uniformBuffer, 0, kBindingSize},
+ {1, storageBuffer, 0, kBindingSize},
+ {2, readonlyStorageBuffer, 0, kBindingSize}});
// Dynamic offset + offset isn't larger than buffer size.
// But with binding size, it will trigger OOB error.
- std::array<uint32_t, 2> offsets = {512, 0};
+ std::array<uint32_t, 3> offsets = {768, 256, 0};
- TestRenderPassBindGroup(bindGroup, offsets.data(), 2, false);
+ TestRenderPassBindGroup(bindGroup, offsets.data(), 3, false);
- TestComputePassBindGroup(bindGroup, offsets.data(), 2, false);
+ TestComputePassBindGroup(bindGroup, offsets.data(), 3, false);
}
// Test cases that test dynamic storage buffer out of bound situation because of binding size.
TEST_F(SetBindGroupValidationTest, BindingSizeOutOfBoundDynamicStorageBuffer) {
wgpu::Buffer uniformBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Uniform);
wgpu::Buffer storageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
- wgpu::BindGroup bindGroup = utils::MakeBindGroup(
- device, mBindGroupLayout,
- {{0, uniformBuffer, 0, kBindingSize}, {1, storageBuffer, 0, kBindingSize}});
+ wgpu::Buffer readonlyStorageBuffer = CreateBuffer(kBufferSize, wgpu::BufferUsage::Storage);
+ wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, mBindGroupLayout,
+ {{0, uniformBuffer, 0, kBindingSize},
+ {1, storageBuffer, 0, kBindingSize},
+ {2, readonlyStorageBuffer, 0, kBindingSize}});
// Dynamic offset + offset isn't larger than buffer size.
// But with binding size, it will trigger OOB error.
- std::array<uint32_t, 2> offsets = {0, 512};
+ std::array<uint32_t, 3> offsets = {0, 256, 768};
- TestRenderPassBindGroup(bindGroup, offsets.data(), 2, false);
+ TestRenderPassBindGroup(bindGroup, offsets.data(), 3, false);
- TestComputePassBindGroup(bindGroup, offsets.data(), 2, false);
+ TestComputePassBindGroup(bindGroup, offsets.data(), 3, false);
}
// Test that an error is produced (and no ASSERTs fired) when using an error bindgroup in
@@ -1115,3 +1140,103 @@
renderPassEncoder.EndPass();
commandEncoder.Finish();
}
+
+class BindGroupLayoutCompatibilityTest : public ValidationTest {
+ public:
+ wgpu::Buffer CreateBuffer(uint64_t bufferSize, wgpu::BufferUsage usage) {
+ wgpu::BufferDescriptor bufferDescriptor;
+ bufferDescriptor.size = bufferSize;
+ bufferDescriptor.usage = usage;
+
+ return device.CreateBuffer(&bufferDescriptor);
+ }
+
+ wgpu::RenderPipeline CreateRenderPipeline(wgpu::BindGroupLayout* bindGroupLayout) {
+ wgpu::ShaderModule vsModule =
+ utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(
+ #version 450
+ void main() {
+ })");
+
+ wgpu::ShaderModule fsModule =
+ utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(
+ #version 450
+ layout(std140, set = 0, binding = 0) buffer SBuffer {
+ vec2 value2;
+ } sBuffer;
+ layout(std140, set = 0, binding = 1) readonly buffer RBuffer {
+ vec2 value3;
+ } rBuffer;
+ layout(location = 0) out vec4 fragColor;
+ void main() {
+ })");
+
+ utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
+ pipelineDescriptor.vertexStage.module = vsModule;
+ pipelineDescriptor.cFragmentStage.module = fsModule;
+ wgpu::PipelineLayout pipelineLayout =
+ utils::MakeBasicPipelineLayout(device, bindGroupLayout);
+ pipelineDescriptor.layout = pipelineLayout;
+ return device.CreateRenderPipeline(&pipelineDescriptor);
+ }
+
+ wgpu::ComputePipeline CreateComputePipeline(wgpu::BindGroupLayout* bindGroupLayout) {
+ wgpu::ShaderModule csModule =
+ utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, R"(
+ #version 450
+ const uint kTileSize = 4;
+ const uint kInstances = 11;
+
+ layout(local_size_x = kTileSize, local_size_y = kTileSize, local_size_z = 1) in;
+ layout(std140, set = 0, binding = 0) buffer SBuffer {
+ float value2;
+ } dst;
+ layout(std140, set = 0, binding = 1) readonly buffer RBuffer {
+ readonly float value3;
+ } rdst;
+ void main() {
+ })");
+
+ wgpu::PipelineLayout pipelineLayout =
+ utils::MakeBasicPipelineLayout(device, bindGroupLayout);
+
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.layout = pipelineLayout;
+ csDesc.computeStage.module = csModule;
+ csDesc.computeStage.entryPoint = "main";
+
+ return device.CreateComputePipeline(&csDesc);
+ }
+};
+
+// Test cases that test bind group layout mismatch with shader. The second item in bind group layout
+// is a writable storage buffer, but the second item in shader is a readonly storage buffer. It is
+// valid.
+TEST_F(BindGroupLayoutCompatibilityTest, RWStorageInBGLWithROStorageInShader) {
+ // Set up the bind group layout.
+ wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
+ wgpu::BindingType::StorageBuffer, true},
+ {1, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
+ wgpu::BindingType::StorageBuffer, true}});
+
+ CreateRenderPipeline(&bindGroupLayout);
+
+ CreateComputePipeline(&bindGroupLayout);
+}
+
+// Test cases that test bind group layout mismatch with shader. The first item in bind group layout
+// is a readonly storage buffer, but the first item in shader is a writable storage buffer. It is
+// invalid.
+TEST_F(BindGroupLayoutCompatibilityTest, ROStorageInBGLWithRWStorageInShader) {
+ // Set up the bind group layout.
+ wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
+ wgpu::BindingType::ReadonlyStorageBuffer, true},
+ {1, wgpu::ShaderStage::Compute | wgpu::ShaderStage::Fragment,
+ wgpu::BindingType::ReadonlyStorageBuffer, true}});
+
+ ASSERT_DEVICE_ERROR(CreateRenderPipeline(&bindGroupLayout));
+
+ ASSERT_DEVICE_ERROR(CreateComputePipeline(&bindGroupLayout));
+}