| // 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/combine_samplers.h" |
| |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "src/tint/program_builder.h" |
| #include "src/tint/sem/function.h" |
| #include "src/tint/sem/statement.h" |
| |
| #include "src/tint/utils/map.h" |
| |
| TINT_INSTANTIATE_TYPEINFO(tint::transform::CombineSamplers); |
| TINT_INSTANTIATE_TYPEINFO(tint::transform::CombineSamplers::BindingInfo); |
| |
| namespace { |
| |
| bool IsGlobal(const tint::sem::VariablePair& pair) { |
| return pair.first->Is<tint::sem::GlobalVariable>() && |
| (!pair.second || pair.second->Is<tint::sem::GlobalVariable>()); |
| } |
| |
| } // namespace |
| |
| namespace tint::transform { |
| |
| using namespace tint::number_suffixes; // NOLINT |
| |
| CombineSamplers::BindingInfo::BindingInfo(const BindingMap& map, |
| const sem::BindingPoint& placeholder) |
| : binding_map(map), placeholder_binding_point(placeholder) {} |
| CombineSamplers::BindingInfo::BindingInfo(const BindingInfo& other) = default; |
| CombineSamplers::BindingInfo::~BindingInfo() = default; |
| |
| /// PIMPL state for the transform |
| struct CombineSamplers::State { |
| /// The source program |
| const Program* const src; |
| /// The target program builder |
| ProgramBuilder b; |
| /// The clone context |
| CloneContext ctx = {&b, src, /* auto_clone_symbols */ true}; |
| |
| /// The binding info |
| const BindingInfo* binding_info; |
| |
| /// Map from a texture/sampler pair to the corresponding combined sampler |
| /// variable |
| using CombinedTextureSamplerMap = std::unordered_map<sem::VariablePair, const ast::Variable*>; |
| |
| /// Use sem::BindingPoint without scope. |
| using BindingPoint = sem::BindingPoint; |
| |
| /// A map of all global texture/sampler variable pairs to the global |
| /// combined sampler variable that will replace it. |
| CombinedTextureSamplerMap global_combined_texture_samplers_; |
| |
| /// A map of all texture/sampler variable pairs that contain a function |
| /// parameter to the combined sampler function paramter that will replace it. |
| std::unordered_map<const sem::Function*, CombinedTextureSamplerMap> |
| function_combined_texture_samplers_; |
| |
| /// Placeholder global samplers used when a function contains texture-only |
| /// references (one comparison sampler, one regular). These are also used as |
| /// temporary sampler parameters to the texture builtins to satisfy the WGSL |
| /// resolver, but are then ignored and removed by the GLSL writer. |
| const ast::Variable* placeholder_samplers_[2] = {}; |
| |
| /// Group and binding attributes used by all combined sampler globals. |
| /// Group 0 and binding 0 are used, with collisions disabled. |
| /// @returns the newly-created attribute list |
| auto Attributes() const { |
| utils::Vector<const ast::Attribute*, 3> attributes{ctx.dst->Group(0_a), |
| ctx.dst->Binding(0_a)}; |
| attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision)); |
| return attributes; |
| } |
| |
| /// Constructor |
| /// @param program the source program |
| /// @param info the binding map information |
| State(const Program* program, const BindingInfo* info) : src(program), binding_info(info) {} |
| |
| /// Creates a combined sampler global variables. |
| /// (Note this is actually a Texture node at the AST level, but it will be |
| /// written as the corresponding sampler (eg., sampler2D) on GLSL output.) |
| /// @param texture_var the texture (global) variable |
| /// @param sampler_var the sampler (global) variable |
| /// @param name the default name to use (may be overridden by map lookup) |
| /// @returns the newly-created global variable |
| const ast::Variable* CreateCombinedGlobal(const sem::Variable* texture_var, |
| const sem::Variable* sampler_var, |
| std::string name) { |
| SamplerTexturePair bp_pair; |
| bp_pair.texture_binding_point = texture_var->As<sem::GlobalVariable>()->BindingPoint(); |
| bp_pair.sampler_binding_point = sampler_var |
| ? sampler_var->As<sem::GlobalVariable>()->BindingPoint() |
| : binding_info->placeholder_binding_point; |
| auto it = binding_info->binding_map.find(bp_pair); |
| if (it != binding_info->binding_map.end()) { |
| name = it->second; |
| } |
| const ast::Type* type = CreateCombinedASTTypeFor(texture_var, sampler_var); |
| Symbol symbol = ctx.dst->Symbols().New(name); |
| return ctx.dst->GlobalVar(symbol, type, Attributes()); |
| } |
| |
| /// Creates placeholder global sampler variables. |
| /// @param kind the sampler kind to create for |
| /// @returns the newly-created global variable |
| const ast::Variable* CreatePlaceholder(ast::SamplerKind kind) { |
| const ast::Type* type = ctx.dst->ty.sampler(kind); |
| const char* name = kind == ast::SamplerKind::kComparisonSampler |
| ? "placeholder_comparison_sampler" |
| : "placeholder_sampler"; |
| Symbol symbol = ctx.dst->Symbols().New(name); |
| return ctx.dst->GlobalVar(symbol, type, Attributes()); |
| } |
| |
| /// Creates ast::Type for a given texture and sampler variable pair. |
| /// Depth textures with no samplers are turned into the corresponding |
| /// f32 texture (e.g., texture_depth_2d -> texture_2d<f32>). |
| /// @param texture the texture variable of interest |
| /// @param sampler the texture variable of interest |
| /// @returns the newly-created type |
| const ast::Type* CreateCombinedASTTypeFor(const sem::Variable* texture, |
| const sem::Variable* sampler) { |
| const type::Type* texture_type = texture->Type()->UnwrapRef(); |
| const type::DepthTexture* depth = texture_type->As<type::DepthTexture>(); |
| if (depth && !sampler) { |
| return ctx.dst->create<ast::SampledTexture>(depth->dim(), ctx.dst->create<ast::F32>()); |
| } else { |
| return CreateASTTypeFor(ctx, texture_type); |
| } |
| } |
| |
| /// Runs the transform |
| /// @returns the new program or SkipTransform if the transform is not required |
| ApplyResult Run() { |
| auto& sem = ctx.src->Sem(); |
| |
| // Remove all texture and sampler global variables. These will be replaced |
| // by combined samplers. |
| for (auto* global : ctx.src->AST().GlobalVariables()) { |
| auto* global_sem = sem.Get(global)->As<sem::GlobalVariable>(); |
| auto* type = sem.Get(global->type); |
| if (tint::IsAnyOf<type::Texture, type::Sampler>(type) && |
| !type->Is<type::StorageTexture>()) { |
| ctx.Remove(ctx.src->AST().GlobalDeclarations(), global); |
| } else if (global->HasBindingPoint()) { |
| auto binding_point = global_sem->BindingPoint(); |
| if (binding_point.group == 0 && binding_point.binding == 0) { |
| auto* attribute = |
| ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision); |
| ctx.InsertFront(global->attributes, attribute); |
| } |
| } |
| } |
| |
| // Rewrite all function signatures to use combined samplers, and remove |
| // separate textures & samplers. Create new combined globals where found. |
| ctx.ReplaceAll([&](const ast::Function* ast_fn) -> const ast::Function* { |
| if (auto* fn = sem.Get(ast_fn)) { |
| auto pairs = fn->TextureSamplerPairs(); |
| if (pairs.IsEmpty()) { |
| return nullptr; |
| } |
| utils::Vector<const ast::Parameter*, 8> params; |
| for (auto pair : fn->TextureSamplerPairs()) { |
| const sem::Variable* texture_var = pair.first; |
| const sem::Variable* sampler_var = pair.second; |
| std::string name = |
| ctx.src->Symbols().NameFor(texture_var->Declaration()->symbol); |
| if (sampler_var) { |
| name += |
| "_" + ctx.src->Symbols().NameFor(sampler_var->Declaration()->symbol); |
| } |
| if (IsGlobal(pair)) { |
| // Both texture and sampler are global; add a new global variable |
| // to represent the combined sampler (if not already created). |
| utils::GetOrCreate(global_combined_texture_samplers_, pair, [&] { |
| return CreateCombinedGlobal(texture_var, sampler_var, name); |
| }); |
| } else { |
| // Either texture or sampler (or both) is a function parameter; |
| // add a new function parameter to represent the combined sampler. |
| auto* type = CreateCombinedASTTypeFor(texture_var, sampler_var); |
| auto* var = ctx.dst->Param(ctx.dst->Symbols().New(name), type); |
| params.Push(var); |
| function_combined_texture_samplers_[fn][pair] = var; |
| } |
| } |
| // Filter out separate textures and samplers from the original |
| // function signature. |
| for (auto* param : fn->Parameters()) { |
| if (!param->Type()->IsAnyOf<type::Texture, type::Sampler>()) { |
| params.Push(ctx.Clone(param->Declaration())); |
| } |
| } |
| // Create a new function signature that differs only in the parameter |
| // list. |
| auto symbol = ctx.Clone(ast_fn->symbol); |
| auto* return_type = ctx.Clone(ast_fn->return_type); |
| auto* body = ctx.Clone(ast_fn->body); |
| auto attributes = ctx.Clone(ast_fn->attributes); |
| auto return_type_attributes = ctx.Clone(ast_fn->return_type_attributes); |
| return ctx.dst->create<ast::Function>(symbol, params, return_type, body, |
| std::move(attributes), |
| std::move(return_type_attributes)); |
| } |
| return nullptr; |
| }); |
| |
| // Replace all function call expressions containing texture or |
| // sampler parameters to use the current function's combined samplers or |
| // the combined global samplers, as appropriate. |
| ctx.ReplaceAll([&](const ast::CallExpression* expr) -> const ast::Expression* { |
| if (auto* call = sem.Get(expr)->UnwrapMaterialize()->As<sem::Call>()) { |
| utils::Vector<const ast::Expression*, 8> args; |
| // Replace all texture builtin calls. |
| if (auto* builtin = call->Target()->As<sem::Builtin>()) { |
| const auto& signature = builtin->Signature(); |
| auto sampler_index = signature.IndexOf(sem::ParameterUsage::kSampler); |
| auto texture_index = signature.IndexOf(sem::ParameterUsage::kTexture); |
| if (texture_index == -1) { |
| return nullptr; |
| } |
| const sem::Expression* texture = |
| call->Arguments()[static_cast<size_t>(texture_index)]; |
| // We don't want to combine storage textures with anything, since |
| // they never have associated samplers in GLSL. |
| if (texture->Type()->UnwrapRef()->Is<type::StorageTexture>()) { |
| return nullptr; |
| } |
| const sem::Expression* sampler = |
| sampler_index != -1 ? call->Arguments()[static_cast<size_t>(sampler_index)] |
| : nullptr; |
| auto* texture_var = texture->As<sem::VariableUser>()->Variable(); |
| auto* sampler_var = |
| sampler ? sampler->As<sem::VariableUser>()->Variable() : nullptr; |
| sem::VariablePair new_pair(texture_var, sampler_var); |
| for (auto* arg : expr->args) { |
| auto* type = ctx.src->TypeOf(arg)->UnwrapRef(); |
| if (type->Is<type::Texture>()) { |
| const ast::Variable* var = |
| IsGlobal(new_pair) |
| ? global_combined_texture_samplers_[new_pair] |
| : function_combined_texture_samplers_[call->Stmt()->Function()] |
| [new_pair]; |
| args.Push(ctx.dst->Expr(var->symbol)); |
| } else if (auto* sampler_type = type->As<type::Sampler>()) { |
| ast::SamplerKind kind = sampler_type->kind(); |
| int index = (kind == ast::SamplerKind::kSampler) ? 0 : 1; |
| const ast::Variable*& p = placeholder_samplers_[index]; |
| if (!p) { |
| p = CreatePlaceholder(kind); |
| } |
| args.Push(ctx.dst->Expr(p->symbol)); |
| } else { |
| args.Push(ctx.Clone(arg)); |
| } |
| } |
| const ast::Expression* value = |
| ctx.dst->Call(ctx.Clone(expr->target.name), args); |
| if (builtin->Type() == sem::BuiltinType::kTextureLoad && |
| texture_var->Type()->UnwrapRef()->Is<type::DepthTexture>() && |
| !call->Stmt()->Declaration()->Is<ast::CallStatement>()) { |
| value = ctx.dst->MemberAccessor(value, "x"); |
| } |
| return value; |
| } |
| // Replace all function calls. |
| if (auto* callee = call->Target()->As<sem::Function>()) { |
| for (auto pair : callee->TextureSamplerPairs()) { |
| // Global pairs used by the callee do not require a function |
| // parameter at the call site. |
| if (IsGlobal(pair)) { |
| continue; |
| } |
| const sem::Variable* texture_var = pair.first; |
| const sem::Variable* sampler_var = pair.second; |
| if (auto* param = texture_var->As<sem::Parameter>()) { |
| const sem::Expression* texture = call->Arguments()[param->Index()]; |
| texture_var = texture->As<sem::VariableUser>()->Variable(); |
| } |
| if (sampler_var) { |
| if (auto* param = sampler_var->As<sem::Parameter>()) { |
| const sem::Expression* sampler = call->Arguments()[param->Index()]; |
| sampler_var = sampler->As<sem::VariableUser>()->Variable(); |
| } |
| } |
| sem::VariablePair new_pair(texture_var, sampler_var); |
| // If both texture and sampler are (now) global, pass that |
| // global variable to the callee. Otherwise use the caller's |
| // function parameter for this pair. |
| const ast::Variable* var = |
| IsGlobal(new_pair) |
| ? global_combined_texture_samplers_[new_pair] |
| : function_combined_texture_samplers_[call->Stmt()->Function()] |
| [new_pair]; |
| auto* arg = ctx.dst->Expr(var->symbol); |
| args.Push(arg); |
| } |
| // Append all of the remaining non-texture and non-sampler |
| // parameters. |
| for (auto* arg : expr->args) { |
| if (!ctx.src->TypeOf(arg) |
| ->UnwrapRef() |
| ->IsAnyOf<type::Texture, type::Sampler>()) { |
| args.Push(ctx.Clone(arg)); |
| } |
| } |
| return ctx.dst->Call(ctx.Clone(expr->target.name), args); |
| } |
| } |
| return nullptr; |
| }); |
| |
| ctx.Clone(); |
| return Program(std::move(b)); |
| } |
| }; |
| |
| CombineSamplers::CombineSamplers() = default; |
| |
| CombineSamplers::~CombineSamplers() = default; |
| |
| Transform::ApplyResult CombineSamplers::Apply(const Program* src, |
| const DataMap& inputs, |
| DataMap&) const { |
| auto* binding_info = inputs.Get<BindingInfo>(); |
| if (!binding_info) { |
| ProgramBuilder b; |
| b.Diagnostics().add_error(diag::System::Transform, |
| "missing transform data for " + std::string(TypeInfo().name)); |
| return Program(std::move(b)); |
| } |
| |
| return State(src, binding_info).Run(); |
| } |
| |
| } // namespace tint::transform |