blob: 4f6838808e10e6ab1df47a5676b4ff7cfe5a4f4a [file] [log] [blame]
// 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