|  | // 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 (var->declared_address_space == ast::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"), 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<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 |