|  | // 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/transform/multiplanar_external_texture.h" | 
|  |  | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "src/ast/function.h" | 
|  | #include "src/program_builder.h" | 
|  | #include "src/sem/call.h" | 
|  | #include "src/sem/variable.h" | 
|  |  | 
|  | TINT_INSTANTIATE_TYPEINFO(tint::transform::MultiplanarExternalTexture); | 
|  | TINT_INSTANTIATE_TYPEINFO( | 
|  | tint::transform::MultiplanarExternalTexture::NewBindingPoints); | 
|  |  | 
|  | namespace tint { | 
|  | namespace transform { | 
|  | namespace { | 
|  |  | 
|  | /// This struct stores symbols for new bindings created as a result of | 
|  | /// transforming a texture_external instance. | 
|  | struct NewBindingSymbols { | 
|  | Symbol ext_tex_params_binding_sym; | 
|  | Symbol ext_tex_plane_1_binding_sym; | 
|  | }; | 
|  | }  // namespace | 
|  |  | 
|  | /// State holds the current transform state | 
|  | struct MultiplanarExternalTexture::State { | 
|  | /// Symbol for the ExternalTextureParams struct | 
|  | Symbol external_texture_params_struct_sym; | 
|  |  | 
|  | /// Symbol for the textureLoadExternal function | 
|  | Symbol texture_load_external_sym; | 
|  |  | 
|  | /// Symbol for the textureSampleExternal function | 
|  | Symbol texture_sample_external_sym; | 
|  |  | 
|  | /// Storage for new bindings that have been created corresponding to an | 
|  | /// original texture_external binding. | 
|  | std::unordered_map<Symbol, NewBindingSymbols> new_binding_symbols; | 
|  | }; | 
|  |  | 
|  | MultiplanarExternalTexture::NewBindingPoints::NewBindingPoints( | 
|  | BindingsMap inputBindingsMap) | 
|  | : bindings_map(std::move(inputBindingsMap)) {} | 
|  | MultiplanarExternalTexture::NewBindingPoints::~NewBindingPoints() = default; | 
|  |  | 
|  | MultiplanarExternalTexture::MultiplanarExternalTexture() = default; | 
|  | MultiplanarExternalTexture::~MultiplanarExternalTexture() = default; | 
|  |  | 
|  | // 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 seperate Y and UV planes. | 
|  | void MultiplanarExternalTexture::Run(CloneContext& ctx, | 
|  | const DataMap& inputs, | 
|  | DataMap&) { | 
|  | State state; | 
|  | auto& b = *ctx.dst; | 
|  |  | 
|  | auto* new_binding_points = inputs.Get<NewBindingPoints>(); | 
|  |  | 
|  | if (!new_binding_points) { | 
|  | b.Diagnostics().add_error( | 
|  | diag::System::Transform, | 
|  | "missing new binding point data for " + std::string(TypeInfo().name)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | 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). | 
|  | ctx.ReplaceAll([&](const ast::Variable* var) -> const ast::Variable* { | 
|  | if (!::tint::Is<ast::ExternalTexture>(var->type)) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // If the decorations are empty, then this must be a texture_external being | 
|  | // passed as a function parameter. We need to unpack this into multiple | 
|  | // parameters - but this hasn't been implemented so produce an error. | 
|  | if (var->decorations.empty()) { | 
|  | b.Diagnostics().add_error( | 
|  | diag::System::Transform, | 
|  | "transforming a texture_external passed as a user-defined function " | 
|  | "parameter has not been implemented."); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // If we find a texture_external binding, we know we must emit the | 
|  | // ExternalTextureParams struct. | 
|  | if (!state.external_texture_params_struct_sym.IsValid()) { | 
|  | ast::StructMemberList member_list = { | 
|  | b.Member("numPlanes", b.ty.u32()), b.Member("vr", b.ty.f32()), | 
|  | b.Member("ug", b.ty.f32()), b.Member("vg", b.ty.f32()), | 
|  | b.Member("ub", b.ty.f32())}; | 
|  |  | 
|  | state.external_texture_params_struct_sym = | 
|  | b.Symbols().New("ExternalTextureParams"); | 
|  |  | 
|  | b.Structure(state.external_texture_params_struct_sym, member_list, | 
|  | ast::DecorationList{b.StructBlock()}); | 
|  | } | 
|  |  | 
|  | // 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 = {var->BindingPoint().group->value, | 
|  | var->BindingPoint().binding->value}; | 
|  | BindingPoints bps; | 
|  | 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) + | 
|  | "}"); | 
|  | return nullptr; | 
|  | } else { | 
|  | 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 symbol associated with the texture_external binding that | 
|  | // corresponds with the new bindings. | 
|  | NewBindingSymbols new_binding_syms; | 
|  | new_binding_syms.ext_tex_plane_1_binding_sym = | 
|  | b.Symbols().New("ext_tex_plane_1"); | 
|  | b.Global(new_binding_syms.ext_tex_plane_1_binding_sym, | 
|  | b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()), | 
|  | b.GroupAndBinding(bps.plane_1.group, bps.plane_1.binding)); | 
|  |  | 
|  | new_binding_syms.ext_tex_params_binding_sym = | 
|  | b.Symbols().New("ext_tex_params"); | 
|  | b.Global(new_binding_syms.ext_tex_params_binding_sym, | 
|  | 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_sym = ctx.Clone(var->symbol); | 
|  | ast::DecorationList cloned_decorations = ctx.Clone(var->decorations); | 
|  | const ast::Expression* cloned_constructor = ctx.Clone(var->constructor); | 
|  | state.new_binding_symbols[cloned_sym] = new_binding_syms; | 
|  |  | 
|  | return b.Var(cloned_sym, | 
|  | b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()), | 
|  | cloned_constructor, cloned_decorations); | 
|  | }); | 
|  |  | 
|  | // Transform the original textureLoad and textureSampleLevel calls into | 
|  | // textureLoadExternal and textureSampleExternal calls. | 
|  | ctx.ReplaceAll([&](const ast::CallExpression* expr) | 
|  | -> const ast::CallExpression* { | 
|  | auto* intrinsic = sem.Get(expr)->Target()->As<sem::Intrinsic>(); | 
|  |  | 
|  | if (!intrinsic || | 
|  | !intrinsic->Parameters()[0]->Type()->Is<sem::ExternalTexture>() || | 
|  | intrinsic->Parameters().empty() || | 
|  | intrinsic->Type() == sem::IntrinsicType::kTextureDimensions) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | const ast::Expression* ext_tex_plane_0_binding_param = | 
|  | ctx.Clone(expr->args[0]); | 
|  |  | 
|  | // Lookup the symbols for the new bindings using the symbol from the | 
|  | // original texture_external. | 
|  | Symbol ext_tex_plane_1_binding_sym = | 
|  | state | 
|  | .new_binding_symbols[ext_tex_plane_0_binding_param | 
|  | ->As<ast::IdentifierExpression>() | 
|  | ->symbol] | 
|  | .ext_tex_plane_1_binding_sym; | 
|  | Symbol ext_tex_params_binding_sym = | 
|  | state | 
|  | .new_binding_symbols[ext_tex_plane_0_binding_param | 
|  | ->As<ast::IdentifierExpression>() | 
|  | ->symbol] | 
|  | .ext_tex_params_binding_sym; | 
|  |  | 
|  | // 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. | 
|  | if (!ext_tex_plane_1_binding_sym.IsValid() || | 
|  | !ext_tex_params_binding_sym.IsValid()) { | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | ast::IdentifierExpression* exp; | 
|  | ast::ExpressionList params; | 
|  |  | 
|  | if (intrinsic->Type() == sem::IntrinsicType::kTextureLoad) { | 
|  | if (expr->args.size() != 2) { | 
|  | TINT_ICE(Transform, b.Diagnostics()) | 
|  | << "expected textureLoad call with a texture_external " | 
|  | "to " | 
|  | "have 2 parameters, found " | 
|  | << expr->args.size() << " parameters"; | 
|  | } | 
|  |  | 
|  | if (!state.texture_load_external_sym.IsValid()) { | 
|  | state.texture_load_external_sym = | 
|  | b.Symbols().New("textureLoadExternal"); | 
|  |  | 
|  | // Emit the textureLoadExternal function. | 
|  | ast::VariableList var_list = { | 
|  | 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(state.external_texture_params_struct_sym))}; | 
|  |  | 
|  | ast::StatementList statement_list = | 
|  | createTexFnExtStatementList(b, sem::IntrinsicType::kTextureLoad); | 
|  |  | 
|  | b.Func(state.texture_load_external_sym, var_list, b.ty.vec4(b.ty.f32()), | 
|  | statement_list, {}); | 
|  | } | 
|  |  | 
|  | exp = | 
|  | b.create<ast::IdentifierExpression>(state.texture_load_external_sym); | 
|  | params = {ext_tex_plane_0_binding_param, | 
|  | b.Expr(ext_tex_plane_1_binding_sym), ctx.Clone(expr->args[1]), | 
|  | b.Expr(ext_tex_params_binding_sym)}; | 
|  | } else if (intrinsic->Type() == sem::IntrinsicType::kTextureSampleLevel) { | 
|  | if (expr->args.size() != 3) { | 
|  | TINT_ICE(Transform, b.Diagnostics()) | 
|  | << "expected textureSampleLevel call with a " | 
|  | "texture_external to have 3 parameters, found " | 
|  | << expr->args.size() << " parameters"; | 
|  | } | 
|  |  | 
|  | if (!state.texture_sample_external_sym.IsValid()) { | 
|  | state.texture_sample_external_sym = | 
|  | b.Symbols().New("textureSampleExternal"); | 
|  |  | 
|  | // Emit the textureSampleExternal function. | 
|  | ast::VariableList varList = { | 
|  | 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(state.external_texture_params_struct_sym))}; | 
|  |  | 
|  | ast::StatementList statementList = createTexFnExtStatementList( | 
|  | b, sem::IntrinsicType::kTextureSampleLevel); | 
|  |  | 
|  | b.Func(state.texture_sample_external_sym, varList, | 
|  | b.ty.vec4(b.ty.f32()), statementList, {}); | 
|  | } | 
|  | exp = b.create<ast::IdentifierExpression>( | 
|  | state.texture_sample_external_sym); | 
|  | params = {ext_tex_plane_0_binding_param, | 
|  | b.Expr(ext_tex_plane_1_binding_sym), ctx.Clone(expr->args[1]), | 
|  | ctx.Clone(expr->args[2]), b.Expr(ext_tex_params_binding_sym)}; | 
|  | } | 
|  |  | 
|  | return b.Call(exp, params); | 
|  | }); | 
|  |  | 
|  | ctx.Clone(); | 
|  | } | 
|  |  | 
|  | // Constructs a StatementList containing all the statements making up the bodies | 
|  | // of the textureSampleExternal and textureLoadExternal functions. | 
|  | ast::StatementList MultiplanarExternalTexture::createTexFnExtStatementList( | 
|  | ProgramBuilder& b, | 
|  | sem::IntrinsicType callType) { | 
|  | using f32 = ProgramBuilder::f32; | 
|  | const ast::CallExpression* single_plane_call; | 
|  | const ast::CallExpression* plane_0_call; | 
|  | const ast::CallExpression* plane_1_call; | 
|  | if (callType == sem::IntrinsicType::kTextureSampleLevel) { | 
|  | // textureSampleLevel(plane0, smp, coord.xy, 0.0); | 
|  | single_plane_call = | 
|  | b.Call("textureSampleLevel", "plane0", "smp", "coord", 0.0f); | 
|  | // textureSampleLevel(plane0, smp, coord.xy, 0.0); | 
|  | plane_0_call = b.Call("textureSampleLevel", "plane0", "smp", "coord", 0.0f); | 
|  | // textureSampleLevel(plane1, smp, coord.xy, 0.0); | 
|  | plane_1_call = b.Call("textureSampleLevel", "plane1", "smp", "coord", 0.0f); | 
|  | } else if (callType == sem::IntrinsicType::kTextureLoad) { | 
|  | // textureLoad(plane0, coords.xy, 0); | 
|  | single_plane_call = b.Call("textureLoad", "plane0", "coord", 0); | 
|  | // textureLoad(plane0, coords.xy, 0); | 
|  | plane_0_call = b.Call("textureLoad", "plane0", "coord", 0); | 
|  | // textureLoad(plane1, coords.xy, 0); | 
|  | plane_1_call = b.Call("textureLoad", "plane1", "coord", 0); | 
|  | } | 
|  |  | 
|  | return { | 
|  | // if (params.numPlanes == 1u) { | 
|  | //    return singlePlaneCall | 
|  | // } | 
|  | b.If(b.create<ast::BinaryExpression>( | 
|  | ast::BinaryOp::kEqual, b.MemberAccessor("params", "numPlanes"), | 
|  | b.Expr(1u)), | 
|  | b.Block(b.Return(single_plane_call))), | 
|  | // let y = plane0Call.r - 0.0625; | 
|  | b.Decl(b.Const("y", nullptr, | 
|  | b.Sub(b.MemberAccessor(plane_0_call, "r"), 0.0625f))), | 
|  | // let uv = plane1Call.rg - 0.5; | 
|  | b.Decl(b.Const("uv", nullptr, | 
|  | b.Sub(b.MemberAccessor(plane_1_call, "rg"), 0.5f))), | 
|  | // let u = uv.x; | 
|  | b.Decl(b.Const("u", nullptr, b.MemberAccessor("uv", "x"))), | 
|  | // let v = uv.y; | 
|  | b.Decl(b.Const("v", nullptr, b.MemberAccessor("uv", "y"))), | 
|  | // let r = 1.164 * y + params.vr * v; | 
|  | b.Decl(b.Const("r", nullptr, | 
|  | b.Add(b.Mul(1.164f, "y"), | 
|  | b.Mul(b.MemberAccessor("params", "vr"), "v")))), | 
|  | // let g = 1.164 * y - params.ug * u - params.vg * v; | 
|  | b.Decl(b.Const("g", nullptr, | 
|  | b.Sub(b.Sub(b.Mul(1.164f, "y"), | 
|  | b.Mul(b.MemberAccessor("params", "ug"), "u")), | 
|  | b.Mul(b.MemberAccessor("params", "vg"), "v")))), | 
|  | // let b = 1.164 * y + params.ub * u; | 
|  | b.Decl(b.Const("b", nullptr, | 
|  | b.Add(b.Mul(1.164f, "y"), | 
|  | b.Mul(b.MemberAccessor("params", "ub"), "u")))), | 
|  | // return vec4<f32>(r, g, b, 1.0); | 
|  | b.Return(b.vec4<f32>("r", "g", "b", 1.0f)), | 
|  | }; | 
|  | } | 
|  |  | 
|  | }  // namespace transform | 
|  | }  // namespace tint |