From bf7ec8f70bce2c273b8bb3287f0edb8dfb46667b Mon Sep 17 00:00:00 2001 From: Brandon Jones Date: Wed, 17 Nov 2021 12:10:16 +0000 Subject: [PATCH] Add MultiplanarExternalTextureTransform and Tests Implements MultiplanarExternalTextureTransform to allow transforming a texture_external binding into two texture_2d bindings and a uniform buffer binding. Transforms textureSampleLevel and textureLoad calls with a texture_external parameter into custom functions that can handle both single-plane RGBA or bi-planar YUV. Includes tests. Bug: dawn:1082 Change-Id: Icb6d8b0f3773feca01c833171f07230c3531f3aa Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/68620 Kokoro: Kokoro Commit-Queue: Ben Clayton Reviewed-by: Ben Clayton --- src/BUILD.gn | 2 + src/CMakeLists.txt | 3 + src/transform/multiplanar_external_texture.cc | 356 +++++++++++++++ src/transform/multiplanar_external_texture.h | 105 +++++ .../multiplanar_external_texture_test.cc | 408 ++++++++++++++++++ test/BUILD.gn | 1 + 6 files changed, 875 insertions(+) create mode 100644 src/transform/multiplanar_external_texture.cc create mode 100644 src/transform/multiplanar_external_texture.h create mode 100644 src/transform/multiplanar_external_texture_test.cc diff --git a/src/BUILD.gn b/src/BUILD.gn index 8140244b65..fb8c8e7053 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -453,6 +453,8 @@ libtint_source_set("libtint_core_all_src") { "transform/manager.h", "transform/module_scope_var_to_entry_point_param.cc", "transform/module_scope_var_to_entry_point_param.h", + "transform/multiplanar_external_texture.cc", + "transform/multiplanar_external_texture.h", "transform/num_workgroups_from_uniform.cc", "transform/num_workgroups_from_uniform.h", "transform/pad_array_elements.cc", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 866edf9fd0..3bb6ae7f4e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -317,6 +317,8 @@ set(TINT_LIB_SRCS transform/manager.h transform/module_scope_var_to_entry_point_param.cc transform/module_scope_var_to_entry_point_param.h + transform/multiplanar_external_texture.cc + transform/multiplanar_external_texture.h transform/num_workgroups_from_uniform.cc transform/num_workgroups_from_uniform.h transform/pad_array_elements.cc @@ -954,6 +956,7 @@ if(${TINT_BUILD_TESTS}) transform/inline_pointer_lets_test.cc transform/loop_to_for_loop_test.cc transform/module_scope_var_to_entry_point_param_test.cc + transform/multiplanar_external_texture_test.cc transform/num_workgroups_from_uniform_test.cc transform/pad_array_elements_test.cc transform/promote_initializers_to_const_var_test.cc diff --git a/src/transform/multiplanar_external_texture.cc b/src/transform/multiplanar_external_texture.cc new file mode 100644 index 0000000000..599f593c43 --- /dev/null +++ b/src/transform/multiplanar_external_texture.cc @@ -0,0 +1,356 @@ +// 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 +#include + +#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 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 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(); + + 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 + // binding and create two additional bindings (one texture_2d 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(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 + // 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(); + + if (!intrinsic || + !intrinsic->Parameters()[0]->Type()->Is() || + 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() + ->symbol] + .ext_tex_plane_1_binding_sym; + Symbol ext_tex_params_binding_sym = + state + .new_binding_symbols[ext_tex_plane_0_binding_param + ->As() + ->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(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( + 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::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(r, g, b, 1.0); + b.Return(b.vec4("r", "g", "b", 1.0f)), + }; +} + +} // namespace transform +} // namespace tint diff --git a/src/transform/multiplanar_external_texture.h b/src/transform/multiplanar_external_texture.h new file mode 100644 index 0000000000..718dd3a105 --- /dev/null +++ b/src/transform/multiplanar_external_texture.h @@ -0,0 +1,105 @@ +// 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. + +#ifndef SRC_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_ +#define SRC_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_ + +#include +#include + +#include "src/ast/struct_member.h" +#include "src/sem/binding_point.h" +#include "src/sem/intrinsic_type.h" +#include "src/transform/transform.h" + +namespace tint { +namespace transform { + +/// BindingPoint is an alias to sem::BindingPoint +using BindingPoint = sem::BindingPoint; + +/// This struct identifies the binding groups and locations for new bindings to +/// use when transforming a texture_external instance. +struct BindingPoints { + /// The desired binding location of the texture_2d representing plane #1 when + /// a texture_external binding is expanded. + BindingPoint plane_1; + /// The desired binding location of the ExternalTextureParams uniform when a + /// texture_external binding is expanded. + BindingPoint params; +}; + +/// Within the MultiplanarExternalTexture transform, each instance of a +/// texture_external binding is unpacked into two texture_2d bindings +/// representing two possible planes of a 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. +class MultiplanarExternalTexture + : public Castable { + public: + /// BindingsMap is a map where the key is the binding location of a + /// texture_external and the value is a struct containing the desired + /// locations for new bindings expanded from the texture_external instance. + using BindingsMap = std::unordered_map; + + /// NewBindingPoints is consumed by the MultiplanarExternalTexture transform. + /// Data holds information about location of each texture_external binding and + /// which binding slots it should expand into. + struct NewBindingPoints : public Castable { + /// Constructor + /// @param bm a map to the new binding slots to use. + explicit NewBindingPoints(BindingsMap bm); + + /// Destructor + ~NewBindingPoints() override; + + /// A map of new binding points to use. + const BindingsMap bindings_map; + }; + + /// Constructor + MultiplanarExternalTexture(); + /// Destructor + ~MultiplanarExternalTexture() override; + + protected: + struct State; + + /// Runs the transform using the CloneContext built for transforming a + /// program. Run() is responsible for calling Clone() on the CloneContext. + /// @param ctx the CloneContext primed with the input program and + /// ProgramBuilder + /// @param inputs optional extra transform-specific input data + /// @param outputs optional extra transform-specific output data + void Run(CloneContext& ctx, const DataMap& inputs, DataMap& outputs) override; + + /// Creates the statement list for the TextureSampleExternal and + /// TextureLoadExternal functions. + /// @param b a reference to the ProgramBuilder associated with the destination + /// context + /// @param callType determines the kind of param list to emit (either + /// textureLoad or textureSampleLevel) + /// @returns a statement list that is used to create the TextureSampleExternal + /// and TextureLoadExternal functions. + ast::StatementList createTexFnExtStatementList(ProgramBuilder& b, + sem::IntrinsicType callType); +}; + +} // namespace transform +} // namespace tint + +#endif // SRC_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_ diff --git a/src/transform/multiplanar_external_texture_test.cc b/src/transform/multiplanar_external_texture_test.cc new file mode 100644 index 0000000000..489a437029 --- /dev/null +++ b/src/transform/multiplanar_external_texture_test.cc @@ -0,0 +1,408 @@ +// 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 "src/transform/test_helper.h" + +namespace tint { +namespace transform { +namespace { + +using MultiplanarExternalTextureTest = TransformTest; + +// Running the transform without passing in data for the new bindings should +// result in an error. +TEST_F(MultiplanarExternalTextureTest, ErrorNoPassedData) { + auto* src = R"( +[[group(0), binding(0)]] var s : sampler; +[[group(0), binding(1)]] var ext_tex : texture_external; + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return textureSampleLevel(ext_tex, s, coord.xy); +} +)"; + auto* expect = + R"(error: missing new binding point data for tint::transform::MultiplanarExternalTexture)"; + + auto got = Run(src); + EXPECT_EQ(expect, str(got)); +} + +// Running the transform with incorrect binding data should result in an error. +TEST_F(MultiplanarExternalTextureTest, ErrorIncorrectBindingPont) { + auto* src = R"( +[[group(0), binding(0)]] var s : sampler; +[[group(0), binding(1)]] var ext_tex : texture_external; + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return textureSampleLevel(ext_tex, s, coord.xy); +} +)"; + + auto* expect = + R"(error: missing new binding points for texture_external at binding {0,1})"; + + DataMap data; + // This bindings map specifies 0,0 as the location of the texture_external, + // which is incorrect. + data.Add( + MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}}); + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +// Tests that the transform works with a textureDimensions call. +TEST_F(MultiplanarExternalTextureTest, Dimensions) { + auto* src = R"( +[[group(0), binding(0)]] var ext_tex : texture_external; + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + var dim : vec2; + dim = textureDimensions(ext_tex); + return vec4(0.0, 0.0, 0.0, 0.0); +} +)"; + + auto* expect = R"( +[[block]] +struct ExternalTextureParams { + numPlanes : u32; + vr : f32; + ug : f32; + vg : f32; + ub : f32; +}; + +[[group(0), binding(1)]] var ext_tex_plane_1 : texture_2d; + +[[group(0), binding(2)]] var ext_tex_params : ExternalTextureParams; + +[[group(0), binding(0)]] var ext_tex : texture_2d; + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + var dim : vec2; + dim = textureDimensions(ext_tex); + return vec4(0.0, 0.0, 0.0, 0.0); +} +)"; + + DataMap data; + data.Add( + MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}}); + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +// Test that the transform works with a textureSampleLevel call. +TEST_F(MultiplanarExternalTextureTest, BasicTextureSampleLevel) { + auto* src = R"( +[[group(0), binding(0)]] var s : sampler; +[[group(0), binding(1)]] var ext_tex : texture_external; + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return textureSampleLevel(ext_tex, s, coord.xy); +} +)"; + + auto* expect = R"( +[[group(0), binding(0)]] var s : sampler; + +[[block]] +struct ExternalTextureParams { + numPlanes : u32; + vr : f32; + ug : f32; + vg : f32; + ub : f32; +}; + +[[group(0), binding(2)]] var ext_tex_plane_1 : texture_2d; + +[[group(0), binding(3)]] var ext_tex_params : ExternalTextureParams; + +[[group(0), binding(1)]] var ext_tex : texture_2d; + +fn textureSampleExternal(plane0 : texture_2d, plane1 : texture_2d, smp : sampler, coord : vec2, params : ExternalTextureParams) -> vec4 { + if ((params.numPlanes == 1u)) { + return textureSampleLevel(plane0, smp, coord, 0.0); + } + let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625); + let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5); + let u = uv.x; + let v = uv.y; + let r = ((1.164000034 * y) + (params.vr * v)); + let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v)); + let b = ((1.164000034 * y) + (params.ub * u)); + return vec4(r, g, b, 1.0); +} + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params); +} +)"; + + DataMap data; + data.Add( + MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}}); + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +// Tests that the transform works with a textureLoad call. +TEST_F(MultiplanarExternalTextureTest, BasicTextureLoad) { + auto* src = R"( +[[group(0), binding(0)]] var ext_tex : texture_external; + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return textureLoad(ext_tex, vec2(1, 1)); +} +)"; + + auto* expect = R"( +[[block]] +struct ExternalTextureParams { + numPlanes : u32; + vr : f32; + ug : f32; + vg : f32; + ub : f32; +}; + +[[group(0), binding(1)]] var ext_tex_plane_1 : texture_2d; + +[[group(0), binding(2)]] var ext_tex_params : ExternalTextureParams; + +[[group(0), binding(0)]] var ext_tex : texture_2d; + +fn textureLoadExternal(plane0 : texture_2d, plane1 : texture_2d, coord : vec2, params : ExternalTextureParams) -> vec4 { + if ((params.numPlanes == 1u)) { + return textureLoad(plane0, coord, 0); + } + let y = (textureLoad(plane0, coord, 0).r - 0.0625); + let uv = (textureLoad(plane1, coord, 0).rg - 0.5); + let u = uv.x; + let v = uv.y; + let r = ((1.164000034 * y) + (params.vr * v)); + let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v)); + let b = ((1.164000034 * y) + (params.ub * u)); + return vec4(r, g, b, 1.0); +} + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return textureLoadExternal(ext_tex, ext_tex_plane_1, vec2(1, 1), ext_tex_params); +} +)"; + + DataMap data; + data.Add( + MultiplanarExternalTexture::BindingsMap{{{0, 0}, {{0, 1}, {0, 2}}}}); + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +// Tests that the transform works with both a textureSampleLevel and textureLoad +// call. +TEST_F(MultiplanarExternalTextureTest, TextureSampleAndTextureLoad) { + auto* src = R"( +[[group(0), binding(0)]] var s : sampler; +[[group(0), binding(1)]] var ext_tex : texture_external; + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return textureSampleLevel(ext_tex, s, coord.xy) + textureLoad(ext_tex, vec2(1, 1)); +} +)"; + + auto* expect = R"( +[[group(0), binding(0)]] var s : sampler; + +[[block]] +struct ExternalTextureParams { + numPlanes : u32; + vr : f32; + ug : f32; + vg : f32; + ub : f32; +}; + +[[group(0), binding(2)]] var ext_tex_plane_1 : texture_2d; + +[[group(0), binding(3)]] var ext_tex_params : ExternalTextureParams; + +[[group(0), binding(1)]] var ext_tex : texture_2d; + +fn textureSampleExternal(plane0 : texture_2d, plane1 : texture_2d, smp : sampler, coord : vec2, params : ExternalTextureParams) -> vec4 { + if ((params.numPlanes == 1u)) { + return textureSampleLevel(plane0, smp, coord, 0.0); + } + let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625); + let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5); + let u = uv.x; + let v = uv.y; + let r = ((1.164000034 * y) + (params.vr * v)); + let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v)); + let b = ((1.164000034 * y) + (params.ub * u)); + return vec4(r, g, b, 1.0); +} + +fn textureLoadExternal(plane0 : texture_2d, plane1 : texture_2d, coord : vec2, params : ExternalTextureParams) -> vec4 { + if ((params.numPlanes == 1u)) { + return textureLoad(plane0, coord, 0); + } + let y = (textureLoad(plane0, coord, 0).r - 0.0625); + let uv = (textureLoad(plane1, coord, 0).rg - 0.5); + let u = uv.x; + let v = uv.y; + let r = ((1.164000034 * y) + (params.vr * v)); + let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v)); + let b = ((1.164000034 * y) + (params.ub * u)); + return vec4(r, g, b, 1.0); +} + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return (textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params) + textureLoadExternal(ext_tex, ext_tex_plane_1, vec2(1, 1), ext_tex_params)); +} +)"; + + DataMap data; + data.Add( + MultiplanarExternalTexture::BindingsMap{{{0, 1}, {{0, 2}, {0, 3}}}}); + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +// Tests that the transform works with many instances of texture_external. +TEST_F(MultiplanarExternalTextureTest, ManyTextureSampleLevel) { + auto* src = R"( +[[group(0), binding(0)]] var s : sampler; +[[group(0), binding(1)]] var ext_tex : texture_external; +[[group(0), binding(2)]] var ext_tex_1 : texture_external; +[[group(0), binding(3)]] var ext_tex_2 : texture_external; +[[group(1), binding(0)]] var ext_tex_3 : texture_external; + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return textureSampleLevel(ext_tex, s, coord.xy) + textureSampleLevel(ext_tex_1, s, coord.xy) + textureSampleLevel(ext_tex_2, s, coord.xy) + textureSampleLevel(ext_tex_3, s, coord.xy); +} +)"; + + auto* expect = R"( +[[group(0), binding(0)]] var s : sampler; + +[[block]] +struct ExternalTextureParams { + numPlanes : u32; + vr : f32; + ug : f32; + vg : f32; + ub : f32; +}; + +[[group(0), binding(4)]] var ext_tex_plane_1 : texture_2d; + +[[group(0), binding(5)]] var ext_tex_params : ExternalTextureParams; + +[[group(0), binding(1)]] var ext_tex : texture_2d; + +[[group(0), binding(6)]] var ext_tex_plane_1_1 : texture_2d; + +[[group(0), binding(7)]] var ext_tex_params_1 : ExternalTextureParams; + +[[group(0), binding(2)]] var ext_tex_1 : texture_2d; + +[[group(0), binding(8)]] var ext_tex_plane_1_2 : texture_2d; + +[[group(0), binding(9)]] var ext_tex_params_2 : ExternalTextureParams; + +[[group(0), binding(3)]] var ext_tex_2 : texture_2d; + +[[group(1), binding(1)]] var ext_tex_plane_1_3 : texture_2d; + +[[group(1), binding(2)]] var ext_tex_params_3 : ExternalTextureParams; + +[[group(1), binding(0)]] var ext_tex_3 : texture_2d; + +fn textureSampleExternal(plane0 : texture_2d, plane1 : texture_2d, smp : sampler, coord : vec2, params : ExternalTextureParams) -> vec4 { + if ((params.numPlanes == 1u)) { + return textureSampleLevel(plane0, smp, coord, 0.0); + } + let y = (textureSampleLevel(plane0, smp, coord, 0.0).r - 0.0625); + let uv = (textureSampleLevel(plane1, smp, coord, 0.0).rg - 0.5); + let u = uv.x; + let v = uv.y; + let r = ((1.164000034 * y) + (params.vr * v)); + let g = (((1.164000034 * y) - (params.ug * u)) - (params.vg * v)); + let b = ((1.164000034 * y) + (params.ub * u)); + return vec4(r, g, b, 1.0); +} + +[[stage(fragment)]] +fn main([[builtin(position)]] coord : vec4) -> [[location(0)]] vec4 { + return (((textureSampleExternal(ext_tex, ext_tex_plane_1, s, coord.xy, ext_tex_params) + textureSampleExternal(ext_tex_1, ext_tex_plane_1_1, s, coord.xy, ext_tex_params_1)) + textureSampleExternal(ext_tex_2, ext_tex_plane_1_2, s, coord.xy, ext_tex_params_2)) + textureSampleExternal(ext_tex_3, ext_tex_plane_1_3, s, coord.xy, ext_tex_params_3)); +} +)"; + + DataMap data; + data.Add( + MultiplanarExternalTexture::BindingsMap{ + {{0, 1}, {{0, 4}, {0, 5}}}, + {{0, 2}, {{0, 6}, {0, 7}}}, + {{0, 3}, {{0, 8}, {0, 9}}}, + {{1, 0}, {{1, 1}, {1, 2}}}, + }); + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +// Tests that the texture_external passed as a function parameter produces the +// correct output. +TEST_F(MultiplanarExternalTextureTest, ExternalTexturePassedAsParam) { + auto* src = R"( +fn f(t : texture_external, s : sampler) { + textureSampleLevel(t, s, vec2(1.0, 2.0)); + } + + [[group(0), binding(0)]] var ext_tex : texture_external; + [[group(0), binding(1)]] var smp : sampler; + + [[stage(fragment)]] + fn main() { + f(ext_tex, smp); + } +)"; + + auto* expect = + "error: transforming a texture_external passed as a user-defined " + "function parameter has not been implemented."; + DataMap data; + data.Add( + MultiplanarExternalTexture::BindingsMap{ + {{0, 0}, {{0, 2}, {0, 3}}}, + }); + auto got = Run(src, data); + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace transform +} // namespace tint diff --git a/test/BUILD.gn b/test/BUILD.gn index 2a1883c357..907018a0c1 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -316,6 +316,7 @@ tint_unittests_source_set("tint_unittests_transform_src") { "../src/transform/inline_pointer_lets_test.cc", "../src/transform/loop_to_for_loop_test.cc", "../src/transform/module_scope_var_to_entry_point_param_test.cc", + "../src/transform/multiplanar_external_texture_test.cc", "../src/transform/num_workgroups_from_uniform_test.cc", "../src/transform/pad_array_elements_test.cc", "../src/transform/promote_initializers_to_const_var_test.cc",