Tint: Add vertex validation for storage buffers and textures
Add validation checks for use of storage buffers and textures with an
access mode other than read in a vertex shader stage.
Relevant spec change: https://github.com/gpuweb/gpuweb/pull/3947
This fixes the currently failing CTS tests:
webgpu:shader,validation,decl,var:shader_stage:stage="vertex";kind="handle_rw"
webgpu:shader,validation,decl,var:shader_stage:stage="vertex";kind="handle_wo"
webgpu:shader,validation,decl,var:shader_stage:stage="vertex";kind="storage_rw"
This change depends on many fixes to existing tests tracked in crbug.com/344846829
Change-Id: Ie51f8efeb5cd7e1c2097413e0bf02d15ce87ec02
Bug: 344846829
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/190781
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Natalie Chouinard <chouinard@google.com>
diff --git a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
index 46594be..923fec7 100644
--- a/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/RenderPipelineValidationTests.cpp
@@ -1203,9 +1203,9 @@
struct Dst {
data : array<u32, 100>
}
- @group(0) @binding(0) var<storage, read_write> dst : Dst;
+ @group(0) @binding(0) var<storage, read> dst : Dst;
@vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
- dst.data[VertexIndex] = 0x1234u;
+ var val = dst.data[VertexIndex];
return vec4f();
})");
@@ -1213,7 +1213,7 @@
descriptor.layout = nullptr;
descriptor.vertex.module = vsModuleWithStorageBuffer;
descriptor.cFragment.module = fsModule;
- ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+ device.CreateRenderPipeline(&descriptor);
}
// Tests that only strip primitive topologies allow an index format
diff --git a/src/tint/lang/wgsl/resolver/validation_test.cc b/src/tint/lang/wgsl/resolver/validation_test.cc
index c0b1f87..c43efd0 100644
--- a/src/tint/lang/wgsl/resolver/validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/validation_test.cc
@@ -133,6 +133,57 @@
9:10 note: called by entry point 'f0')");
}
+TEST_F(ResolverValidationTest, RWStorageBufferUsedInVertexStage) {
+ // @group(0) @binding(0) var<storage, read_write> v : vec4<f32>;
+ // @vertex
+ // fn main() -> @builtin(position) vec4f {
+ // return v;
+ // }
+
+ GlobalVar(Source{{1, 2}}, "v", ty.vec4<f32>(), core::AddressSpace::kStorage,
+ core::Access::kReadWrite, Binding(0_a), Group(0_a));
+ Func("main", tint::Empty, ty.vec4<f32>(),
+ Vector{
+ Return(Expr(Source{{3, 4}}, "v")),
+ },
+ Vector{
+ Stage(ast::PipelineStage::kVertex),
+ },
+ Vector{
+ Builtin(core::BuiltinValue::kPosition),
+ });
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ R"(3:4 error: var with 'storage' address space and 'read_write' access mode cannot be used by vertex pipeline stage
+1:2 note: variable is declared here)");
+}
+
+TEST_F(ResolverValidationTest, RWStorageTextureUsedInVertexStage) {
+ auto type = ty.storage_texture(core::type::TextureDimension::k2d, core::TexelFormat::kR32Uint,
+ core::Access::kReadWrite);
+ GlobalVar(Source{{1, 2}}, "v", type, Binding(0_a), Group(0_a));
+ GlobalVar("res", ty.vec4<f32>(), core::AddressSpace::kPrivate);
+ Func("main", tint::Empty, ty.vec4<f32>(),
+ Vector{
+ Assign(Phony(), Expr(Source{{3, 4}}, "v")),
+ Return(Expr(Source{{3, 4}}, "res")),
+ },
+ Vector{
+ Stage(ast::PipelineStage::kVertex),
+ },
+ Vector{
+ Builtin(core::BuiltinValue::kPosition),
+ });
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ R"(3:4 error: storage texture with 'read_write' access mode cannot be used by vertex pipeline stage
+1:2 note: variable is declared here)");
+}
+
TEST_F(ResolverValidationDeathTest, UnhandledStmt) {
EXPECT_DEATH_IF_SUPPORTED(
{
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index cfd005d..c5def01 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -2164,6 +2164,15 @@
}
bool Validator::PipelineStages(VectorRef<sem::Function*> entry_points) const {
+ auto var_source = [&](const sem::Function* func, const sem::GlobalVariable* var) {
+ for (auto* user : var->Users()) {
+ if (user->Stmt() && func == user->Stmt()->Function()) {
+ return user->Declaration()->source;
+ }
+ }
+ return Source();
+ };
+
auto backtrace = [&](const sem::Function* func, const sem::Function* entry_point) {
if (func != entry_point) {
TraverseCallChain(entry_point, func, [&](const sem::Function* f) {
@@ -2178,30 +2187,40 @@
};
auto check_var_uses = [&](const sem::Function* func, const sem::Function* entry_point) {
- auto err = [&](ast::PipelineStage stage, const sem::GlobalVariable* var) {
- Source source;
- for (auto* user : var->Users()) {
- if (func == user->Stmt()->Function()) {
- source = user->Declaration()->source;
- break;
- }
- }
- AddError(source) << "var with " << style::Enum(var->AddressSpace())
- << " address space cannot be used by " << stage << " pipeline stage";
+ auto var_decl_note = [&](const sem::GlobalVariable* var) {
AddNote(var->Declaration()->source) << "variable is declared here";
backtrace(func, entry_point);
- return false;
};
auto stage = entry_point->Declaration()->PipelineStage();
for (auto* var : func->DirectlyReferencedGlobals()) {
- if (stage != ast::PipelineStage::kCompute &&
- var->AddressSpace() == core::AddressSpace::kWorkgroup) {
- return err(stage, var);
+ Source source = var_source(func, var);
+ if ((stage != ast::PipelineStage::kCompute &&
+ var->AddressSpace() == core::AddressSpace::kWorkgroup) ||
+ (stage != ast::PipelineStage::kFragment &&
+ var->AddressSpace() == core::AddressSpace::kPixelLocal)) {
+ AddError(source) << "var with " << style::Enum(var->AddressSpace())
+ << " address space cannot be used by " << stage
+ << " pipeline stage";
+ var_decl_note(var);
+ return false;
}
- if (stage != ast::PipelineStage::kFragment &&
- var->AddressSpace() == core::AddressSpace::kPixelLocal) {
- return err(stage, var);
+ if (stage == ast::PipelineStage::kVertex &&
+ var->AddressSpace() == core::AddressSpace::kStorage &&
+ var->Access() != core::Access::kRead) {
+ AddError(source) << "var with " << style::Enum(var->AddressSpace())
+ << " address space and " << style::Enum(var->Access())
+ << " access mode cannot be used by " << stage << " pipeline stage";
+ var_decl_note(var);
+ return false;
+ }
+ auto* storage = var->Type()->UnwrapRef()->As<core::type::StorageTexture>();
+ if (stage == ast::PipelineStage::kVertex && storage &&
+ storage->access() != core::Access::kRead) {
+ AddError(source) << "storage texture with " << style::Enum(storage->access())
+ << " access mode cannot be used by " << stage << " pipeline stage";
+ var_decl_note(var);
+ return false;
}
}
return true;