| // 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 <utility> |
| |
| #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<const ast::Attribute*> attributes) { |
| for (auto* attribute : attributes) { |
| if (auto* builtin_attribute = attribute->As<ast::BuiltinAttribute>()) { |
| 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<sem::Struct>()) { |
| for (auto* member : struct_ty->Members()) { |
| if (ContainsFragDepth(member->Declaration()->attributes)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| bool ShouldRun(const Program* program) { |
| 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; |
| } |
| |
| } // anonymous namespace |
| |
| ClampFragDepth::ClampFragDepth() = default; |
| ClampFragDepth::~ClampFragDepth() = default; |
| |
| Transform::ApplyResult ClampFragDepth::Apply(const Program* src, const DataMap&, DataMap&) const { |
| ProgramBuilder b; |
| CloneContext ctx{&b, src, /* auto_clone_symbols */ true}; |
| |
| // Abort on any use of push constants in the module. |
| for (auto* global : src->AST().GlobalVariables()) { |
| if (auto* var = global->As<ast::Var>()) { |
| if (TINT_UNLIKELY(var->declared_address_space == type::AddressSpace::kPushConstant)) { |
| TINT_ICE(Transform, b.Diagnostics()) |
| << "ClampFragDepth doesn't know how to handle module that already use push " |
| "constants."; |
| return Program(std::move(b)); |
| } |
| } |
| } |
| |
| if (!ShouldRun(src)) { |
| return SkipTransform; |
| } |
| |
| auto& sem = src->Sem(); |
| auto& sym = 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<push_constant> 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"), type::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<const ast::Struct*, Symbol, 4u> 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<sem::Struct>()->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<ast::TypeName>()->name)); |
| |
| utils::Vector<const ast::Expression*, 8u> initializer_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); |
| } |
| initializer_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(initializer_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(); |
| return Program(std::move(b)); |
| } |
| |
| } // namespace tint::transform |