• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Tint Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/transform/multiplanar_external_texture.h"
16 
17 #include <string>
18 #include <vector>
19 
20 #include "src/ast/function.h"
21 #include "src/program_builder.h"
22 #include "src/sem/call.h"
23 #include "src/sem/function.h"
24 #include "src/sem/variable.h"
25 
26 TINT_INSTANTIATE_TYPEINFO(tint::transform::MultiplanarExternalTexture);
27 TINT_INSTANTIATE_TYPEINFO(
28     tint::transform::MultiplanarExternalTexture::NewBindingPoints);
29 
30 namespace tint {
31 namespace transform {
32 namespace {
33 
34 /// This struct stores symbols for new bindings created as a result of
35 /// transforming a texture_external instance.
36 struct NewBindingSymbols {
37   Symbol params;
38   Symbol plane_0;
39   Symbol plane_1;
40 };
41 }  // namespace
42 
43 /// State holds the current transform state
44 struct MultiplanarExternalTexture::State {
45   /// The clone context.
46   CloneContext& ctx;
47 
48   /// ProgramBuilder for the context
49   ProgramBuilder& b;
50 
51   /// Desination binding locations for the expanded texture_external provided as
52   /// input into the transform.
53   const NewBindingPoints* new_binding_points;
54 
55   /// Symbol for the ExternalTextureParams struct
56   Symbol params_struct_sym;
57 
58   /// Symbol for the textureLoadExternal function
59   Symbol texture_load_external_sym;
60 
61   /// Symbol for the textureSampleExternal function
62   Symbol texture_sample_external_sym;
63 
64   /// Storage for new bindings that have been created corresponding to an
65   /// original texture_external binding.
66   std::unordered_map<Symbol, NewBindingSymbols> new_binding_symbols;
67 
68   /// Constructor
69   /// @param context the clone
70   /// @param newBindingPoints the input destination binding locations for the
71   /// expanded texture_external
Statetint::transform::MultiplanarExternalTexture::State72   State(CloneContext& context, const NewBindingPoints* newBindingPoints)
73       : ctx(context), b(*context.dst), new_binding_points(newBindingPoints) {}
74 
75   /// Processes the module
Processtint::transform::MultiplanarExternalTexture::State76   void Process() {
77     auto& sem = ctx.src->Sem();
78 
79     // For each texture_external binding, we replace it with a texture_2d<f32>
80     // binding and create two additional bindings (one texture_2d<f32> to
81     // represent the secondary plane and one uniform buffer for the
82     // ExternalTextureParams struct).
83     ctx.ReplaceAll([&](const ast::Variable* var) -> const ast::Variable* {
84       if (!::tint::Is<ast::ExternalTexture>(var->type)) {
85         return nullptr;
86       }
87 
88       // If the decorations are empty, then this must be a texture_external
89       // passed as a function parameter. These variables are transformed
90       // elsewhere.
91       if (var->decorations.empty()) {
92         return nullptr;
93       }
94 
95       // If we find a texture_external binding, we know we must emit the
96       // ExternalTextureParams struct.
97       if (!params_struct_sym.IsValid()) {
98         createExtTexParamsStruct();
99       }
100 
101       // The binding points for the newly introduced bindings must have been
102       // provided to this transform. We fetch the new binding points by
103       // providing the original texture_external binding points into the
104       // passed map.
105       BindingPoint bp = {var->BindingPoint().group->value,
106                          var->BindingPoint().binding->value};
107 
108       BindingsMap::const_iterator it =
109           new_binding_points->bindings_map.find(bp);
110       if (it == new_binding_points->bindings_map.end()) {
111         b.Diagnostics().add_error(
112             diag::System::Transform,
113             "missing new binding points for texture_external at binding {" +
114                 std::to_string(bp.group) + "," + std::to_string(bp.binding) +
115                 "}");
116         return nullptr;
117       }
118 
119       BindingPoints bps = it->second;
120 
121       // Symbols for the newly created bindings must be saved so they can be
122       // passed as parameters later. These are placed in a map and keyed by
123       // the source symbol associated with the texture_external binding that
124       // corresponds with the new destination bindings.
125       // NewBindingSymbols new_binding_syms;
126       auto& syms = new_binding_symbols[var->symbol];
127       syms.plane_0 = ctx.Clone(var->symbol);
128       syms.plane_1 = b.Symbols().New("ext_tex_plane_1");
129       b.Global(syms.plane_1,
130                b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
131                b.GroupAndBinding(bps.plane_1.group, bps.plane_1.binding));
132       syms.params = b.Symbols().New("ext_tex_params");
133       b.Global(syms.params, b.ty.type_name("ExternalTextureParams"),
134                ast::StorageClass::kUniform,
135                b.GroupAndBinding(bps.params.group, bps.params.binding));
136 
137       // Replace the original texture_external binding with a texture_2d<f32>
138       // binding.
139       ast::DecorationList cloned_decorations = ctx.Clone(var->decorations);
140       const ast::Expression* cloned_constructor = ctx.Clone(var->constructor);
141 
142       return b.Var(syms.plane_0,
143                    b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32()),
144                    cloned_constructor, cloned_decorations);
145     });
146 
147     // Transform the original textureLoad and textureSampleLevel calls into
148     // textureLoadExternal and textureSampleExternal calls.
149     ctx.ReplaceAll(
150         [&](const ast::CallExpression* expr) -> const ast::CallExpression* {
151           auto* intrinsic = sem.Get(expr)->Target()->As<sem::Intrinsic>();
152 
153           if (intrinsic && !intrinsic->Parameters().empty() &&
154               intrinsic->Parameters()[0]->Type()->Is<sem::ExternalTexture>() &&
155               intrinsic->Type() != sem::IntrinsicType::kTextureDimensions) {
156             auto it = new_binding_symbols.find(
157                 expr->args[0]->As<ast::IdentifierExpression>()->symbol);
158             if (it == new_binding_symbols.end()) {
159               // If valid new binding locations were not provided earlier, we
160               // would have been unable to create these symbols. An error
161               // message was emitted earlier, so just return early to avoid
162               // internal compiler errors and retain a clean error message.
163               return nullptr;
164             }
165             auto& syms = it->second;
166 
167             if (intrinsic->Type() == sem::IntrinsicType::kTextureLoad) {
168               return createTexLdExt(expr, syms);
169             }
170 
171             if (intrinsic->Type() == sem::IntrinsicType::kTextureSampleLevel) {
172               return createTexSmpExt(expr, syms);
173             }
174 
175           } else if (sem.Get(expr)->Target()->Is<sem::Function>()) {
176             // The call expression may be to a user-defined function that
177             // contains a texture_external parameter. These need to be expanded
178             // out to multiple plane textures and the texture parameters
179             // structure.
180             for (const ast::Expression* arg : expr->args) {
181               if (auto* id_expr = arg->As<ast::IdentifierExpression>()) {
182                 // Check if a parameter is a texture_external by trying to find
183                 // it in the transform state.
184                 auto it = new_binding_symbols.find(id_expr->symbol);
185                 if (it != new_binding_symbols.end()) {
186                   auto& syms = it->second;
187                   // When we find a texture_external, we must unpack it into its
188                   // components.
189                   ctx.Replace(id_expr, b.Expr(syms.plane_0));
190                   ctx.InsertAfter(expr->args, id_expr, b.Expr(syms.plane_1));
191                   ctx.InsertAfter(expr->args, id_expr, b.Expr(syms.params));
192                 }
193               }
194             }
195           }
196 
197           return nullptr;
198         });
199 
200     // We must update all the texture_external parameters for user declared
201     // functions.
202     ctx.ReplaceAll([&](const ast::Function* fn) -> const ast::Function* {
203       for (const ast::Variable* param : fn->params) {
204         if (::tint::Is<ast::ExternalTexture>(param->type)) {
205           // If we find a texture_external, we must ensure the
206           // ExternalTextureParams struct exists.
207           if (!params_struct_sym.IsValid()) {
208             createExtTexParamsStruct();
209           }
210           // When a texture_external is found, we insert all components
211           // the texture_external into the parameter list. We must also place
212           // the new symbols into the transform state so they can be used when
213           // transforming function calls.
214           auto& syms = new_binding_symbols[param->symbol];
215           syms.plane_0 = ctx.Clone(param->symbol);
216           syms.plane_1 = b.Symbols().New("ext_tex_plane_1");
217           syms.params = b.Symbols().New("ext_tex_params");
218           auto tex2d_f32 = [&] {
219             return b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32());
220           };
221           ctx.Replace(param, b.Param(syms.plane_0, tex2d_f32()));
222           ctx.InsertAfter(fn->params, param,
223                           b.Param(syms.plane_1, tex2d_f32()));
224           ctx.InsertAfter(
225               fn->params, param,
226               b.Param(syms.params, b.ty.type_name(params_struct_sym)));
227         }
228       }
229       // Clone the function. This will use the Replace() and InsertAfter() calls
230       // above.
231       return nullptr;
232     });
233   }
234 
235   /// Creates the ExternalTextureParams struct.
createExtTexParamsStructtint::transform::MultiplanarExternalTexture::State236   void createExtTexParamsStruct() {
237     ast::StructMemberList member_list = {
238         b.Member("numPlanes", b.ty.u32()), b.Member("vr", b.ty.f32()),
239         b.Member("ug", b.ty.f32()), b.Member("vg", b.ty.f32()),
240         b.Member("ub", b.ty.f32())};
241 
242     params_struct_sym = b.Symbols().New("ExternalTextureParams");
243 
244     b.Structure(params_struct_sym, member_list,
245                 ast::DecorationList{b.StructBlock()});
246   }
247 
248   /// Constructs a StatementList containing all the statements making up the
249   /// bodies of the textureSampleExternal and textureLoadExternal functions.
250   /// @param callType determines which function body to generate
251   /// @returns a statement list that makes of the body of the chosen function
createTexFnExtStatementListtint::transform::MultiplanarExternalTexture::State252   ast::StatementList createTexFnExtStatementList(sem::IntrinsicType callType) {
253     using f32 = ProgramBuilder::f32;
254     const ast::CallExpression* single_plane_call;
255     const ast::CallExpression* plane_0_call;
256     const ast::CallExpression* plane_1_call;
257     if (callType == sem::IntrinsicType::kTextureSampleLevel) {
258       // textureSampleLevel(plane0, smp, coord.xy, 0.0);
259       single_plane_call =
260           b.Call("textureSampleLevel", "plane0", "smp", "coord", 0.0f);
261       // textureSampleLevel(plane0, smp, coord.xy, 0.0);
262       plane_0_call =
263           b.Call("textureSampleLevel", "plane0", "smp", "coord", 0.0f);
264       // textureSampleLevel(plane1, smp, coord.xy, 0.0);
265       plane_1_call =
266           b.Call("textureSampleLevel", "plane1", "smp", "coord", 0.0f);
267     } else if (callType == sem::IntrinsicType::kTextureLoad) {
268       // textureLoad(plane0, coords.xy, 0);
269       single_plane_call = b.Call("textureLoad", "plane0", "coord", 0);
270       // textureLoad(plane0, coords.xy, 0);
271       plane_0_call = b.Call("textureLoad", "plane0", "coord", 0);
272       // textureLoad(plane1, coords.xy, 0);
273       plane_1_call = b.Call("textureLoad", "plane1", "coord", 0);
274     }
275 
276     return {
277         // if (params.numPlanes == 1u) {
278         //    return singlePlaneCall
279         // }
280         b.If(b.create<ast::BinaryExpression>(
281                  ast::BinaryOp::kEqual, b.MemberAccessor("params", "numPlanes"),
282                  b.Expr(1u)),
283              b.Block(b.Return(single_plane_call))),
284         // let y = plane0Call.r - 0.0625;
285         b.Decl(b.Const("y", nullptr,
286                        b.Sub(b.MemberAccessor(plane_0_call, "r"), 0.0625f))),
287         // let uv = plane1Call.rg - 0.5;
288         b.Decl(b.Const("uv", nullptr,
289                        b.Sub(b.MemberAccessor(plane_1_call, "rg"), 0.5f))),
290         // let u = uv.x;
291         b.Decl(b.Const("u", nullptr, b.MemberAccessor("uv", "x"))),
292         // let v = uv.y;
293         b.Decl(b.Const("v", nullptr, b.MemberAccessor("uv", "y"))),
294         // let r = 1.164 * y + params.vr * v;
295         b.Decl(b.Const("r", nullptr,
296                        b.Add(b.Mul(1.164f, "y"),
297                              b.Mul(b.MemberAccessor("params", "vr"), "v")))),
298         // let g = 1.164 * y - params.ug * u - params.vg * v;
299         b.Decl(
300             b.Const("g", nullptr,
301                     b.Sub(b.Sub(b.Mul(1.164f, "y"),
302                                 b.Mul(b.MemberAccessor("params", "ug"), "u")),
303                           b.Mul(b.MemberAccessor("params", "vg"), "v")))),
304         // let b = 1.164 * y + params.ub * u;
305         b.Decl(b.Const("b", nullptr,
306                        b.Add(b.Mul(1.164f, "y"),
307                              b.Mul(b.MemberAccessor("params", "ub"), "u")))),
308         // return vec4<f32>(r, g, b, 1.0);
309         b.Return(b.vec4<f32>("r", "g", "b", 1.0f)),
310     };
311   }
312 
313   /// Creates the textureSampleExternal function if needed and returns a call
314   /// expression to it.
315   /// @param expr the call expression being transformed
316   /// @param syms the expanded symbols to be used in the new call
317   /// @returns a call expression to textureSampleExternal
createTexSmpExttint::transform::MultiplanarExternalTexture::State318   const ast::CallExpression* createTexSmpExt(const ast::CallExpression* expr,
319                                              NewBindingSymbols syms) {
320     ast::ExpressionList params;
321     const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]);
322 
323     if (expr->args.size() != 3) {
324       TINT_ICE(Transform, b.Diagnostics())
325           << "expected textureSampleLevel call with a "
326              "texture_external to have 3 parameters, found "
327           << expr->args.size() << " parameters";
328     }
329 
330     if (!texture_sample_external_sym.IsValid()) {
331       texture_sample_external_sym = b.Symbols().New("textureSampleExternal");
332 
333       // Emit the textureSampleExternal function.
334       ast::VariableList varList = {
335           b.Param("plane0",
336                   b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
337           b.Param("plane1",
338                   b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
339           b.Param("smp", b.ty.sampler(ast::SamplerKind::kSampler)),
340           b.Param("coord", b.ty.vec2(b.ty.f32())),
341           b.Param("params", b.ty.type_name(params_struct_sym))};
342 
343       ast::StatementList statementList =
344           createTexFnExtStatementList(sem::IntrinsicType::kTextureSampleLevel);
345 
346       b.Func(texture_sample_external_sym, varList, b.ty.vec4(b.ty.f32()),
347              statementList, {});
348     }
349 
350     const ast::IdentifierExpression* exp = b.Expr(texture_sample_external_sym);
351     params = {plane_0_binding_param, b.Expr(syms.plane_1),
352               ctx.Clone(expr->args[1]), ctx.Clone(expr->args[2]),
353               b.Expr(syms.params)};
354     return b.Call(exp, params);
355   }
356 
357   /// Creates the textureLoadExternal function if needed and returns a call
358   /// expression to it.
359   /// @param expr the call expression being transformed
360   /// @param syms the expanded symbols to be used in the new call
361   /// @returns a call expression to textureLoadExternal
createTexLdExttint::transform::MultiplanarExternalTexture::State362   const ast::CallExpression* createTexLdExt(const ast::CallExpression* expr,
363                                             NewBindingSymbols syms) {
364     ast::ExpressionList params;
365     const ast::Expression* plane_0_binding_param = ctx.Clone(expr->args[0]);
366 
367     if (expr->args.size() != 2) {
368       TINT_ICE(Transform, b.Diagnostics())
369           << "expected textureLoad call with a texture_external "
370              "to have 2 parameters, found "
371           << expr->args.size() << " parameters";
372     }
373 
374     if (!texture_load_external_sym.IsValid()) {
375       texture_load_external_sym = b.Symbols().New("textureLoadExternal");
376 
377       // Emit the textureLoadExternal function.
378       ast::VariableList var_list = {
379           b.Param("plane0",
380                   b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
381           b.Param("plane1",
382                   b.ty.sampled_texture(ast::TextureDimension::k2d, b.ty.f32())),
383           b.Param("coord", b.ty.vec2(b.ty.i32())),
384           b.Param("params", b.ty.type_name(params_struct_sym))};
385 
386       ast::StatementList statement_list =
387           createTexFnExtStatementList(sem::IntrinsicType::kTextureLoad);
388 
389       b.Func(texture_load_external_sym, var_list, b.ty.vec4(b.ty.f32()),
390              statement_list, {});
391     }
392 
393     const ast::IdentifierExpression* exp = b.Expr(texture_load_external_sym);
394     params = {plane_0_binding_param, b.Expr(syms.plane_1),
395               ctx.Clone(expr->args[1]), b.Expr(syms.params)};
396     return b.Call(exp, params);
397   }
398 };
399 
NewBindingPoints(BindingsMap inputBindingsMap)400 MultiplanarExternalTexture::NewBindingPoints::NewBindingPoints(
401     BindingsMap inputBindingsMap)
402     : bindings_map(std::move(inputBindingsMap)) {}
403 MultiplanarExternalTexture::NewBindingPoints::~NewBindingPoints() = default;
404 
405 MultiplanarExternalTexture::MultiplanarExternalTexture() = default;
406 MultiplanarExternalTexture::~MultiplanarExternalTexture() = default;
407 
408 // Within this transform, an instance of a texture_external binding is unpacked
409 // into two texture_2d<f32> bindings representing two possible planes of a
410 // single texture and a uniform buffer binding representing a struct of
411 // parameters. Calls to textureLoad or textureSampleLevel that contain a
412 // texture_external parameter will be transformed into a newly generated version
413 // of the function, which can perform the desired operation on a single RGBA
414 // plane or on seperate Y and UV planes.
Run(CloneContext & ctx,const DataMap & inputs,DataMap &)415 void MultiplanarExternalTexture::Run(CloneContext& ctx,
416                                      const DataMap& inputs,
417                                      DataMap&) {
418   auto* new_binding_points = inputs.Get<NewBindingPoints>();
419 
420   if (!new_binding_points) {
421     ctx.dst->Diagnostics().add_error(
422         diag::System::Transform,
423         "missing new binding point data for " + std::string(TypeInfo().name));
424     return;
425   }
426 
427   State state(ctx, new_binding_points);
428 
429   state.Process();
430 
431   ctx.Clone();
432 }
433 
434 }  // namespace transform
435 }  // namespace tint
436