spirv-reader: Infer a handle type when needed
Fixed: tint:374 Change-Id: I4785a48d456a6b5ba1fa8c9950b4c72d00853fc9 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33981 Commit-Queue: David Neto <dneto@google.com> Reviewed-by: Ben Clayton <bclayton@google.com> Auto-Submit: David Neto <dneto@google.com>
This commit is contained in:
parent
83b32455c2
commit
ddaf59016b
|
@ -1604,63 +1604,128 @@ ParserImpl::GetMemoryObjectDeclarationForHandle(uint32_t id,
|
|||
|
||||
ast::type::Type* ParserImpl::GetTypeForHandleVar(
|
||||
const spvtools::opt::Instruction& var) {
|
||||
// This can be a sampler or image.
|
||||
// Determine it from the usage inferred for the variable.
|
||||
const Usage& usage = handle_usage_[&var];
|
||||
// The WGSL handle type is determined by looking at information from
|
||||
// several sources:
|
||||
// - the usage of the handle by image access instructions
|
||||
// - the SPIR-V type declaration
|
||||
// Each source does not have enough information to completely determine
|
||||
// the result.
|
||||
|
||||
// Messages are phrased in terms of images and samplers because those
|
||||
// are the only SPIR-V handles supported by WGSL.
|
||||
|
||||
// Get the SPIR-V handle type.
|
||||
const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
|
||||
if (!ptr_type) {
|
||||
Fail() << "Invalid type for variable " << var.PrettyPrint();
|
||||
return nullptr;
|
||||
}
|
||||
const auto* raw_handle_type =
|
||||
def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
|
||||
if (!raw_handle_type) {
|
||||
Fail() << "Invalid pointer type for variable " << var.PrettyPrint();
|
||||
return nullptr;
|
||||
}
|
||||
switch (raw_handle_type->opcode()) {
|
||||
case SpvOpTypeSampler:
|
||||
case SpvOpTypeImage:
|
||||
// The expected cases.
|
||||
break;
|
||||
case SpvOpTypeArray:
|
||||
case SpvOpTypeRuntimeArray:
|
||||
Fail()
|
||||
<< "arrays of textures or samplers are not supported in WGSL; can't "
|
||||
"translate variable "
|
||||
<< var.PrettyPrint();
|
||||
return nullptr;
|
||||
default:
|
||||
Fail() << "invalid type for image or sampler variable "
|
||||
<< var.PrettyPrint();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The variable could be a sampler or image.
|
||||
// Where possible, determine which one it is from the usage inferred
|
||||
// for the variable.
|
||||
Usage usage = handle_usage_[&var];
|
||||
if (!usage.IsValid()) {
|
||||
Fail() << "Invalid sampler or texture usage for variable "
|
||||
<< var.PrettyPrint() << "\n"
|
||||
<< usage;
|
||||
return nullptr;
|
||||
}
|
||||
// Infer a handle type, if usage didn't already tell us.
|
||||
if (!usage.IsComplete()) {
|
||||
// TODO(dneto): In SPIR-V you could statically reference a texture or
|
||||
// sampler without using it in a way that gives us a clue on how to declare
|
||||
// it. In that case look at the underlying OpTypeSampler or OpTypeImage; in
|
||||
// the OpTypeImage see if it has a format. Sampled images alway have
|
||||
// Unknown format. For WGSL, storage images always have a format. And then
|
||||
// check for NonWritable or NonReadabl
|
||||
Fail() << "Unsupported: Incomplete usage on samper or texture usage for "
|
||||
"variable "
|
||||
<< var.PrettyPrint() << "\n"
|
||||
<< usage;
|
||||
return nullptr;
|
||||
// In SPIR-V you could statically reference a texture or sampler without
|
||||
// using it in a way that gives us a clue on how to declare it. Look inside
|
||||
// the store type to infer a usage.
|
||||
if (raw_handle_type->opcode() == SpvOpTypeSampler) {
|
||||
usage.AddSampler();
|
||||
} else {
|
||||
// It's a texture.
|
||||
if (raw_handle_type->NumInOperands() != 7) {
|
||||
Fail() << "invalid SPIR-V image type: expected 7 operands: "
|
||||
<< raw_handle_type->PrettyPrint();
|
||||
return nullptr;
|
||||
}
|
||||
const auto sampled_param = raw_handle_type->GetSingleWordInOperand(5);
|
||||
const auto format_param = raw_handle_type->GetSingleWordInOperand(6);
|
||||
// Only storage images have a format.
|
||||
if ((format_param != SpvImageFormatUnknown) ||
|
||||
sampled_param == 2 /* without sampler */) {
|
||||
// Get NonWritable and NonReadable attributes of the variable.
|
||||
bool is_nonwritable = false;
|
||||
bool is_nonreadable = false;
|
||||
for (const auto& deco : GetDecorationsFor(var.result_id())) {
|
||||
if (deco.size() != 1) {
|
||||
continue;
|
||||
}
|
||||
if (deco[0] == SpvDecorationNonWritable) {
|
||||
is_nonwritable = true;
|
||||
}
|
||||
if (deco[0] == SpvDecorationNonReadable) {
|
||||
is_nonreadable = true;
|
||||
}
|
||||
}
|
||||
if (is_nonwritable && is_nonreadable) {
|
||||
Fail() << "storage image variable is both NonWritable and NonReadable"
|
||||
<< var.PrettyPrint();
|
||||
}
|
||||
if (!is_nonwritable && !is_nonreadable) {
|
||||
Fail()
|
||||
<< "storage image variable is neither NonWritable nor NonReadable"
|
||||
<< var.PrettyPrint();
|
||||
}
|
||||
// Let's make it one of the storage textures.
|
||||
if (is_nonwritable) {
|
||||
usage.AddStorageReadTexture();
|
||||
} else {
|
||||
usage.AddStorageWriteTexture();
|
||||
}
|
||||
} else {
|
||||
usage.AddSampledTexture();
|
||||
}
|
||||
}
|
||||
if (!usage.IsComplete()) {
|
||||
Fail()
|
||||
<< "internal error: should have inferred a complete handle type. got "
|
||||
<< usage.to_str();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the Tint handle type.
|
||||
ast::type::Type* ast_store_type = nullptr;
|
||||
if (usage.IsSampler()) {
|
||||
ast_store_type = ast_module_.create<ast::type::SamplerType>(
|
||||
usage.IsComparisonSampler() ? ast::type::SamplerKind::kComparisonSampler
|
||||
: ast::type::SamplerKind::kSampler);
|
||||
} else if (usage.IsTexture()) {
|
||||
const auto* ptr_type = def_use_mgr_->GetDef(var.type_id());
|
||||
if (!ptr_type) {
|
||||
Fail() << "Invalid type for variable " << var.PrettyPrint();
|
||||
return nullptr;
|
||||
}
|
||||
const auto* raw_image_type =
|
||||
def_use_mgr_->GetDef(ptr_type->GetSingleWordInOperand(1));
|
||||
if (!raw_image_type) {
|
||||
Fail() << "Invalid pointer type for variable " << var.PrettyPrint();
|
||||
return nullptr;
|
||||
}
|
||||
switch (raw_image_type->opcode()) {
|
||||
case SpvOpTypeImage: // The expected case.
|
||||
break;
|
||||
case SpvOpTypeArray:
|
||||
case SpvOpTypeRuntimeArray:
|
||||
Fail() << "arrays of textures are not supported in WGSL; can't "
|
||||
"translate variable "
|
||||
<< var.PrettyPrint();
|
||||
return nullptr;
|
||||
default:
|
||||
Fail() << "invalid type for image variable " << var.PrettyPrint();
|
||||
return nullptr;
|
||||
}
|
||||
const spvtools::opt::analysis::Image* image_type =
|
||||
type_mgr_->GetType(raw_image_type->result_id())->AsImage();
|
||||
type_mgr_->GetType(raw_handle_type->result_id())->AsImage();
|
||||
if (!image_type) {
|
||||
Fail() << "internal error: Couldn't look up image type"
|
||||
<< raw_image_type->PrettyPrint();
|
||||
<< raw_handle_type->PrettyPrint();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1676,7 +1741,7 @@ ast::type::Type* ParserImpl::GetTypeForHandleVar(
|
|||
(image_type->format() == SpvImageFormatUnknown)) {
|
||||
// Make a sampled texture type.
|
||||
auto* ast_sampled_component_type =
|
||||
ConvertType(raw_image_type->GetSingleWordInOperand(0));
|
||||
ConvertType(raw_handle_type->GetSingleWordInOperand(0));
|
||||
|
||||
// Vulkan ignores the depth parameter on OpImage, so pay attention to the
|
||||
// usage as well. That is, it's valid for a Vulkan shader to use an
|
||||
|
@ -1693,31 +1758,9 @@ ast::type::Type* ParserImpl::GetTypeForHandleVar(
|
|||
dim, ast_sampled_component_type);
|
||||
}
|
||||
} else {
|
||||
// Make a storage texture.
|
||||
bool is_nonwritable = false;
|
||||
bool is_nonreadable = false;
|
||||
for (const auto& deco : GetDecorationsFor(var.result_id())) {
|
||||
if (deco.size() != 1) {
|
||||
continue;
|
||||
}
|
||||
if (deco[0] == SpvDecorationNonWritable) {
|
||||
is_nonwritable = true;
|
||||
}
|
||||
if (deco[0] == SpvDecorationNonReadable) {
|
||||
is_nonreadable = true;
|
||||
}
|
||||
}
|
||||
if (is_nonwritable && is_nonreadable) {
|
||||
Fail() << "storage image variable is both NonWritable and NonReadable"
|
||||
<< var.PrettyPrint();
|
||||
}
|
||||
if (!is_nonwritable && !is_nonreadable) {
|
||||
Fail()
|
||||
<< "storage image variable is neither NonWritable nor NonReadable"
|
||||
<< var.PrettyPrint();
|
||||
}
|
||||
const auto access = is_nonwritable ? ast::AccessControl::kReadOnly
|
||||
: ast::AccessControl::kWriteOnly;
|
||||
const auto access = usage.IsStorageReadTexture()
|
||||
? ast::AccessControl::kReadOnly
|
||||
: ast::AccessControl::kWriteOnly;
|
||||
const auto format = enum_converter_.ToImageFormat(image_type->format());
|
||||
if (format == ast::type::ImageFormat::kNone) {
|
||||
return nullptr;
|
||||
|
@ -1731,6 +1774,7 @@ ast::type::Type* ParserImpl::GetTypeForHandleVar(
|
|||
<< var.PrettyPrint();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Form the pointer type.
|
||||
return ast_module_.create<ast::type::PointerType>(
|
||||
ast_store_type, ast::StorageClass::kUniformConstant);
|
||||
|
|
|
@ -44,6 +44,23 @@
|
|||
#include "src/reader/spirv/usage.h"
|
||||
#include "src/source.h"
|
||||
|
||||
/// This is the implementation of the SPIR-V parser for Tint.
|
||||
|
||||
/// Notes on terminology:
|
||||
///
|
||||
/// A WGSL "handle" is an opaque object used for accessing a resource via
|
||||
/// special builtins. In SPIR-V, a handle is stored a variable in the
|
||||
/// UniformConstant storage class. The handles supported by SPIR-V are:
|
||||
/// - images, both sampled texture and storage image
|
||||
/// - samplers
|
||||
/// - combined image+sampler
|
||||
/// - acceleration structures for raytracing.
|
||||
///
|
||||
/// WGSL only supports samplers and images, but calls images "textures".
|
||||
/// When emitting errors, we aim to use terminology most likely to be
|
||||
/// familiar to Vulkan SPIR-V developers. We will tend to use "image"
|
||||
/// and "sampler" instead of "handle".
|
||||
|
||||
namespace tint {
|
||||
namespace reader {
|
||||
namespace spirv {
|
||||
|
|
|
@ -1061,6 +1061,117 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
|
||||
// Test emission of handle variables.
|
||||
|
||||
// Test emission of variables where we don't have enough clues from their
|
||||
// use in image access instructions in executable code. For these we have
|
||||
// to infer usage from the SPIR-V sampler or image type.
|
||||
struct DeclUnderspecifiedHandleCase {
|
||||
std::string decorations; // SPIR-V decorations
|
||||
std::string inst; // SPIR-V variable declarations
|
||||
std::string var_decl; // WGSL variable declaration
|
||||
};
|
||||
inline std::ostream& operator<<(std::ostream& out,
|
||||
const DeclUnderspecifiedHandleCase& c) {
|
||||
out << "DeclUnderspecifiedHandleCase(" << c.inst << "\n" << c.var_decl << ")";
|
||||
return out;
|
||||
}
|
||||
|
||||
using SpvParserTest_DeclUnderspecifiedHandle =
|
||||
SpvParserTestBase<::testing::TestWithParam<DeclUnderspecifiedHandleCase>>;
|
||||
|
||||
TEST_P(SpvParserTest_DeclUnderspecifiedHandle, Variable) {
|
||||
const auto assembly = Preamble() + R"(
|
||||
OpDecorate %10 DescriptorSet 0
|
||||
OpDecorate %10 Binding 0
|
||||
)" + GetParam().decorations +
|
||||
CommonTypes() + GetParam().inst +
|
||||
R"(
|
||||
|
||||
%main = OpFunction %void None %voidfn
|
||||
%entry = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
|
||||
EXPECT_TRUE(p->error().empty()) << p->error();
|
||||
const auto module = p->module().to_str();
|
||||
EXPECT_THAT(module, HasSubstr(GetParam().var_decl)) << module;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(Samplers,
|
||||
SpvParserTest_DeclUnderspecifiedHandle,
|
||||
::testing::Values(
|
||||
|
||||
DeclUnderspecifiedHandleCase{"", R"(
|
||||
%ptr = OpTypePointer UniformConstant %sampler
|
||||
%10 = OpVariable %ptr UniformConstant
|
||||
)",
|
||||
R"(
|
||||
DecoratedVariable{
|
||||
Decorations{
|
||||
SetDecoration{0}
|
||||
BindingDecoration{0}
|
||||
}
|
||||
x_10
|
||||
uniform_constant
|
||||
__sampler_sampler
|
||||
})"}));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(Images,
|
||||
SpvParserTest_DeclUnderspecifiedHandle,
|
||||
::testing::Values(
|
||||
|
||||
DeclUnderspecifiedHandleCase{"", R"(
|
||||
%10 = OpVariable %ptr_f_texture_1d UniformConstant
|
||||
)",
|
||||
R"(
|
||||
DecoratedVariable{
|
||||
Decorations{
|
||||
SetDecoration{0}
|
||||
BindingDecoration{0}
|
||||
}
|
||||
x_10
|
||||
uniform_constant
|
||||
__sampled_texture_1d__f32
|
||||
})"},
|
||||
DeclUnderspecifiedHandleCase{R"(
|
||||
OpDecorate %10 NonWritable
|
||||
)",
|
||||
R"(
|
||||
%10 = OpVariable %ptr_f_storage_1d UniformConstant
|
||||
)",
|
||||
R"(
|
||||
DecoratedVariable{
|
||||
Decorations{
|
||||
SetDecoration{0}
|
||||
BindingDecoration{0}
|
||||
}
|
||||
x_10
|
||||
uniform_constant
|
||||
__storage_texture_read_only_1d_rg32float
|
||||
})"},
|
||||
DeclUnderspecifiedHandleCase{R"(
|
||||
OpDecorate %10 NonReadable
|
||||
)",
|
||||
R"(
|
||||
%10 = OpVariable %ptr_f_storage_1d UniformConstant
|
||||
)",
|
||||
R"(
|
||||
DecoratedVariable{
|
||||
Decorations{
|
||||
SetDecoration{0}
|
||||
BindingDecoration{0}
|
||||
}
|
||||
x_10
|
||||
uniform_constant
|
||||
__storage_texture_write_only_1d_rg32float
|
||||
})"}
|
||||
|
||||
));
|
||||
|
||||
// Test emission of variables when we have sampled image accesses in
|
||||
// executable code.
|
||||
|
||||
struct DeclSampledImageCase {
|
||||
std::string inst; // The provoking image access instruction.
|
||||
std::string var_decl; // WGSL variable declaration
|
||||
|
@ -1068,7 +1179,7 @@ struct DeclSampledImageCase {
|
|||
};
|
||||
inline std::ostream& operator<<(std::ostream& out,
|
||||
const DeclSampledImageCase& c) {
|
||||
out << "UsageSampledImageCase(" << c.inst << "\n"
|
||||
out << "DeclSampledImageCase(" << c.inst << "\n"
|
||||
<< c.var_decl << "\n"
|
||||
<< c.texture_builtin << ")";
|
||||
return out;
|
||||
|
|
Loading…
Reference in New Issue