|  | // 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->UnwrapLoad()->As<sem::VariableUser>()->Variable(); | 
|  | auto* sampler_var = | 
|  | sampler ? sampler->UnwrapLoad()->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->UnwrapLoad()->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->UnwrapLoad()->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 |