diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn index 524d5ac432..0f62e3fc93 100644 --- a/src/tint/BUILD.gn +++ b/src/tint/BUILD.gn @@ -482,8 +482,11 @@ libtint_source_set("libtint_core_all_src") { "transform/builtin_polyfill.h", "transform/calculate_array_length.cc", "transform/calculate_array_length.h", + "transform/calculate_array_length.h", "transform/canonicalize_entry_point_io.cc", "transform/canonicalize_entry_point_io.h", + "transform/clamp_frag_depth.cc", + "transform/clamp_frag_depth.h", "transform/combine_samplers.cc", "transform/combine_samplers.h", "transform/decompose_memory_access.cc", @@ -1191,6 +1194,7 @@ if (tint_build_unittests) { "transform/builtin_polyfill_test.cc", "transform/calculate_array_length_test.cc", "transform/canonicalize_entry_point_io_test.cc", + "transform/clamp_frag_depth_test.cc", "transform/combine_samplers_test.cc", "transform/decompose_memory_access_test.cc", "transform/decompose_strided_array_test.cc", diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt index a56c7ac4ae..940c96b3db 100644 --- a/src/tint/CMakeLists.txt +++ b/src/tint/CMakeLists.txt @@ -394,6 +394,8 @@ set(TINT_LIB_SRCS transform/builtin_polyfill.h transform/calculate_array_length.cc transform/calculate_array_length.h + transform/clamp_frag_depth.cc + transform/clamp_frag_depth.h transform/canonicalize_entry_point_io.cc transform/canonicalize_entry_point_io.h transform/combine_samplers.cc @@ -1105,6 +1107,7 @@ if(TINT_BUILD_TESTS) transform/binding_remapper_test.cc transform/builtin_polyfill_test.cc transform/calculate_array_length_test.cc + transform/clamp_frag_depth_test.cc transform/canonicalize_entry_point_io_test.cc transform/combine_samplers_test.cc transform/decompose_memory_access_test.cc diff --git a/src/tint/transform/clamp_frag_depth.cc b/src/tint/transform/clamp_frag_depth.cc new file mode 100644 index 0000000000..1be231ad7b --- /dev/null +++ b/src/tint/transform/clamp_frag_depth.cc @@ -0,0 +1,202 @@ +// Copyright 2022 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/tint/transform/clamp_frag_depth.h" + + #include + +#include "src/tint/ast/attribute.h" +#include "src/tint/ast/builtin_attribute.h" +#include "src/tint/ast/builtin_value.h" +#include "src/tint/ast/function.h" +#include "src/tint/ast/module.h" +#include "src/tint/ast/struct.h" +#include "src/tint/ast/type.h" +#include "src/tint/program_builder.h" +#include "src/tint/sem/function.h" +#include "src/tint/sem/statement.h" +#include "src/tint/sem/struct.h" +#include "src/tint/utils/scoped_assignment.h" +#include "src/tint/utils/vector.h" + +TINT_INSTANTIATE_TYPEINFO(tint::transform::ClampFragDepth); + +namespace tint::transform { + +namespace { + +bool ContainsFragDepth(utils::VectorRef attributes) { + for (auto* attribute : attributes) { + if (auto* builtin_attribute = attribute->As()) { + if (builtin_attribute->builtin == ast::BuiltinValue::kFragDepth) { + return true; + } + } + } + + return false; +} + +bool ReturnsFragDepthAsValue(const ast::Function* fn) { + return ContainsFragDepth(fn->return_type_attributes); +} + +bool ReturnsFragDepthInStruct(const sem::Info& sem, const ast::Function* fn) { + if (auto* struct_ty = sem.Get(fn)->ReturnType()->As()) { + for (auto* member : struct_ty->Members()) { + if (ContainsFragDepth(member->Declaration()->attributes)) { + return true; + } + } + } + + return false; +} + +} // anonymous namespace + +ClampFragDepth::ClampFragDepth() = default; +ClampFragDepth::~ClampFragDepth() = default; + +bool ClampFragDepth::ShouldRun(const Program* program, const DataMap&) const { + auto& sem = program->Sem(); + + for (auto* fn : program->AST().Functions()) { + if (fn->PipelineStage() == ast::PipelineStage::kFragment && + (ReturnsFragDepthAsValue(fn) || ReturnsFragDepthInStruct(sem, fn))) { + return true; + } + } + + return false; +} + +void ClampFragDepth::Run(CloneContext& ctx, const DataMap&, DataMap&) const { + // Abort on any use of push constants in the module. + for (auto* global : ctx.src->AST().GlobalVariables()) { + if (auto* var = global->As()) { + if (var->declared_address_space == ast::AddressSpace::kPushConstant) { + TINT_ICE(Transform, ctx.dst->Diagnostics()) + << "ClampFragDepth doesn't know how to handle module that already use push " + "constants."; + return; + } + } + } + + auto& b = *ctx.dst; + auto& sem = ctx.src->Sem(); + auto& sym = ctx.src->Symbols(); + + // At least one entry-point needs clamping. Add the following to the module: + // + // enable chromium_experimental_push_constant; + // + // struct FragDepthClampArgs { + // min : f32, + // max : f32, + // } + // var frag_depth_clamp_args : FragDepthClampArgs; + // + // fn clamp_frag_depth(v : f32) -> f32 { + // return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max); + // } + b.Enable(ast::Extension::kChromiumExperimentalPushConstant); + + b.Structure(b.Symbols().New("FragDepthClampArgs"), + utils::Vector{b.Member("min", b.ty.f32()), b.Member("max", b.ty.f32())}); + + auto args_sym = b.Symbols().New("frag_depth_clamp_args"); + b.GlobalVar(args_sym, b.ty.type_name("FragDepthClampArgs"), ast::AddressSpace::kPushConstant); + + auto base_fn_sym = b.Symbols().New("clamp_frag_depth"); + b.Func(base_fn_sym, utils::Vector{b.Param("v", b.ty.f32())}, b.ty.f32(), + utils::Vector{b.Return(b.Call("clamp", "v", b.MemberAccessor(args_sym, "min"), + b.MemberAccessor(args_sym, "max")))}); + + // If true, the currently cloned function returns frag depth directly as a scalar + bool returns_frag_depth_as_value = false; + + // If valid, the currently cloned function returns frag depth in a struct + // The symbol is the name of the helper function to apply the depth clamping. + Symbol returns_frag_depth_as_struct_helper; + + // Map of io struct to helper function to return the structure with the depth clamping applied. + utils::Hashmap io_structs_clamp_helpers; + + // Register a callback that will be called for each visted AST function. + // This call wraps the cloning of the function's statements, and will assign to + // `returns_frag_depth_as_value` or `returns_frag_depth_as_struct_helper` if the function's + // return value requires depth clamping. + ctx.ReplaceAll([&](const ast::Function* fn) { + if (fn->PipelineStage() != ast::PipelineStage::kFragment) { + return ctx.CloneWithoutTransform(fn); + } + + if (ReturnsFragDepthAsValue(fn)) { + TINT_SCOPED_ASSIGNMENT(returns_frag_depth_as_value, true); + return ctx.CloneWithoutTransform(fn); + } + + if (ReturnsFragDepthInStruct(sem, fn)) { + // At most once per I/O struct, add the conversion function: + // + // fn clamp_frag_depth_S(s : S) -> S { + // return S(s.first, s.second, clamp_frag_depth(s.frag_depth), s.last); + // } + auto* struct_ty = sem.Get(fn)->ReturnType()->As()->Declaration(); + auto helper = io_structs_clamp_helpers.GetOrCreate(struct_ty, [&] { + auto* return_ty = fn->return_type; + auto fn_sym = b.Symbols().New("clamp_frag_depth_" + + sym.NameFor(return_ty->As()->name)); + + utils::Vector constructor_args; + for (auto* member : struct_ty->members) { + const ast::Expression* arg = b.MemberAccessor("s", ctx.Clone(member->symbol)); + if (ContainsFragDepth(member->attributes)) { + arg = b.Call(base_fn_sym, arg); + } + constructor_args.Push(arg); + } + utils::Vector params{b.Param("s", ctx.Clone(return_ty))}; + utils::Vector body{ + b.Return(b.Construct(ctx.Clone(return_ty), std::move(constructor_args))), + }; + b.Func(fn_sym, params, ctx.Clone(return_ty), body); + return fn_sym; + }); + + TINT_SCOPED_ASSIGNMENT(returns_frag_depth_as_struct_helper, helper); + return ctx.CloneWithoutTransform(fn); + } + + return ctx.CloneWithoutTransform(fn); + }); + + // Replace the return statements `return expr` with `return clamp_frag_depth(expr)`. + ctx.ReplaceAll([&](const ast::ReturnStatement* stmt) -> const ast::ReturnStatement* { + if (returns_frag_depth_as_value) { + return b.Return(stmt->source, b.Call(base_fn_sym, ctx.Clone(stmt->value))); + } + if (returns_frag_depth_as_struct_helper.IsValid()) { + return b.Return(stmt->source, + b.Call(returns_frag_depth_as_struct_helper, ctx.Clone(stmt->value))); + } + return nullptr; + }); + + ctx.Clone(); +} + +} // namespace tint::transform diff --git a/src/tint/transform/clamp_frag_depth.h b/src/tint/transform/clamp_frag_depth.h new file mode 100644 index 0000000000..3b15f11c1f --- /dev/null +++ b/src/tint/transform/clamp_frag_depth.h @@ -0,0 +1,81 @@ +// Copyright 2022 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_TINT_TRANSFORM_CLAMP_FRAG_DEPTH_H_ +#define SRC_TINT_TRANSFORM_CLAMP_FRAG_DEPTH_H_ + +#include "src/tint/transform/transform.h" + +// Forward declarations +namespace tint { +class CloneContext; +} // namespace tint + +namespace tint::transform { + +/// Add clamping of the `@builtin(frag_depth)` output of fragment shaders using two push constants +/// provided by the outside environment. For example the following code: +/// +/// ``` +/// @fragment fn main() -> @builtin(frag_depth) f32 { +/// return 0.0; +/// } +/// ``` +/// +/// Is transformed to: +/// +/// ``` +/// enable chromium_experimental_push_constant; +/// +/// struct FragDepthClampArgs { +/// min : f32, +/// max : f32, +/// } +/// +/// var frag_depth_clamp_args : FragDepthClampArgs; +/// +/// fn clamp_frag_depth(v : f32) -> f32 { +/// return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max); +/// } +/// +/// @fragment +/// fn main() -> @builtin(frag_depth) f32 { +/// return clamp_frag_depth(0.0); +/// } +/// ``` +class ClampFragDepth final : public Castable { + public: + /// Constructor + ClampFragDepth(); + /// Destructor + ~ClampFragDepth() override; + + /// @param program the program to inspect + /// @param data optional extra transform-specific input data + /// @returns true if this transform should be run for the given program + bool ShouldRun(const Program* program, const DataMap& data = {}) const override; + + protected: + /// Runs the transform using the CloneContext built for transforming a + /// program. Run() is responsible for calling Clone() on the CloneContext. + /// @param ctx the CloneContext primed with the input program and + /// ProgramBuilder + /// @param inputs optional extra transform-specific input data + /// @param outputs optional extra transform-specific output data + void Run(CloneContext& ctx, const DataMap& inputs, DataMap& outputs) const override; +}; + +} // namespace tint::transform + +#endif // SRC_TINT_TRANSFORM_CLAMP_FRAG_DEPTH_H_ diff --git a/src/tint/transform/clamp_frag_depth_test.cc b/src/tint/transform/clamp_frag_depth_test.cc new file mode 100644 index 0000000000..b94d5af262 --- /dev/null +++ b/src/tint/transform/clamp_frag_depth_test.cc @@ -0,0 +1,381 @@ +// Copyright 2021 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/tint/transform/clamp_frag_depth.h" + +#include "src/tint/transform/test_helper.h" + +namespace tint::transform { +namespace { + +using ClampFragDepthTest = TransformTest; + +TEST_F(ClampFragDepthTest, ShouldRunEmptyModule) { + auto* src = R"()"; + + EXPECT_FALSE(ShouldRun(src)); +} + +TEST_F(ClampFragDepthTest, ShouldRunNoFragmentShader) { + auto* src = R"( + fn f() -> f32 { + return 0.0; + } + + @compute @workgroup_size(1) fn cs() { + } + + @vertex fn vs() -> @builtin(position) vec4 { + return vec4(); + } + )"; + + EXPECT_FALSE(ShouldRun(src)); +} + +TEST_F(ClampFragDepthTest, ShouldRunFragmentShaderNoReturnType) { + auto* src = R"( + @fragment fn main() { + } + )"; + + EXPECT_FALSE(ShouldRun(src)); +} + +TEST_F(ClampFragDepthTest, ShouldRunFragmentShaderNoFragDepth) { + auto* src = R"( + @fragment fn main() -> @location(0) f32 { + return 0.0; + } + + struct S { + @location(0) a : f32, + @builtin(sample_mask) b : u32, + } + @fragment fn main2() -> S { + return S(); + } + )"; + + EXPECT_FALSE(ShouldRun(src)); +} + +TEST_F(ClampFragDepthTest, ShouldRunFragDepthAsDirectReturn) { + auto* src = R"( + @fragment fn main() -> @builtin(frag_depth) f32 { + return 0.0; + } + )"; + + EXPECT_TRUE(ShouldRun(src)); +} + +TEST_F(ClampFragDepthTest, ShouldRunFragDepthInStruct) { + auto* src = R"( + struct S { + @location(0) a : f32, + @builtin(frag_depth) b : f32, + @location(1) c : f32, + } + @fragment fn main() -> S { + return S(); + } + )"; + + EXPECT_TRUE(ShouldRun(src)); +} + +TEST_F(ClampFragDepthTest, SingleReturnOfFragDepth) { + auto* src = R"( + @fragment fn main() -> @builtin(frag_depth) f32 { + return 0.0; + } + )"; + + auto* expect = R"( +enable chromium_experimental_push_constant; + +struct FragDepthClampArgs { + min : f32, + max : f32, +} + +var frag_depth_clamp_args : FragDepthClampArgs; + +fn clamp_frag_depth(v : f32) -> f32 { + return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max); +} + +@fragment +fn main() -> @builtin(frag_depth) f32 { + return clamp_frag_depth(0.0); +} +)"; + + auto got = Run(src); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(ClampFragDepthTest, MultipleReturnOfFragDepth) { + auto* src = R"( + @fragment fn main() -> @builtin(frag_depth) f32 { + if (false) { + return 1.0; + } + return 0.0; + } + )"; + + auto* expect = R"( +enable chromium_experimental_push_constant; + +struct FragDepthClampArgs { + min : f32, + max : f32, +} + +var frag_depth_clamp_args : FragDepthClampArgs; + +fn clamp_frag_depth(v : f32) -> f32 { + return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max); +} + +@fragment +fn main() -> @builtin(frag_depth) f32 { + if (false) { + return clamp_frag_depth(1.0); + } + return clamp_frag_depth(0.0); +} +)"; + + auto got = Run(src); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(ClampFragDepthTest, OtherFunctionWithoutFragDepth) { + auto* src = R"( + @fragment fn main() -> @builtin(frag_depth) f32 { + return 0.0; + } + @fragment fn friend() -> @location(0) f32 { + return 0.0; + } + )"; + + auto* expect = R"( +enable chromium_experimental_push_constant; + +struct FragDepthClampArgs { + min : f32, + max : f32, +} + +var frag_depth_clamp_args : FragDepthClampArgs; + +fn clamp_frag_depth(v : f32) -> f32 { + return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max); +} + +@fragment +fn main() -> @builtin(frag_depth) f32 { + return clamp_frag_depth(0.0); +} + +@fragment +fn friend() -> @location(0) f32 { + return 0.0; +} +)"; + + auto got = Run(src); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(ClampFragDepthTest, SimpleReturnOfStruct) { + auto* src = R"( + struct S { + @builtin(frag_depth) frag_depth : f32, + } + + @fragment fn main() -> S { + return S(0.0); + } + )"; + + auto* expect = R"( +enable chromium_experimental_push_constant; + +struct FragDepthClampArgs { + min : f32, + max : f32, +} + +var frag_depth_clamp_args : FragDepthClampArgs; + +fn clamp_frag_depth(v : f32) -> f32 { + return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max); +} + +struct S { + @builtin(frag_depth) + frag_depth : f32, +} + +fn clamp_frag_depth_S(s : S) -> S { + return S(clamp_frag_depth(s.frag_depth)); +} + +@fragment +fn main() -> S { + return clamp_frag_depth_S(S(0.0)); +} +)"; + + auto got = Run(src); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(ClampFragDepthTest, MixOfFunctionReturningStruct) { + auto* src = R"( + struct S { + @builtin(frag_depth) frag_depth : f32, + } + struct S2 { + @builtin(frag_depth) frag_depth : f32, + } + + @fragment fn returnS() -> S { + return S(0.0); + } + @fragment fn againReturnS() -> S { + return S(0.0); + } + @fragment fn returnS2() -> S2 { + return S2(0.0); + } + )"; + + // clamp_frag_depth_S is emitted only once. + // S2 gets its own clamping function. + auto* expect = R"( +enable chromium_experimental_push_constant; + +struct FragDepthClampArgs { + min : f32, + max : f32, +} + +var frag_depth_clamp_args : FragDepthClampArgs; + +fn clamp_frag_depth(v : f32) -> f32 { + return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max); +} + +struct S { + @builtin(frag_depth) + frag_depth : f32, +} + +struct S2 { + @builtin(frag_depth) + frag_depth : f32, +} + +fn clamp_frag_depth_S(s : S) -> S { + return S(clamp_frag_depth(s.frag_depth)); +} + +@fragment +fn returnS() -> S { + return clamp_frag_depth_S(S(0.0)); +} + +@fragment +fn againReturnS() -> S { + return clamp_frag_depth_S(S(0.0)); +} + +fn clamp_frag_depth_S2(s : S2) -> S2 { + return S2(clamp_frag_depth(s.frag_depth)); +} + +@fragment +fn returnS2() -> S2 { + return clamp_frag_depth_S2(S2(0.0)); +} +)"; + + auto got = Run(src); + EXPECT_EQ(expect, str(got)); +} + +TEST_F(ClampFragDepthTest, ComplexIOStruct) { + auto* src = R"( + struct S { + @location(0) blou : vec4, + @location(1) bi : vec4, + @builtin(frag_depth) frag_depth : f32, + @location(2) boul : i32, + @builtin(sample_mask) ga : u32, + } + + @fragment fn main() -> S { + return S(vec4(), vec4(), 0.0, 1, 0u); + } + )"; + + auto* expect = R"( +enable chromium_experimental_push_constant; + +struct FragDepthClampArgs { + min : f32, + max : f32, +} + +var frag_depth_clamp_args : FragDepthClampArgs; + +fn clamp_frag_depth(v : f32) -> f32 { + return clamp(v, frag_depth_clamp_args.min, frag_depth_clamp_args.max); +} + +struct S { + @location(0) + blou : vec4, + @location(1) + bi : vec4, + @builtin(frag_depth) + frag_depth : f32, + @location(2) + boul : i32, + @builtin(sample_mask) + ga : u32, +} + +fn clamp_frag_depth_S(s : S) -> S { + return S(s.blou, s.bi, clamp_frag_depth(s.frag_depth), s.boul, s.ga); +} + +@fragment +fn main() -> S { + return clamp_frag_depth_S(S(vec4(), vec4(), 0.0, 1, 0u)); +} +)"; + + auto got = Run(src); + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace tint::transform