spirv-reader: support sample_id builtin variable
TODO: support sample_id declared with signed integer store
type, and then having the pointer passed to a helper function.
Bug: tint:471
Change-Id: Iac303ff6118b2d2d518e5070a8d589dcd3616f39
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/40020
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Auto-Submit: David Neto <dneto@google.com>
diff --git a/src/reader/spirv/enum_converter.cc b/src/reader/spirv/enum_converter.cc
index f53c4a6..0bf795b 100644
--- a/src/reader/spirv/enum_converter.cc
+++ b/src/reader/spirv/enum_converter.cc
@@ -86,6 +86,8 @@
return ast::Builtin::kLocalInvocationIndex;
case SpvBuiltInGlobalInvocationId:
return ast::Builtin::kGlobalInvocationId;
+ case SpvBuiltInSampleId:
+ return ast::Builtin::kSampleId;
default:
break;
}
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 1bb6724..ec485bf 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -991,7 +991,7 @@
return false;
}
- if (!RegisterIgnoredBuiltInVariables()) {
+ if (!RegisterSpecialBuiltInVariables()) {
return false;
}
if (!RegisterLocallyDefinedValues()) {
@@ -2023,6 +2023,10 @@
Fail() << "unhandled use of a pointer to the PointSize builtin, with ID: "
<< id;
return {};
+ case SkipReason::kSampleIdBuiltinPointer:
+ Fail() << "unhandled use of a pointer to the SampleId builtin, with ID: "
+ << id;
+ return {};
}
if (identifier_values_.count(id) || parser_impl_.IsScalarSpecConstant(id)) {
auto name = namer_.Name(id);
@@ -2999,9 +3003,24 @@
// Memory accesses must be issued in SPIR-V program order.
// So represent a load by a new const definition.
const auto ptr_id = inst.GetSingleWordInOperand(0);
- if (GetSkipReason(ptr_id) == SkipReason::kPointSizeBuiltinPointer) {
- GetDefInfo(inst.result_id())->skip = SkipReason::kPointSizeBuiltinValue;
- return true;
+ switch (GetSkipReason(ptr_id)) {
+ case SkipReason::kPointSizeBuiltinPointer:
+ GetDefInfo(inst.result_id())->skip =
+ SkipReason::kPointSizeBuiltinValue;
+ return true;
+ case SkipReason::kSampleIdBuiltinPointer: {
+ // The SPIR-V variable is i32, but WGSL requires u32.
+ auto var_id = parser_impl_.IdForSpecialBuiltIn(SpvBuiltInSampleId);
+ auto name = namer_.Name(var_id);
+ ast::Expression* id_expr = create<ast::IdentifierExpression>(
+ Source{}, builder_.Symbols().Register(name));
+ auto expr = TypedExpression{
+ i32_, create<ast::TypeConstructorExpression>(
+ Source{}, i32_, ast::ExpressionList{id_expr})};
+ return EmitConstDefinition(inst, expr);
+ }
+ default:
+ break;
}
auto expr = MakeExpression(ptr_id);
// The load result type is the pointee type of its operand.
@@ -3626,11 +3645,11 @@
create<ast::TypeConstructorExpression>(source, result_type, values)};
}
-bool FunctionEmitter::RegisterIgnoredBuiltInVariables() {
+bool FunctionEmitter::RegisterSpecialBuiltInVariables() {
size_t index = def_info_.size();
- for (auto& ignored_var : parser_impl_.ignored_builtins()) {
- const auto id = ignored_var.first;
- const auto builtin = ignored_var.second;
+ for (auto& special_var : parser_impl_.special_builtins()) {
+ const auto id = special_var.first;
+ const auto builtin = special_var.second;
def_info_[id] =
std::make_unique<DefInfo>(*(def_use_mgr_->GetDef(id)), 0, index);
++index;
@@ -3639,8 +3658,11 @@
case SpvBuiltInPointSize:
def->skip = SkipReason::kPointSizeBuiltinPointer;
break;
+ case SpvBuiltInSampleId:
+ def->skip = SkipReason::kSampleIdBuiltinPointer;
+ break;
default:
- return Fail() << "unrecognized ignored builtin: " << int(builtin);
+ return Fail() << "unrecognized special builtin: " << int(builtin);
}
}
return true;
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index fe6ceaf..6f1ca66 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -223,6 +223,10 @@
/// PointSize builtin. Use 1.0f instead, because that's the only value
/// supported by WebGPU.
kPointSizeBuiltinValue,
+
+ /// `kSampleIdBuiltinPointer`: the value is a pointer to the SampleId builtin
+ /// variable. Don't generate its address.
+ kSampleIdBuiltinPointer,
};
/// Bookkeeping info for a SPIR-V ID defined in the function, or some
@@ -328,6 +332,9 @@
case SkipReason::kPointSizeBuiltinValue:
o << " skip:pointsize_value";
break;
+ case SkipReason::kSampleIdBuiltinPointer:
+ o << " skip:sampleid_pointer";
+ break;
}
o << "}";
return o;
@@ -467,10 +474,11 @@
bool FindIfSelectionInternalHeaders();
/// Creates a DefInfo record for each module-scope builtin variable
- /// that should be ignored.
+ /// that should be handled specially. Either it's ignored, or its store
+ /// type is converted on load.
/// Populates the `def_info_` mapping for such IDs.
/// @returns false on failure
- bool RegisterIgnoredBuiltInVariables();
+ bool RegisterSpecialBuiltInVariables();
/// Creates a DefInfo record for each locally defined SPIR-V ID.
/// Populates the `def_info_` mapping with basic results for such IDs.
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 8a14e8e..2d0b3a2 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1265,8 +1265,18 @@
const auto spv_builtin = static_cast<SpvBuiltIn>(deco[1]);
switch (spv_builtin) {
case SpvBuiltInPointSize:
- ignored_builtins_[id] = spv_builtin;
+ special_builtins_[id] = spv_builtin;
return nullptr;
+ case SpvBuiltInSampleId:
+ // The SPIR-V variable might is likely to be signed (because GLSL
+ // requires signed), but WGSL requires unsigned. Handle specially
+ // so we always perform the conversion at load and store.
+ if (auto* forced_type = unsigned_type_for_[type]) {
+ // Requires conversion and special handling in code generation.
+ special_builtins_[id] = spv_builtin;
+ type = forced_type;
+ }
+ break;
default:
break;
}
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 9827e0e..3b14f77 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -477,9 +477,22 @@
/// A map of SPIR-V identifiers to builtins
using BuiltInsMap = std::unordered_map<uint32_t, SpvBuiltIn>;
- /// @returns a map of builtins that should be ignored as they do not exist in
- /// WGSL.
- const BuiltInsMap& ignored_builtins() const { return ignored_builtins_; }
+ /// @returns a map of builtins that should be handled specially by code
+ /// generation. Either the builtin does not exist in WGSL, or a type
+ /// conversion must be implemented on load and store.
+ const BuiltInsMap& special_builtins() const { return special_builtins_; }
+
+ /// @param builtin the SPIR-V builtin variable kind
+ /// @returns the SPIR-V ID for the variable defining the given builtin, or 0
+ uint32_t IdForSpecialBuiltIn(SpvBuiltIn builtin) const {
+ // Do a linear search.
+ for (const auto& entry : special_builtins_) {
+ if (entry.second == builtin) {
+ return entry.first;
+ }
+ }
+ return 0;
+ }
private:
/// Converts a specific SPIR-V type to a Tint type. Integer case
@@ -634,8 +647,10 @@
handle_type_;
/// Maps the SPIR-V ID of a module-scope builtin variable that should be
- /// ignored, to its builtin kind.
- BuiltInsMap ignored_builtins_;
+ /// ignored or type-converted, to its builtin kind.
+ /// See also BuiltInPositionInfo which is a separate mechanism for a more
+ /// complex case of replacing an entire structure.
+ BuiltInsMap special_builtins_;
};
} // namespace spirv
diff --git a/src/reader/spirv/parser_impl_module_var_test.cc b/src/reader/spirv/parser_impl_module_var_test.cc
index bf5a42a..2fd4ea2 100644
--- a/src/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/reader/spirv/parser_impl_module_var_test.cc
@@ -2021,6 +2021,388 @@
<< ToString(program, fe.ast_body());
}
+// Returns the start of a shader for testing SampleId,
+// parameterized by store type of %int or %uint
+std::string SampleIdPreamble(std::string store_type) {
+ return R"(
+ OpCapability Shader
+ OpCapability SampleRateShading
+ OpMemoryModel Logical Simple
+ OpEntryPoint Fragment %main "main" %1
+ OpExecutionMode %main OriginUpperLeft
+ OpDecorate %1 BuiltIn SampleId
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %uint = OpTypeInt 32 0
+ %int = OpTypeInt 32 1
+ %ptr_ty = OpTypePointer Input )" +
+ store_type + R"(
+ %1 = OpVariable %ptr_ty Input
+)";
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_Load_Direct) {
+ const std::string assembly = SampleIdPreamble("%int") + R"(
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %2 = OpLoad %int %1
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->program().to_str();
+ // Correct declaration
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Variable{
+ Decorations{
+ BuiltinDecoration{sample_id}
+ }
+ x_1
+ in
+ __u32
+ })"));
+
+ // Correct body
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ VariableDeclStatement{
+ VariableConst{
+ x_2
+ none
+ __i32
+ {
+ TypeConstructor[not set]{
+ __i32
+ Identifier[not set]{x_1}
+ }
+ }
+ }
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_Load_CopyObject) {
+ const std::string assembly = SampleIdPreamble("%int") + R"(
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %copy_ptr = OpCopyObject %ptr_ty %1
+ %2 = OpLoad %int %copy_ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->program().to_str();
+ // Correct declaration
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Variable{
+ Decorations{
+ BuiltinDecoration{sample_id}
+ }
+ x_1
+ in
+ __u32
+ })"));
+
+ // Correct body
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ VariableDeclStatement{
+ VariableConst{
+ x_2
+ none
+ __i32
+ {
+ TypeConstructor[not set]{
+ __i32
+ Identifier[not set]{x_1}
+ }
+ }
+ }
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_Load_AccessChain) {
+ const std::string assembly = SampleIdPreamble("%int") + R"(
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %copy_ptr = OpAccessChain %ptr_ty %1
+ %2 = OpLoad %int %copy_ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->program().to_str();
+ // Correct declaration
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Variable{
+ Decorations{
+ BuiltinDecoration{sample_id}
+ }
+ x_1
+ in
+ __u32
+ })"));
+
+ // Correct body
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ VariableDeclStatement{
+ VariableConst{
+ x_2
+ none
+ __i32
+ {
+ TypeConstructor[not set]{
+ __i32
+ Identifier[not set]{x_1}
+ }
+ }
+ }
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_I32_FunctParam) {
+ const std::string assembly = SampleIdPreamble("%int") + R"(
+ %helper_ty = OpTypeFunction %int %ptr_ty
+ %helper = OpFunction %int None %helper_ty
+ %param = OpFunctionParameter %ptr_ty
+ %helper_entry = OpLabel
+ %3 = OpLoad %int %param
+ OpReturnValue %3
+ OpFunctionEnd
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %result = OpFunctionCall %int %helper %1
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto p = parser(test::Assemble(assembly));
+ // TODO(dneto): We can handle this if we make a shadow variable and mutate
+ // the parameter type.
+ ASSERT_FALSE(p->BuildAndParseInternalModule());
+ EXPECT_THAT(
+ p->error(),
+ HasSubstr(
+ "unhandled use of a pointer to the SampleId builtin, with ID: 1"));
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_Load_Direct) {
+ const std::string assembly = SampleIdPreamble("%uint") + R"(
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %2 = OpLoad %uint %1
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->program().to_str();
+ // Correct declaration
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Variable{
+ Decorations{
+ BuiltinDecoration{sample_id}
+ }
+ x_1
+ in
+ __u32
+ })"));
+
+ // Correct body
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ VariableDeclStatement{
+ VariableConst{
+ x_2
+ none
+ __u32
+ {
+ Identifier[not set]{x_1}
+ }
+ }
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_Load_CopyObject) {
+ const std::string assembly = SampleIdPreamble("%uint") + R"(
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %copy_ptr = OpCopyObject %ptr_ty %1
+ %2 = OpLoad %uint %copy_ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->program().to_str();
+ // Correct declaration
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Variable{
+ Decorations{
+ BuiltinDecoration{sample_id}
+ }
+ x_1
+ in
+ __u32
+ })"));
+
+ // Correct body
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ VariableDeclStatement{
+ VariableConst{
+ x_11
+ none
+ __ptr_in__u32
+ {
+ Identifier[not set]{x_1}
+ }
+ }
+ }
+ VariableDeclStatement{
+ VariableConst{
+ x_2
+ none
+ __u32
+ {
+ Identifier[not set]{x_11}
+ }
+ }
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_Load_AccessChain) {
+ const std::string assembly = SampleIdPreamble("%uint") + R"(
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %copy_ptr = OpAccessChain %ptr_ty %1
+ %2 = OpLoad %uint %copy_ptr
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto p = parser(test::Assemble(assembly));
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->program().to_str();
+ // Correct declaration
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Variable{
+ Decorations{
+ BuiltinDecoration{sample_id}
+ }
+ x_1
+ in
+ __u32
+ })"));
+
+ // Correct body
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ VariableDeclStatement{
+ VariableConst{
+ x_2
+ none
+ __u32
+ {
+ Identifier[not set]{x_1}
+ }
+ }
+ })"))
+ << module_str;
+}
+
+TEST_F(SpvModuleScopeVarParserTest, SampleId_U32_FunctParam) {
+ const std::string assembly = SampleIdPreamble("%uint") + R"(
+ %helper_ty = OpTypeFunction %uint %ptr_ty
+ %helper = OpFunction %uint None %helper_ty
+ %param = OpFunctionParameter %ptr_ty
+ %helper_entry = OpLabel
+ %3 = OpLoad %uint %param
+ OpReturnValue %3
+ OpFunctionEnd
+
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ %result = OpFunctionCall %uint %helper %1
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto p = parser(test::Assemble(assembly));
+ // TODO(dneto): We can handle this if we make a shadow variable and mutate
+ // the parameter type.
+ ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+ EXPECT_TRUE(p->error().empty());
+ const auto module_str = p->program().to_str();
+ // Correct declaration
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Variable{
+ Decorations{
+ BuiltinDecoration{sample_id}
+ }
+ x_1
+ in
+ __u32
+ })"));
+
+ // Correct bodies
+ EXPECT_THAT(module_str, HasSubstr(R"(
+ Function x_11 -> __u32
+ (
+ VariableConst{
+ x_12
+ none
+ __ptr_in__u32
+ }
+ )
+ {
+ VariableDeclStatement{
+ VariableConst{
+ x_3
+ none
+ __u32
+ {
+ Identifier[not set]{x_12}
+ }
+ }
+ }
+ Return{
+ {
+ Identifier[not set]{x_3}
+ }
+ }
+ }
+ Function main -> __void
+ StageDecoration{fragment}
+ ()
+ {
+ VariableDeclStatement{
+ VariableConst{
+ x_15
+ none
+ __u32
+ {
+ Call[not set]{
+ Identifier[not set]{x_11}
+ (
+ Identifier[not set]{x_1}
+ )
+ }
+ }
+ }
+ }
+ Return{}
+ }
+})")) << module_str;
+}
+
} // namespace
} // namespace spirv
} // namespace reader