| // Copyright 2021 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/multiplanar_external_texture.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "src/tint/ast/function.h" |
| #include "src/tint/program_builder.h" |
| #include "src/tint/sem/call.h" |
| #include "src/tint/sem/function.h" |
| #include "src/tint/sem/variable.h" |
| |
| TINT_INSTANTIATE_TYPEINFO(tint::transform::MultiplanarExternalTexture); |
| TINT_INSTANTIATE_TYPEINFO(tint::transform::MultiplanarExternalTexture::NewBindingPoints); |
| |
| using namespace tint::number_suffixes; // NOLINT |
| |
| namespace tint::transform { |
| namespace { |
| |
| /// This struct stores symbols for new bindings created as a result of |
| /// transforming a texture_external instance. |
| struct NewBindingSymbols { |
| Symbol params; |
| Symbol plane_0; |
| Symbol plane_1; |
| }; |
| } // namespace |
| |
| /// State holds the current transform state |
| struct MultiplanarExternalTexture::State { |
| /// The clone context. |
| CloneContext& ctx; |
| |
| /// ProgramBuilder for the context |
| ProgramBuilder& b; |
| |
| /// Destination binding locations for the expanded texture_external provided |
| /// as input into the transform. |
| const NewBindingPoints* new_binding_points; |
| |
| /// Symbol for the GammaTransferParams |
| Symbol gamma_transfer_struct_sym; |
| |
| /// Symbol for the ExternalTextureParams struct |
| Symbol params_struct_sym; |
| |
| /// Symbol for the textureLoadExternal function |
| Symbol texture_load_external_sym; |
| |
| /// Symbol for the textureSampleExternal function |
| Symbol texture_sample_external_sym; |
| |
| /// Symbol for the gammaCorrection function |
| Symbol gamma_correction_sym; |
| |
| /// Storage for new bindings that have been created corresponding to an |
| /// original texture_external binding. |
| std::unordered_map<const sem::Variable*, NewBindingSymbols> new_binding_symbols; |
| |
| /// Constructor |
| /// @param context the clone |
| /// @param newBindingPoints the input destination binding locations for the |
| /// expanded texture_external |
| State(CloneContext& context, const NewBindingPoints* newBindingPoints) |
| : ctx(context), b(*context.dst), new_binding_points(newBindingPoints) {} |
| |
| /// Processes the module |
| void Process() { |
| auto& sem = ctx.src->Sem(); |
| |
| // For each texture_external binding, we replace it with a texture_2d<f32> |
| // binding and create two additional bindings (one texture_2d<f32> to |
| // represent the secondary plane and one uniform buffer for the |
| // ExternalTextureParams struct). |
| for (auto* global : ctx.src->AST().GlobalVariables()) { |
| auto* sem_var = sem.Get(global); |
| if (!sem_var->Type()->UnwrapRef()->Is<sem::ExternalTexture>()) { |
| continue; |
| } |
| |
| // If the attributes are empty, then this must be a texture_external |
| // passed as a function parameter. These variables are transformed |
| // elsewhere. |
| if (global->attributes.IsEmpty()) { |
| continue; |
| } |
| |
| // If we find a texture_external binding, we know we must emit the |
| // ExternalTextureParams struct. |
| if (!params_struct_sym.IsValid()) { |
| createExtTexParamsStructs(); |
| } |
| |
| // The binding points for the newly introduced bindings must have been |
| // provided to this transform. We fetch the new binding points by |
| // providing the original texture_external binding points into the |
| // passed map. |
| BindingPoint bp = {global->BindingPoint().group->value, |
| global->BindingPoint().binding->value}; |
| |
| BindingsMap::const_iterator it = new_binding_points->bindings_map.find(bp); |
| if (it == new_binding_points->bindings_map.end()) { |
| b.Diagnostics().add_error( |
| diag::System::Transform, |
| "missing new binding points for texture_external at binding {" + |
| std::to_string(bp.group) + "," + std::to_string(bp.binding) + "}"); |
| continue; |
| } |
| |
| BindingPoints bps = it->second; |
| |
| // Symbols for the newly created bindings must be saved so they can be |
| // passed as parameters later. These are placed in a map and keyed by |
| // the source symbol associated with the texture_external binding that |
| // corresponds with the new destination bindings. |
| // NewBindingSymbols new_binding_syms; |
| auto& syms = new_binding_symbols[sem_var]; |
| syms.plane_0 = ctx.Clone(global->symbol); |
| syms.plane_1 = b.Symbols().New("ext_tex_plane_1"); |
| b.GlobalVar(syms.plane_1, b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()), |
| b.GroupAndBinding(bps.plane_1.group, bps.plane_1.binding)); |
| syms.params = b.Symbols().New("ext_tex_params"); |
| b.GlobalVar(syms.params, b.ty.type_name("ExternalTextureParams"), |
| ast::StorageClass::kUniform, |
| b.GroupAndBinding(bps.params.group, bps.params.binding)); |
| |
| // Replace the original texture_external binding with a texture_2d<f32> |
| // binding. |
| auto cloned_attributes = ctx.Clone(global->attributes); |
| const ast::Expression* cloned_constructor = ctx.Clone(global->constructor); |
| |
| auto* replacement = |
| b.Var(syms.plane_0, b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()), |
| cloned_constructor, cloned_attributes); |
| ctx.Replace(global, replacement); |
| } |
| |
| // We must update all the texture_external parameters for user declared |
| // functions. |
| for (auto* fn : ctx.src->AST().Functions()) { |
| for (const ast::Variable* param : fn->params) { |
| if (auto* sem_var = sem.Get(param)) { |
| if (!sem_var->Type()->UnwrapRef()->Is<sem::ExternalTexture>()) { |
| continue; |
| } |
| // If we find a texture_external, we must ensure the |
| // ExternalTextureParams struct exists. |
| if (!params_struct_sym.IsValid()) { |
| createExtTexParamsStructs(); |
| } |
| // When a texture_external is found, we insert all components |
| // the texture_external into the parameter list. We must also place |
| // the new symbols into the transform state so they can be used when |
| // transforming function calls. |
| auto& syms = new_binding_symbols[sem_var]; |
| syms.plane_0 = ctx.Clone(param->symbol); |
| syms.plane_1 = b.Symbols().New("ext_tex_plane_1"); |
| syms.params = b.Symbols().New("ext_tex_params"); |
| auto tex2d_f32 = [&] { |
| return b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()); |
| }; |
| ctx.Replace(param, b.Param(syms.plane_0, tex2d_f32())); |
| ctx.InsertAfter(fn->params, param, b.Param(syms.plane_1, tex2d_f32())); |
| ctx.InsertAfter(fn->params, param, |
| b.Param(syms.params, b.ty.type_name(params_struct_sym))); |
| } |
| } |
| } |
| |
| // Transform the original textureLoad and textureSampleLevel calls into |
| // textureLoadExternal and textureSampleExternal calls. |
| ctx.ReplaceAll([&](const ast::CallExpression* expr) -> const ast::CallExpression* { |
| auto* call = sem.Get(expr)->UnwrapMaterialize()->As<sem::Call>(); |
| auto* builtin = call->Target()->As<sem::Builtin>(); |
| |
| if (builtin && !builtin->Parameters().IsEmpty() && |
| builtin->Parameters()[0]->Type()->Is<sem::ExternalTexture>() && |
| builtin->Type() != sem::BuiltinType::kTextureDimensions) { |
| if (auto* var_user = sem.Get<sem::VariableUser>(expr->args[0])) { |
| auto it = new_binding_symbols.find(var_user->Variable()); |
| if (it == new_binding_symbols.end()) { |
| // If valid new binding locations were not provided earlier, we |
| // would have been unable to create these symbols. An error |
| // message was emitted earlier, so just return early to avoid |
| // internal compiler errors and retain a clean error message. |
| return nullptr; |
| } |
| auto& syms = it->second; |
| |
| if (builtin->Type() == sem::BuiltinType::kTextureLoad) { |
| return createTexLdExt(expr, syms); |
| } |
| |
| if (builtin->Type() == sem::BuiltinType::kTextureSampleLevel) { |
| return createTexSmpExt(expr, syms); |
| } |
| } |
| |
| } else if (call->Target()->Is<sem::Function>()) { |
| // The call expression may be to a user-defined function that |
| // contains a texture_external parameter. These need to be expanded |
| // out to multiple plane textures and the texture parameters |
| // structure. |
| for (auto* arg : expr->args) { |
| if (auto* var_user = sem.Get<sem::VariableUser>(arg)) { |
| // Check if a parameter is a texture_external by trying to find |
| // it in the transform state. |
| auto it = new_binding_symbols.find(var_user->Variable()); |
| if (it != new_binding_symbols.end()) { |
| auto& syms = it->second; |
| // When we find a texture_external, we must unpack it into its |
| // components. |
| ctx.Replace(arg, b.Expr(syms.plane_0)); |
| ctx.InsertAfter(expr->args, arg, b.Expr(syms.plane_1)); |
| ctx.InsertAfter(expr->args, arg, b.Expr(syms.params)); |
| } |
| } |
| } |
| } |
| |
| return nullptr; |
| }); |
| } |
| |
| /// Creates the parameter structs associated with the transform. |
| void createExtTexParamsStructs() { |
| // Create GammaTransferParams struct. |
| utils::Vector gamma_transfer_member_list{ |
| b.Member("G", b.ty.f32()), b.Member("A", b.ty.f32()), b.Member("B", b.ty.f32()), |
| b.Member("C", b.ty.f32()), b.Member("D", b.ty.f32()), b.Member("E", b.ty.f32()), |
| b.Member("F", b.ty.f32()), b.Member("padding", b.ty.u32())}; |
| |
| gamma_transfer_struct_sym = b.Symbols().New("GammaTransferParams"); |
| |
| b.Structure(gamma_transfer_struct_sym, gamma_transfer_member_list); |
| |
| // Create ExternalTextureParams struct. |
| utils::Vector ext_tex_params_member_list{ |
| b.Member("numPlanes", b.ty.u32()), |
| b.Member("doYuvToRgbConversionOnly", b.ty.u32()), |
| b.Member("yuvToRgbConversionMatrix", b.ty.mat3x4(b.ty.f32())), |
| b.Member("gammaDecodeParams", b.ty.type_name("GammaTransferParams")), |
| b.Member("gammaEncodeParams", b.ty.type_name("GammaTransferParams")), |
| b.Member("gamutConversionMatrix", b.ty.mat3x3(b.ty.f32()))}; |
| |
| params_struct_sym = b.Symbols().New("ExternalTextureParams"); |
| |
| b.Structure(params_struct_sym, ext_tex_params_member_list); |
| } |
| |
| /// Creates the gammaCorrection function if needed and returns a call |
| /// expression to it. |
| void createGammaCorrectionFn() { |
| gamma_correction_sym = b.Symbols().New("gammaCorrection"); |
| |
| b.Func( |
| gamma_correction_sym, |
| utils::Vector{ |
| b.Param("v", b.ty.vec3<f32>()), |
| b.Param("params", b.ty.type_name(gamma_transfer_struct_sym)), |
| }, |
| b.ty.vec3<f32>(), |
| utils::Vector{ |
| // let cond = abs(v) < vec3(params.D); |
| b.Decl(b.Let( |
| "cond", nullptr, |
| b.LessThan(b.Call("abs", "v"), b.vec3<f32>(b.MemberAccessor("params", "D"))))), |
| // let t = sign(v) * ((params.C * abs(v)) + params.F); |
| b.Decl(b.Let("t", nullptr, |
| b.Mul(b.Call("sign", "v"), |
| b.Add(b.Mul(b.MemberAccessor("params", "C"), b.Call("abs", "v")), |
| b.MemberAccessor("params", "F"))))), |
| // let f = (sign(v) * pow(((params.A * abs(v)) + params.B), |
| // vec3(params.G))) + params.E; |
| b.Decl(b.Let("f", nullptr, |
| b.Mul(b.Call("sign", "v"), |
| b.Add(b.Call("pow", |
| b.Add(b.Mul(b.MemberAccessor("params", "A"), |
| b.Call("abs", "v")), |
| b.MemberAccessor("params", "B")), |
| b.vec3<f32>(b.MemberAccessor("params", "G"))), |
| b.MemberAccessor("params", "E"))))), |
| // return select(f, t, cond); |
| b.Return(b.Call("select", "f", "t", "cond")), |
| }); |
| } |
| |
| /// Constructs a StatementList containing all the statements making up the |
| /// bodies of the textureSampleExternal and textureLoadExternal functions. |
| /// @param call_type determines which function body to generate |
| /// @returns a statement list that makes of the body of the chosen function |
| auto createTexFnExtStatementList(sem::BuiltinType call_type) { |
| const ast::CallExpression* single_plane_call = nullptr; |
| const ast::CallExpression* plane_0_call = nullptr; |
| const ast::CallExpression* plane_1_call = nullptr; |
| if (call_type == sem::BuiltinType::kTextureSampleLevel) { |
| // textureSampleLevel(plane0, smp, coord.xy, 0.0); |
| single_plane_call = b.Call("textureSampleLevel", "plane0", "smp", "coord", 0_f); |
| // textureSampleLevel(plane0, smp, coord.xy, 0.0); |
| plane_0_call = b.Call("textureSampleLevel", "plane0", "smp", "coord", 0_f); |
| // textureSampleLevel(plane1, smp, coord.xy, 0.0); |
| plane_1_call = b.Call("textureSampleLevel", "plane1", "smp", "coord", 0_f); |
| } else if (call_type == sem::BuiltinType::kTextureLoad) { |
| // textureLoad(plane0, coords.xy, 0); |
| single_plane_call = b.Call("textureLoad", "plane0", "coord", 0_i); |
| // textureLoad(plane0, coords.xy, 0); |
| plane_0_call = b.Call("textureLoad", "plane0", "coord", 0_i); |
| // textureLoad(plane1, coords.xy, 0); |
| plane_1_call = b.Call("textureLoad", "plane1", "coord", 0_i); |
| } else { |
| TINT_ICE(Transform, b.Diagnostics()) << "unhandled builtin: " << call_type; |
| } |
| |
| return utils::Vector{ |
| // var color: vec3<f32>; |
| b.Decl(b.Var("color", b.ty.vec3(b.ty.f32()))), |
| // if ((params.numPlanes == 1u)) |
| b.If(b.create<ast::BinaryExpression>( |
| ast::BinaryOp::kEqual, b.MemberAccessor("params", "numPlanes"), b.Expr(1_u)), |
| b.Block( |
| // color = textureLoad(plane0, coord, 0).rgb; |
| b.Assign("color", b.MemberAccessor(single_plane_call, "rgb"))), |
| b.Else(b.Block( |
| // color = vec4<f32>(plane_0_call.r, plane_1_call.rg, 1.0) * |
| // params.yuvToRgbConversionMatrix; |
| b.Assign("color", |
| b.Mul(b.vec4<f32>(b.MemberAccessor(plane_0_call, "r"), |
| b.MemberAccessor(plane_1_call, "rg"), 1_f), |
| b.MemberAccessor("params", "yuvToRgbConversionMatrix")))))), |
| // if (params.doYuvToRgbConversionOnly == 0u) |
| b.If(b.create<ast::BinaryExpression>( |
| ast::BinaryOp::kEqual, b.MemberAccessor("params", "doYuvToRgbConversionOnly"), |
| b.Expr(0_u)), |
| b.Block( |
| // color = gammaConversion(color, gammaDecodeParams); |
| b.Assign("color", b.Call("gammaCorrection", "color", |
| b.MemberAccessor("params", "gammaDecodeParams"))), |
| // color = (params.gamutConversionMatrix * color); |
| b.Assign("color", |
| b.Mul(b.MemberAccessor("params", "gamutConversionMatrix"), "color")), |
| // color = gammaConversion(color, gammaEncodeParams); |
| b.Assign("color", b.Call("gammaCorrection", "color", |
| b.MemberAccessor("params", "gammaEncodeParams"))))), |
| // return vec4<f32>(color, 1.f); |
| b.Return(b.vec4<f32>("color", 1_f))}; |
| } |
| |
| /// Creates the textureSampleExternal function if needed and returns a call |
| /// expression to it. |
| /// @param expr the call expression being transformed |
| /// @param syms the expanded symbols to be used in the new call |
| /// @returns a call expression to textureSampleExternal |
| const ast::CallExpression* createTexSmpExt(const ast::CallExpression* expr, |
| NewBindingSymbols syms) { |
| const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]); |
| |
| if (expr->args.Length() != 3) { |
| TINT_ICE(Transform, b.Diagnostics()) << "expected textureSampleLevel call with a " |
| "texture_external to have 3 parameters, found " |
| << expr->args.Length() << " parameters"; |
| } |
| |
| // TextureSampleExternal calls the gammaCorrection function, so ensure it |
| // exists. |
| if (!gamma_correction_sym.IsValid()) { |
| createGammaCorrectionFn(); |
| } |
| |
| if (!texture_sample_external_sym.IsValid()) { |
| texture_sample_external_sym = b.Symbols().New("textureSampleExternal"); |
| |
| // Emit the textureSampleExternal function. |
| b.Func( |
| texture_sample_external_sym, |
| utils::Vector{ |
| b.Param("plane0", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())), |
| b.Param("plane1", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())), |
| b.Param("smp", b.ty.sampler(ast::SamplerKind::kSampler)), |
| b.Param("coord", b.ty.vec2(b.ty.f32())), |
| b.Param("params", b.ty.type_name(params_struct_sym)), |
| }, |
| b.ty.vec4(b.ty.f32()), |
| utils::Vector{ |
| createTexFnExtStatementList(sem::BuiltinType::kTextureSampleLevel), |
| }); |
| } |
| |
| const ast::IdentifierExpression* exp = b.Expr(texture_sample_external_sym); |
| return b.Call(exp, utils::Vector{ |
| plane_0_binding_param, |
| b.Expr(syms.plane_1), |
| ctx.Clone(expr->args[1]), |
| ctx.Clone(expr->args[2]), |
| b.Expr(syms.params), |
| }); |
| } |
| |
| /// Creates the textureLoadExternal function if needed and returns a call |
| /// expression to it. |
| /// @param expr the call expression being transformed |
| /// @param syms the expanded symbols to be used in the new call |
| /// @returns a call expression to textureLoadExternal |
| const ast::CallExpression* createTexLdExt(const ast::CallExpression* expr, |
| NewBindingSymbols syms) { |
| const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]); |
| |
| if (expr->args.Length() != 2) { |
| TINT_ICE(Transform, b.Diagnostics()) |
| << "expected textureLoad call with a texture_external " |
| "to have 2 parameters, found " |
| << expr->args.Length() << " parameters"; |
| } |
| |
| // TextureLoadExternal calls the gammaCorrection function, so ensure it |
| // exists. |
| if (!gamma_correction_sym.IsValid()) { |
| createGammaCorrectionFn(); |
| } |
| |
| if (!texture_load_external_sym.IsValid()) { |
| texture_load_external_sym = b.Symbols().New("textureLoadExternal"); |
| |
| // Emit the textureLoadExternal function. |
| b.Func( |
| texture_load_external_sym, |
| utils::Vector{ |
| b.Param("plane0", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())), |
| b.Param("plane1", b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())), |
| b.Param("coord", b.ty.vec2(b.ty.i32())), |
| b.Param("params", b.ty.type_name(params_struct_sym)), |
| }, |
| b.ty.vec4(b.ty.f32()), |
| utils::Vector{ |
| createTexFnExtStatementList(sem::BuiltinType::kTextureLoad), |
| }); |
| } |
| |
| return b.Call(texture_load_external_sym, plane_0_binding_param, syms.plane_1, |
| ctx.Clone(expr->args[1]), syms.params); |
| } |
| }; |
| |
| MultiplanarExternalTexture::NewBindingPoints::NewBindingPoints(BindingsMap inputBindingsMap) |
| : bindings_map(std::move(inputBindingsMap)) {} |
| MultiplanarExternalTexture::NewBindingPoints::~NewBindingPoints() = default; |
| |
| MultiplanarExternalTexture::MultiplanarExternalTexture() = default; |
| MultiplanarExternalTexture::~MultiplanarExternalTexture() = default; |
| |
| bool MultiplanarExternalTexture::ShouldRun(const Program* program, const DataMap&) const { |
| for (auto* node : program->ASTNodes().Objects()) { |
| if (auto* ty = node->As<ast::Type>()) { |
| if (program->Sem().Get<sem::ExternalTexture>(ty)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| // Within this transform, an instance of a texture_external binding is unpacked |
| // into two texture_2d<f32> bindings representing two possible planes of a |
| // single texture and a uniform buffer binding representing a struct of |
| // parameters. Calls to textureLoad or textureSampleLevel that contain a |
| // texture_external parameter will be transformed into a newly generated version |
| // of the function, which can perform the desired operation on a single RGBA |
| // plane or on separate Y and UV planes. |
| void MultiplanarExternalTexture::Run(CloneContext& ctx, const DataMap& inputs, DataMap&) const { |
| auto* new_binding_points = inputs.Get<NewBindingPoints>(); |
| |
| if (!new_binding_points) { |
| ctx.dst->Diagnostics().add_error( |
| diag::System::Transform, |
| "missing new binding point data for " + std::string(TypeInfo().name)); |
| return; |
| } |
| |
| State state(ctx, new_binding_points); |
| |
| state.Process(); |
| |
| ctx.Clone(); |
| } |
| |
| } // namespace tint::transform |