diff --git a/BUILD.gn b/BUILD.gn index 924e83218a..0a47cd7841 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -470,6 +470,8 @@ source_set("libtint_spv_reader_src") { "src/reader/spirv/parser.h", "src/reader/spirv/parser_impl.cc", "src/reader/spirv/parser_impl.h", + "src/reader/spirv/usage.cc", + "src/reader/spirv/usage.h", ] deps = [ @@ -855,6 +857,7 @@ source_set("tint_unittests_spv_reader_src") { "src/reader/spirv/parser_test.cc", "src/reader/spirv/spirv_tools_helpers_test.cc", "src/reader/spirv/spirv_tools_helpers_test.h", + "src/reader/spirv/usage_test.cc", ] configs += [ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 717a6303dd..99e56366c4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -273,6 +273,8 @@ if(${TINT_BUILD_SPV_READER}) reader/spirv/parser.h reader/spirv/parser_impl.cc reader/spirv/parser_impl.h + reader/spirv/usage.cc + reader/spirv/usage.h ) endif() @@ -459,6 +461,7 @@ if(${TINT_BUILD_SPV_READER}) reader/spirv/parser_test.cc reader/spirv/spirv_tools_helpers_test.cc reader/spirv/spirv_tools_helpers_test.h + reader/spirv/usage_test.cc ) endif() diff --git a/src/reader/spirv/usage.cc b/src/reader/spirv/usage.cc new file mode 100644 index 0000000000..23635e16e8 --- /dev/null +++ b/src/reader/spirv/usage.cc @@ -0,0 +1,186 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/reader/spirv/usage.h" + +namespace tint { +namespace reader { +namespace spirv { + +Usage::Usage() {} +Usage::Usage(const Usage& other) = default; +Usage::~Usage() = default; + +std::ostream& Usage::operator<<(std::ostream& out) const { + out << "Usage("; + if (IsSampler()) { + out << "Sampler("; + if (is_comparison_sampler_) { + out << " comparison"; + } + out << " )"; + } + if (IsTexture()) { + out << "Texture("; + if (is_sampled_) { + out << " is_sampled"; + } + if (is_multisampled_) { + out << " ms"; + } + if (is_depth_) { + out << " depth"; + } + if (is_storage_read_) { + out << " read"; + } + if (is_storage_write_) { + out << " write"; + } + out << " )"; + } + out << ")"; + return out; +} + +bool Usage::IsValid() const { + // Check sampler state internal consistency. + if (is_comparison_sampler_ && !is_sampler_) { + return false; + } + + // Check texture state. + // |is_texture_| is implied by any of the later texture-based properties. + if ((IsStorageTexture() || is_sampled_ || is_multisampled_ || is_depth_) && + !is_texture_) { + return false; + } + if (is_texture_) { + // Multisampled implies sampled. + if (is_multisampled_) { + if (!is_sampled_) { + return false; + } + } + // Depth implies sampled. + if (is_depth_) { + if (!is_sampled_) { + return false; + } + } + + // Sampled can't be storage. + if (is_sampled_) { + if (IsStorageTexture()) { + return false; + } + } + + // Storage can't be sampled. + if (IsStorageTexture()) { + if (is_sampled_) { + return false; + } + } + // Storage texture can't also be a sampler. + if (IsStorageTexture()) { + if (is_sampler_) { + return false; + } + } + + // Can't be both read and write. This is a restriction in WebGPU. + if (is_storage_read_ && is_storage_write_) { + return false; + } + } + return true; +} + +bool Usage::IsComplete() const { + if (!IsValid()) { + return false; + } + if (IsSampler()) { + return true; + } + if (IsTexture()) { + return is_sampled_ || IsStorageTexture(); + } + return false; +} + +bool Usage::operator==(const Usage& other) const { + return is_sampler_ == other.is_sampler_ && + is_comparison_sampler_ == other.is_comparison_sampler_ && + is_texture_ == other.is_texture_ && is_sampled_ == other.is_sampled_ && + is_multisampled_ == other.is_multisampled_ && + is_depth_ == other.is_depth_ && + is_storage_read_ == other.is_storage_read_ && + is_storage_write_ == other.is_storage_write_; +} + +void Usage::Add(const Usage& other) { + is_sampler_ = is_sampler_ || other.is_sampler_; + is_comparison_sampler_ = + is_comparison_sampler_ || other.is_comparison_sampler_; + is_texture_ = is_texture_ || other.is_texture_; + is_sampled_ = is_sampled_ || other.is_sampled_; + is_multisampled_ = is_multisampled_ || other.is_multisampled_; + is_depth_ = is_depth_ || other.is_depth_; + is_storage_read_ = is_storage_read_ || other.is_storage_read_; + is_storage_write_ = is_storage_write_ || other.is_storage_write_; +} + +void Usage::AddSampler() { + is_sampler_ = true; +} + +void Usage::AddComparisonSampler() { + AddSampler(); + is_comparison_sampler_ = true; +} + +void Usage::AddTexture() { + is_texture_ = true; +} + +void Usage::AddStorageReadTexture() { + AddTexture(); + is_storage_read_ = true; +} + +void Usage::AddStorageWriteTexture() { + AddTexture(); + is_storage_write_ = true; +} + +void Usage::AddSampledTexture() { + AddTexture(); + is_sampled_ = true; +} + +void Usage::AddMultisampledTexture() { + AddSampledTexture(); + is_multisampled_ = true; +} + +void Usage::AddDepthTexture() { + AddSampledTexture(); + is_depth_ = true; +} + +} // namespace spirv +} // namespace reader +} // namespace tint diff --git a/src/reader/spirv/usage.h b/src/reader/spirv/usage.h new file mode 100644 index 0000000000..370b6a6631 --- /dev/null +++ b/src/reader/spirv/usage.h @@ -0,0 +1,130 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SRC_READER_SPIRV_USAGE_H_ +#define SRC_READER_SPIRV_USAGE_H_ + +#include + +namespace tint { +namespace reader { +namespace spirv { + +/// Records the properties of a sampler or texture based on how it's used +/// by image instructions inside function bodies. +/// +/// For example: +/// +/// If %X is the "Image" parameter of an OpImageWrite instruction then +/// - The memory object declaration underlying %X will gain +/// AddStorageWriteTexture usage +/// +/// If %Y is the "Sampled Image" parameter of an OpImageSampleDrefExplicitLod +/// instruction, and %Y is composed from sampler %YSam and image %YIm, then: +/// - The memory object declaration underlying %YSam will gain +/// AddComparisonSampler usage +/// - The memory object declaration unederlying %YIm will gain +/// AddSampledTexture and AddDepthTexture usages +class Usage { + public: + Usage(); + Usage(const Usage& other); + ~Usage(); + + /// @returns true if this usage is internally consistent + bool IsValid() const; + /// @returns true if the usage fully determines a WebGPU binding type. + bool IsComplete() const; + + /// @returns true if this usage is a sampler usage. + bool IsSampler() const { return is_sampler_; } + /// @returns true if this usage is a comparison sampler usage. + bool IsComparisonSampler() const { return is_comparison_sampler_; } + + /// @returns true if this usage is a texture usage. + bool IsTexture() const { return is_texture_; } + /// @returns true if this usage is a sampled texture usage. + bool IsSampledTexture() const { return is_sampled_; } + /// @returns true if this usage is a multisampled texture usage. + bool IsMultisampledTexture() const { return is_multisampled_; } + /// @returns true if this usage is a dpeth texture usage. + bool IsDepthTexture() const { return is_depth_; } + /// @returns true if this usage is a read-only storage texture + bool IsStorageReadTexture() const { return is_storage_read_; } + /// @returns true if this usage is a write-only storage texture + bool IsStorageWriteTexture() const { return is_storage_write_; } + + /// @returns true if this is a storage texture. + bool IsStorageTexture() const { + return is_storage_read_ || is_storage_write_; + } + + /// Emits this usage to the given stream + /// @param out the output stream. + /// @returns the modified stream. + std::ostream& operator<<(std::ostream& out) const; + + /// Equality operator + /// @param other the RHS of the equality test. + /// @returns true if |other| is identical to *this + bool operator==(const Usage& other) const; + + /// Adds the usages from another usage object. + /// @param other the other usage + void Add(const Usage& other); + + /// Records usage as a sampler. + void AddSampler(); + /// Records usage as a comparison sampler. + void AddComparisonSampler(); + + /// Records usage as a texture of some kind. + void AddTexture(); + /// Records usage as a read-only storage texture. + void AddStorageReadTexture(); + // Records usage as a write-only storage texture. + void AddStorageWriteTexture(); + // Records usage as a sampled texture. + void AddSampledTexture(); + // Records usage as a multisampled texture. + void AddMultisampledTexture(); + /// Records usage as a depth texture. + void AddDepthTexture(); + + private: + // Sampler properties. + bool is_sampler_ = false; + // A comparison sampler is always a sampler: + // |is_comparison_sampler_| implies |is_sampler_| + bool is_comparison_sampler_ = false; + + // Texture properties. + // |is_texture_| is always implied by any of the others below. + bool is_texture_ = false; + bool is_sampled_ = false; + bool is_multisampled_ = false; // This implies it's sampled as well. + bool is_depth_ = false; + bool is_storage_read_ = false; + bool is_storage_write_ = false; +}; + +inline std::ostream& operator<<(std::ostream& out, const Usage& u) { + return u.operator<<(out); +} + +} // namespace spirv +} // namespace reader +} // namespace tint + +#endif // SRC_READER_SPIRV_USAGE_H_ diff --git a/src/reader/spirv/usage_test.cc b/src/reader/spirv/usage_test.cc new file mode 100644 index 0000000000..ef50656585 --- /dev/null +++ b/src/reader/spirv/usage_test.cc @@ -0,0 +1,297 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "gmock/gmock.h" +#include "src/reader/spirv/parser_impl_test_helper.h" +#include "src/reader/spirv/usage.h" + +namespace tint { +namespace reader { +namespace spirv { +namespace { + +using ::testing::Eq; + +TEST_F(SpvParserTest, Usage_Trivial_Properties) { + Usage u; + EXPECT_TRUE(u.IsValid()); + EXPECT_FALSE(u.IsComplete()); + EXPECT_FALSE(u.IsSampler()); + EXPECT_FALSE(u.IsComparisonSampler()); + EXPECT_FALSE(u.IsTexture()); + EXPECT_FALSE(u.IsSampledTexture()); + EXPECT_FALSE(u.IsMultisampledTexture()); + EXPECT_FALSE(u.IsDepthTexture()); + EXPECT_FALSE(u.IsStorageReadTexture()); + EXPECT_FALSE(u.IsStorageWriteTexture()); +} + +TEST_F(SpvParserTest, Usage_Trivial_Output) { + std::ostringstream ss; + Usage u; + ss << u; + EXPECT_THAT(ss.str(), Eq("Usage()")); +} + +TEST_F(SpvParserTest, Usage_Equality_OneDifference) { + const int num_usages = 9; + std::vector usages(num_usages); + usages[1].AddSampler(); + usages[2].AddComparisonSampler(); + usages[3].AddTexture(); + usages[4].AddSampledTexture(); + usages[5].AddMultisampledTexture(); + usages[6].AddDepthTexture(); + usages[7].AddStorageReadTexture(); + usages[8].AddStorageWriteTexture(); + for (int i = 0; i < num_usages; ++i) { + for (int j = 0; j < num_usages; ++j) { + const auto& lhs = usages[i]; + const auto& rhs = usages[j]; + if (i == j) { + EXPECT_TRUE(lhs == rhs); + } else { + EXPECT_FALSE(lhs == rhs); + } + } + } +} + +TEST_F(SpvParserTest, Usage_Add) { + // Mix two nontrivial usages. + Usage a; + a.AddStorageReadTexture(); + + Usage b; + b.AddComparisonSampler(); + + a.Add(b); + + EXPECT_FALSE(a.IsValid()); + EXPECT_FALSE(a.IsComplete()); + EXPECT_TRUE(a.IsSampler()); + EXPECT_TRUE(a.IsComparisonSampler()); + EXPECT_TRUE(a.IsTexture()); + EXPECT_FALSE(a.IsSampledTexture()); + EXPECT_FALSE(a.IsMultisampledTexture()); + EXPECT_FALSE(a.IsDepthTexture()); + EXPECT_TRUE(a.IsStorageReadTexture()); + EXPECT_FALSE(a.IsStorageWriteTexture()); + + std::ostringstream ss; + ss << a; + EXPECT_THAT(ss.str(), Eq("Usage(Sampler( comparison )Texture( read ))")); +} + +TEST_F(SpvParserTest, Usage_AddSampler) { + std::ostringstream ss; + Usage u; + u.AddSampler(); + + EXPECT_TRUE(u.IsValid()); + EXPECT_TRUE(u.IsComplete()); + EXPECT_TRUE(u.IsSampler()); + EXPECT_FALSE(u.IsComparisonSampler()); + EXPECT_FALSE(u.IsTexture()); + EXPECT_FALSE(u.IsSampledTexture()); + EXPECT_FALSE(u.IsMultisampledTexture()); + EXPECT_FALSE(u.IsDepthTexture()); + EXPECT_FALSE(u.IsStorageReadTexture()); + EXPECT_FALSE(u.IsStorageWriteTexture()); + + ss << u; + EXPECT_THAT(ss.str(), Eq("Usage(Sampler( ))")); + + // Check idempotency + auto copy(u); + u.AddSampler(); + EXPECT_TRUE(u == copy); +} + +TEST_F(SpvParserTest, Usage_AddComparisonSampler) { + std::ostringstream ss; + Usage u; + u.AddComparisonSampler(); + + EXPECT_TRUE(u.IsValid()); + EXPECT_TRUE(u.IsComplete()); + EXPECT_TRUE(u.IsSampler()); + EXPECT_TRUE(u.IsComparisonSampler()); + EXPECT_FALSE(u.IsTexture()); + EXPECT_FALSE(u.IsSampledTexture()); + EXPECT_FALSE(u.IsMultisampledTexture()); + EXPECT_FALSE(u.IsDepthTexture()); + EXPECT_FALSE(u.IsStorageReadTexture()); + EXPECT_FALSE(u.IsStorageWriteTexture()); + + ss << u; + EXPECT_THAT(ss.str(), Eq("Usage(Sampler( comparison ))")); + + auto copy(u); + u.AddComparisonSampler(); + EXPECT_TRUE(u == copy); +} + +TEST_F(SpvParserTest, Usage_AddTexture) { + std::ostringstream ss; + Usage u; + u.AddTexture(); + + EXPECT_TRUE(u.IsValid()); + EXPECT_FALSE(u.IsComplete()); // Don't know if it's sampled or storage + EXPECT_FALSE(u.IsSampler()); + EXPECT_FALSE(u.IsComparisonSampler()); + EXPECT_TRUE(u.IsTexture()); + EXPECT_FALSE(u.IsSampledTexture()); + EXPECT_FALSE(u.IsMultisampledTexture()); + EXPECT_FALSE(u.IsDepthTexture()); + EXPECT_FALSE(u.IsStorageReadTexture()); + EXPECT_FALSE(u.IsStorageWriteTexture()); + + ss << u; + EXPECT_THAT(ss.str(), Eq("Usage(Texture( ))")); + + auto copy(u); + u.AddTexture(); + EXPECT_TRUE(u == copy); +} + +TEST_F(SpvParserTest, Usage_AddSampledTexture) { + std::ostringstream ss; + Usage u; + u.AddSampledTexture(); + + EXPECT_TRUE(u.IsValid()); + EXPECT_TRUE(u.IsComplete()); + EXPECT_FALSE(u.IsSampler()); + EXPECT_FALSE(u.IsComparisonSampler()); + EXPECT_TRUE(u.IsTexture()); + EXPECT_TRUE(u.IsSampledTexture()); + EXPECT_FALSE(u.IsMultisampledTexture()); + EXPECT_FALSE(u.IsDepthTexture()); + EXPECT_FALSE(u.IsStorageReadTexture()); + EXPECT_FALSE(u.IsStorageWriteTexture()); + + ss << u; + EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled ))")); + + auto copy(u); + u.AddSampledTexture(); + EXPECT_TRUE(u == copy); +} + +TEST_F(SpvParserTest, Usage_AddMultisampledTexture) { + std::ostringstream ss; + Usage u; + u.AddMultisampledTexture(); + + EXPECT_TRUE(u.IsValid()); + EXPECT_TRUE(u.IsComplete()); + EXPECT_FALSE(u.IsSampler()); + EXPECT_FALSE(u.IsComparisonSampler()); + EXPECT_TRUE(u.IsTexture()); + EXPECT_TRUE(u.IsSampledTexture()); + EXPECT_TRUE(u.IsMultisampledTexture()); + EXPECT_FALSE(u.IsDepthTexture()); + EXPECT_FALSE(u.IsStorageReadTexture()); + EXPECT_FALSE(u.IsStorageWriteTexture()); + + ss << u; + EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled ms ))")); + + auto copy(u); + u.AddMultisampledTexture(); + EXPECT_TRUE(u == copy); +} + +TEST_F(SpvParserTest, Usage_AddDepthTexture) { + std::ostringstream ss; + Usage u; + u.AddDepthTexture(); + + EXPECT_TRUE(u.IsValid()); + EXPECT_TRUE(u.IsComplete()); + EXPECT_FALSE(u.IsSampler()); + EXPECT_FALSE(u.IsComparisonSampler()); + EXPECT_TRUE(u.IsTexture()); + EXPECT_TRUE(u.IsSampledTexture()); + EXPECT_FALSE(u.IsMultisampledTexture()); + EXPECT_TRUE(u.IsDepthTexture()); + EXPECT_FALSE(u.IsStorageReadTexture()); + EXPECT_FALSE(u.IsStorageWriteTexture()); + + ss << u; + EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled depth ))")); + + auto copy(u); + u.AddDepthTexture(); + EXPECT_TRUE(u == copy); +} + +TEST_F(SpvParserTest, Usage_AddStorageReadTexture) { + std::ostringstream ss; + Usage u; + u.AddStorageReadTexture(); + + EXPECT_TRUE(u.IsValid()); + EXPECT_TRUE(u.IsComplete()); + EXPECT_FALSE(u.IsSampler()); + EXPECT_FALSE(u.IsComparisonSampler()); + EXPECT_TRUE(u.IsTexture()); + EXPECT_FALSE(u.IsSampledTexture()); + EXPECT_FALSE(u.IsMultisampledTexture()); + EXPECT_FALSE(u.IsDepthTexture()); + EXPECT_TRUE(u.IsStorageReadTexture()); + EXPECT_FALSE(u.IsStorageWriteTexture()); + + ss << u; + EXPECT_THAT(ss.str(), Eq("Usage(Texture( read ))")); + + auto copy(u); + u.AddStorageReadTexture(); + EXPECT_TRUE(u == copy); +} + +TEST_F(SpvParserTest, Usage_AddStorageWriteTexture) { + std::ostringstream ss; + Usage u; + u.AddStorageWriteTexture(); + + EXPECT_TRUE(u.IsValid()); + EXPECT_TRUE(u.IsComplete()); + EXPECT_FALSE(u.IsSampler()); + EXPECT_FALSE(u.IsComparisonSampler()); + EXPECT_TRUE(u.IsTexture()); + EXPECT_FALSE(u.IsSampledTexture()); + EXPECT_FALSE(u.IsMultisampledTexture()); + EXPECT_FALSE(u.IsDepthTexture()); + EXPECT_FALSE(u.IsStorageReadTexture()); + EXPECT_TRUE(u.IsStorageWriteTexture()); + + ss << u; + EXPECT_THAT(ss.str(), Eq("Usage(Texture( write ))")); + + auto copy(u); + u.AddStorageWriteTexture(); + EXPECT_TRUE(u == copy); +} + +} // namespace +} // namespace spirv +} // namespace reader +} // namespace tint