Implement textureWrite()

Bug: tint:368
Bug: tint:140
Bug: tint:143
Bug: tint:144
Bug: tint:145
Bug: tint:146
Bug: tint:147
Change-Id: I1b6943d8663fbac8fbaa77a4804afb5a20fdb5e6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/35320
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
Ben Clayton 2020-12-10 18:39:41 +00:00 committed by Commit Bot service account
parent 3a7bba8c98
commit 591268db48
12 changed files with 394 additions and 95 deletions

View File

@ -266,7 +266,7 @@ bool IsTextureIntrinsic(Intrinsic i) {
i == Intrinsic::kTextureSampleLevel ||
i == Intrinsic::kTextureSampleBias ||
i == Intrinsic::kTextureSampleCompare ||
i == Intrinsic::kTextureSampleGrad;
i == Intrinsic::kTextureSampleGrad || i == Intrinsic::kTextureStore;
}
} // namespace intrinsic

View File

@ -91,6 +91,7 @@ enum class Intrinsic {
kTextureSampleCompare,
kTextureSampleGrad,
kTextureSampleLevel,
kTextureStore,
kTrunc
};
@ -143,6 +144,8 @@ struct TextureSignature : public Signature {
size_t sample_index = kNotUsed;
/// `texture` parameter index.
size_t texture = kNotUsed;
/// `value` parameter index.
size_t value = kNotUsed;
};
/// The indices of all possible parameters.
Index idx;

View File

@ -1976,6 +1976,90 @@ std::vector<TextureOverloadCase> TextureOverloadCase::ValidCases() {
b->vec3<i32>(1, 2, 3)); // coords
},
},
{
ValidTextureOverload::kStoreWO1dRgba32float,
"textureStore(t : texture_storage_wo_1d<F>,\n"
" coords : i32,\n"
" value : vec4<T>) -> void",
ast::AccessControl::kWriteOnly,
ast::type::ImageFormat::kRgba32Float,
type::TextureDimension::k1d,
TextureDataType::kF32,
"textureStore",
[](Builder* b) {
return b->ExprList("texture", // t
1, // coords
b->vec4<f32>(2.f, 3.f, 4.f, 5.f)); // value
},
},
{
ValidTextureOverload::kStoreWO1dArrayRgba32float,
"textureStore(t : texture_storage_wo_1d_array<F>,\n"
" coords : i32,\n"
" array_index : i32,\n"
" value : vec4<T>) -> void",
ast::AccessControl::kWriteOnly,
ast::type::ImageFormat::kRgba32Float,
type::TextureDimension::k1dArray,
TextureDataType::kF32,
"textureStore",
[](Builder* b) {
return b->ExprList("texture", // t
1, // coords
2, // array_index
b->vec4<f32>(3.f, 4.f, 5.f, 6.f)); // value
},
},
{
ValidTextureOverload::kStoreWO2dRgba32float,
"textureStore(t : texture_storage_wo_2d<F>,\n"
" coords : vec2<i32>,\n"
" value : vec4<T>) -> void",
ast::AccessControl::kWriteOnly,
ast::type::ImageFormat::kRgba32Float,
type::TextureDimension::k2d,
TextureDataType::kF32,
"textureStore",
[](Builder* b) {
return b->ExprList("texture", // t
b->vec2<i32>(1, 2), // coords
b->vec4<f32>(3.f, 4.f, 5.f, 6.f)); // value
},
},
{
ValidTextureOverload::kStoreWO2dArrayRgba32float,
"textureStore(t : texture_storage_wo_2d_array<F>,\n"
" coords : vec2<i32>,\n"
" array_index : i32,\n"
" value : vec4<T>) -> void",
ast::AccessControl::kWriteOnly,
ast::type::ImageFormat::kRgba32Float,
type::TextureDimension::k2dArray,
TextureDataType::kF32,
"textureStore",
[](Builder* b) {
return b->ExprList("texture", // t
b->vec2<i32>(1, 2), // coords
3, // array_index
b->vec4<f32>(4.f, 5.f, 6.f, 7.f)); // value
},
},
{
ValidTextureOverload::kStoreWO3dRgba32float,
"textureStore(t : texture_storage_wo_3d<F>,\n"
" coords : vec3<i32>,\n"
" value : vec4<T>) -> void",
ast::AccessControl::kWriteOnly,
ast::type::ImageFormat::kRgba32Float,
type::TextureDimension::k3d,
TextureDataType::kF32,
"textureStore",
[](Builder* b) {
return b->ExprList("texture", // t
b->vec3<i32>(1, 2, 3), // coords
b->vec4<f32>(4.f, 5.f, 6.f, 7.f)); // value
},
},
};
}

View File

@ -142,6 +142,11 @@ enum class ValidTextureOverload {
kLoadStorageRO2dRgba32float,
kLoadStorageRO2dArrayRgba32float, // Not permutated for all texel formats
kLoadStorageRO3dRgba32float, // Not permutated for all texel formats
kStoreWO1dRgba32float, // Not permutated for all texel formats
kStoreWO1dArrayRgba32float, // Not permutated for all texel formats
kStoreWO2dRgba32float, // Not permutated for all texel formats
kStoreWO2dArrayRgba32float, // Not permutated for all texel formats
kStoreWO3dRgba32float, // Not permutated for all texel formats
};
/// Describes a texture intrinsic overload

View File

@ -52,6 +52,7 @@
#include "src/ast/type/texture_type.h"
#include "src/ast/type/u32_type.h"
#include "src/ast/type/vector_type.h"
#include "src/ast/type/void_type.h"
#include "src/ast/type_constructor_expression.h"
#include "src/ast/unary_op_expression.h"
#include "src/ast/variable_decl_statement.h"
@ -640,6 +641,14 @@ bool TypeDeterminer::DetermineIntrinsic(ast::IdentifierExpression* ident,
param.idx.offset = param.count++;
}
break;
case ast::Intrinsic::kTextureStore:
param.idx.texture = param.count++;
param.idx.coords = param.count++;
if (is_array) {
param.idx.array_index = param.count++;
}
param.idx.value = param.count++;
break;
default:
set_error(expr->source(),
"Internal compiler error: Unreachable intrinsic " +
@ -658,8 +667,13 @@ bool TypeDeterminer::DetermineIntrinsic(ast::IdentifierExpression* ident,
ident->set_intrinsic_signature(
std::make_unique<ast::intrinsic::TextureSignature>(param));
// Set the function return type
ast::type::Type* return_type = nullptr;
if (ident->intrinsic() == ast::Intrinsic::kTextureStore) {
return_type = mod_->create<ast::type::Void>();
} else {
if (texture->Is<ast::type::DepthTexture>()) {
expr->func()->set_result_type(mod_->create<ast::type::F32>());
return_type = mod_->create<ast::type::F32>();
} else {
ast::type::Type* type = nullptr;
if (auto* storage = texture->As<ast::type::StorageTexture>()) {
@ -670,11 +684,15 @@ bool TypeDeterminer::DetermineIntrinsic(ast::IdentifierExpression* ident,
texture->As<ast::type::MultisampledTexture>()) {
type = msampled->type();
} else {
set_error(expr->source(), "unknown texture type for texture sampling");
set_error(expr->source(),
"unknown texture type for texture sampling");
return false;
}
expr->func()->set_result_type(mod_->create<ast::type::Vector>(type, 4));
return_type = mod_->create<ast::type::Vector>(type, 4);
}
}
expr->func()->set_result_type(return_type);
return true;
}
if (ident->intrinsic() == ast::Intrinsic::kDot) {
@ -996,6 +1014,8 @@ bool TypeDeterminer::SetIntrinsicIfNeeded(ast::IdentifierExpression* ident) {
ident->set_intrinsic(ast::Intrinsic::kTanh);
} else if (ident->name() == "textureLoad") {
ident->set_intrinsic(ast::Intrinsic::kTextureLoad);
} else if (ident->name() == "textureStore") {
ident->set_intrinsic(ast::Intrinsic::kTextureStore);
} else if (ident->name() == "textureSample") {
ident->set_intrinsic(ast::Intrinsic::kTextureSample);
} else if (ident->name() == "textureSampleBias") {

View File

@ -4513,6 +4513,7 @@ std::string to_str(const std::string& function,
maybe_add_param(sig->params.idx.sampler, "sampler");
maybe_add_param(sig->params.idx.sample_index, "sample_index");
maybe_add_param(sig->params.idx.texture, "texture");
maybe_add_param(sig->params.idx.value, "value");
std::sort(
params.begin(), params.end(),
[](const Parameter& a, const Parameter& b) { return a.idx < b.idx; });
@ -4732,6 +4733,16 @@ const char* expected_texture_overload(
return R"(textureLoad(texture, coords, array_index))";
case ValidTextureOverload::kLoadStorageRO3dRgba32float:
return R"(textureLoad(texture, coords))";
case ValidTextureOverload::kStoreWO1dRgba32float:
return R"(textureStore(texture, coords, value))";
case ValidTextureOverload::kStoreWO1dArrayRgba32float:
return R"(textureStore(texture, coords, array_index, value))";
case ValidTextureOverload::kStoreWO2dRgba32float:
return R"(textureStore(texture, coords, value))";
case ValidTextureOverload::kStoreWO2dArrayRgba32float:
return R"(textureStore(texture, coords, array_index, value))";
case ValidTextureOverload::kStoreWO3dRgba32float:
return R"(textureStore(texture, coords, value))";
}
return "<unmatched texture overload>";
}
@ -4748,13 +4759,17 @@ TEST_P(TypeDeterminerTextureIntrinsicTest, Call) {
ASSERT_TRUE(td()->Determine()) << td()->error();
ASSERT_TRUE(td()->DetermineResultType(&call)) << td()->error();
if (std::string(param.function) == "textureStore") {
EXPECT_EQ(call.result_type(), ty.void_);
} else {
switch (param.texture_kind) {
case ast::intrinsic::test::TextureKind::kRegular:
case ast::intrinsic::test::TextureKind::kMultisampled:
case ast::intrinsic::test::TextureKind::kStorage: {
auto* datatype = param.resultVectorComponentType(this);
ASSERT_TRUE(call.result_type()->Is<ast::type::Vector>());
EXPECT_EQ(call.result_type()->As<ast::type::Vector>()->type(), datatype);
EXPECT_EQ(call.result_type()->As<ast::type::Vector>()->type(),
datatype);
break;
}
case ast::intrinsic::test::TextureKind::kDepth: {
@ -4762,6 +4777,7 @@ TEST_P(TypeDeterminerTextureIntrinsicTest, Call) {
break;
}
}
}
auto* sig = static_cast<const ast::intrinsic::TextureSignature*>(
ident->intrinsic_signature());

View File

@ -719,8 +719,6 @@ bool GeneratorImpl::EmitCall(std::ostream& pre,
bool GeneratorImpl::EmitTextureCall(std::ostream& pre,
std::ostream& out,
ast::CallExpression* expr) {
make_indent(out);
auto* ident = expr->func()->As<ast::IdentifierExpression>();
auto params = expr->params();
@ -759,6 +757,9 @@ bool GeneratorImpl::EmitTextureCall(std::ostream& pre,
pack_mip_in_coords = true;
}
break;
case ast::Intrinsic::kTextureStore:
out << "[";
break;
default:
error_ = "Internal compiler error: Unhandled texture intrinsic '" +
ident->name() + "'";
@ -815,7 +816,13 @@ bool GeneratorImpl::EmitTextureCall(std::ostream& pre,
}
}
if (ident->intrinsic() == ast::Intrinsic::kTextureStore) {
out << "] = ";
if (!EmitExpression(pre, out, params[pidx.value]))
return false;
} else {
out << ")";
}
return true;
}

View File

@ -229,6 +229,16 @@ std::string expected_texture_overload(
return R"(texture_tint_0.Load(int3(1, 2, 3)))";
case ValidTextureOverload::kLoadStorageRO3dRgba32float:
return R"(texture_tint_0.Load(int3(1, 2, 3)))";
case ValidTextureOverload::kStoreWO1dRgba32float:
return R"(texture_tint_0[1] = float4(2.0f, 3.0f, 4.0f, 5.0f))";
case ValidTextureOverload::kStoreWO1dArrayRgba32float:
return R"(texture_tint_0[int2(1, 2)] = float4(3.0f, 4.0f, 5.0f, 6.0f))";
case ValidTextureOverload::kStoreWO2dRgba32float:
return R"(texture_tint_0[int2(1, 2)] = float4(3.0f, 4.0f, 5.0f, 6.0f))";
case ValidTextureOverload::kStoreWO2dArrayRgba32float:
return R"(texture_tint_0[int3(1, 2, 3)] = float4(4.0f, 5.0f, 6.0f, 7.0f))";
case ValidTextureOverload::kStoreWO3dRgba32float:
return R"(texture_tint_0[int3(1, 2, 3)] = float4(4.0f, 5.0f, 6.0f, 7.0f))";
}
return "<unmatched texture overload>";
} // NOLINT - Ignore the length of this function

View File

@ -679,6 +679,9 @@ bool GeneratorImpl::EmitTextureCall(ast::CallExpression* expr) {
out_ << ".read(";
lod_param_is_named = false;
break;
case ast::Intrinsic::kTextureStore:
out_ << ".write(";
break;
default:
error_ = "Internal compiler error: Unhandled texture intrinsic '" +
ident->name() + "'";
@ -693,15 +696,8 @@ bool GeneratorImpl::EmitTextureCall(ast::CallExpression* expr) {
first_arg = false;
};
if (pidx.sampler != kNotUsed) {
if (!EmitExpression(params[pidx.sampler])) {
return false;
}
first_arg = false;
}
for (auto idx :
{pidx.coords, pidx.array_index, pidx.depth_ref, pidx.sample_index}) {
for (auto idx : {pidx.value, pidx.sampler, pidx.coords, pidx.array_index,
pidx.depth_ref, pidx.sample_index}) {
if (idx != kNotUsed) {
maybe_write_comma();
if (!EmitExpression(params[idx]))

View File

@ -229,6 +229,16 @@ std::string expected_texture_overload(
return R"(texture_tint_0.read(int2(1, 2), 3))";
case ValidTextureOverload::kLoadStorageRO3dRgba32float:
return R"(texture_tint_0.read(int3(1, 2, 3)))";
case ValidTextureOverload::kStoreWO1dRgba32float:
return R"(texture_tint_0.write(float4(2.0f, 3.0f, 4.0f, 5.0f), 1))";
case ValidTextureOverload::kStoreWO1dArrayRgba32float:
return R"(texture_tint_0.write(float4(3.0f, 4.0f, 5.0f, 6.0f), 1, 2))";
case ValidTextureOverload::kStoreWO2dRgba32float:
return R"(texture_tint_0.write(float4(3.0f, 4.0f, 5.0f, 6.0f), int2(1, 2)))";
case ValidTextureOverload::kStoreWO2dArrayRgba32float:
return R"(texture_tint_0.write(float4(4.0f, 5.0f, 6.0f, 7.0f), int2(1, 2), 3))";
case ValidTextureOverload::kStoreWO3dRgba32float:
return R"(texture_tint_0.write(float4(4.0f, 5.0f, 6.0f, 7.0f), int3(1, 2, 3)))";
}
return "<unmatched texture overload>";
} // NOLINT - Ignore the length of this function

View File

@ -1943,8 +1943,10 @@ void Builder::GenerateTextureIntrinsic(ast::IdentifierExpression* ident,
// Populate the spirv_params with common parameters
OperandList spirv_params;
spirv_params.reserve(8); // Enough to fit most parameter lists
spirv_params.emplace_back(std::move(result_type)); // result type
spirv_params.emplace_back(std::move(result_id)); // result id
if (ident->intrinsic() != ast::Intrinsic::kTextureStore) {
spirv_params.emplace_back(std::move(result_type));
spirv_params.emplace_back(std::move(result_id));
}
// Extra image operands, appended to spirv_params.
struct ImageOperand {
@ -1977,8 +1979,23 @@ void Builder::GenerateTextureIntrinsic(ast::IdentifierExpression* ident,
}
};
if (ident->intrinsic() == ast::Intrinsic::kTextureLoad) {
op = texture_type->Is<ast::type::StorageTexture>() ? spv::Op::OpImageRead
auto append_image_and_coords_to_spirv_params = [&] {
assert(pidx.sampler != kNotUsed);
assert(pidx.texture != kNotUsed);
auto sampler_param = gen_param(pidx.sampler);
auto texture_param = gen_param(pidx.texture);
auto sampled_image =
GenerateSampledImage(texture_type, texture_param, sampler_param);
// Populate the spirv_params with the common parameters
spirv_params.emplace_back(Operand::Int(sampled_image)); // sampled image
append_coords_to_spirv_params();
};
switch (ident->intrinsic()) {
case ast::Intrinsic::kTextureLoad: {
op = texture_type->Is<ast::type::StorageTexture>()
? spv::Op::OpImageRead
: spv::Op::OpImageFetch;
spirv_params.emplace_back(gen_param(pidx.texture));
append_coords_to_spirv_params();
@ -1992,25 +2009,24 @@ void Builder::GenerateTextureIntrinsic(ast::IdentifierExpression* ident,
image_operands.emplace_back(ImageOperand{SpvImageOperandsSampleMask,
gen_param(pidx.sample_index)});
}
} else {
assert(pidx.sampler != kNotUsed);
auto sampler_param = gen_param(pidx.sampler);
auto texture_param = gen_param(pidx.texture);
auto sampled_image =
GenerateSampledImage(texture_type, texture_param, sampler_param);
// Populate the spirv_params with the common parameters
spirv_params.emplace_back(Operand::Int(sampled_image)); // sampled image
break;
}
case ast::Intrinsic::kTextureStore: {
op = spv::Op::OpImageWrite;
spirv_params.emplace_back(gen_param(pidx.texture));
append_coords_to_spirv_params();
switch (ident->intrinsic()) {
spirv_params.emplace_back(gen_param(pidx.value));
break;
}
case ast::Intrinsic::kTextureSample: {
op = spv::Op::OpImageSampleImplicitLod;
append_image_and_coords_to_spirv_params();
break;
}
case ast::Intrinsic::kTextureSampleBias: {
op = spv::Op::OpImageSampleImplicitLod;
append_image_and_coords_to_spirv_params();
assert(pidx.bias != kNotUsed);
image_operands.emplace_back(
ImageOperand{SpvImageOperandsBiasMask, gen_param(pidx.bias)});
@ -2018,6 +2034,7 @@ void Builder::GenerateTextureIntrinsic(ast::IdentifierExpression* ident,
}
case ast::Intrinsic::kTextureSampleLevel: {
op = spv::Op::OpImageSampleExplicitLod;
append_image_and_coords_to_spirv_params();
assert(pidx.level != kNotUsed);
image_operands.emplace_back(
ImageOperand{SpvImageOperandsLodMask, gen_param(pidx.level)});
@ -2025,6 +2042,7 @@ void Builder::GenerateTextureIntrinsic(ast::IdentifierExpression* ident,
}
case ast::Intrinsic::kTextureSampleGrad: {
op = spv::Op::OpImageSampleExplicitLod;
append_image_and_coords_to_spirv_params();
assert(pidx.ddx != kNotUsed);
assert(pidx.ddy != kNotUsed);
image_operands.emplace_back(
@ -2035,6 +2053,7 @@ void Builder::GenerateTextureIntrinsic(ast::IdentifierExpression* ident,
}
case ast::Intrinsic::kTextureSampleCompare: {
op = spv::Op::OpImageSampleDrefExplicitLod;
append_image_and_coords_to_spirv_params();
assert(pidx.depth_ref != kNotUsed);
spirv_params.emplace_back(gen_param(pidx.depth_ref));
@ -2048,7 +2067,6 @@ void Builder::GenerateTextureIntrinsic(ast::IdentifierExpression* ident,
default:
break; // unreachable
}
}
if (pidx.offset != kNotUsed) {
image_operands.emplace_back(

View File

@ -2563,8 +2563,138 @@ expected_texture_overload_spirv expected_texture_overload(
R"(
%10 = OpLoad %3 %1
%8 = OpImageRead %9 %10 %16
)"};
case ValidTextureOverload::kStoreWO1dRgba32float:
return {R"(
%4 = OpTypeVoid
%3 = OpTypeImage %4 1D 0 0 0 2 Rgba32f
%2 = OpTypePointer Private %3
%1 = OpVariable %2 Private
%7 = OpTypeSampler
%6 = OpTypePointer Private %7
%5 = OpVariable %6 Private
%10 = OpTypeInt 32 1
%11 = OpConstant %10 1
%13 = OpTypeFloat 32
%12 = OpTypeVector %13 4
%14 = OpConstant %13 2
%15 = OpConstant %13 3
%16 = OpConstant %13 4
%17 = OpConstant %13 5
%18 = OpConstantComposite %12 %14 %15 %16 %17
)",
R"(
%9 = OpLoad %3 %1
OpImageWrite %9 %11 %18
)"};
case ValidTextureOverload::kStoreWO1dArrayRgba32float:
return {R"(
%4 = OpTypeVoid
%3 = OpTypeImage %4 1D 0 1 0 2 Rgba32f
%2 = OpTypePointer Private %3
%1 = OpVariable %2 Private
%7 = OpTypeSampler
%6 = OpTypePointer Private %7
%5 = OpVariable %6 Private
%11 = OpTypeInt 32 1
%10 = OpTypeVector %11 2
%12 = OpConstant %11 1
%13 = OpConstant %11 2
%14 = OpConstantComposite %10 %12 %13
%16 = OpTypeFloat 32
%15 = OpTypeVector %16 4
%17 = OpConstant %16 3
%18 = OpConstant %16 4
%19 = OpConstant %16 5
%20 = OpConstant %16 6
%21 = OpConstantComposite %15 %17 %18 %19 %20
)",
R"(
%9 = OpLoad %3 %1
OpImageWrite %9 %14 %21
)"};
case ValidTextureOverload::kStoreWO2dRgba32float:
return {R"(
%4 = OpTypeVoid
%3 = OpTypeImage %4 2D 0 0 0 2 Rgba32f
%2 = OpTypePointer Private %3
%1 = OpVariable %2 Private
%7 = OpTypeSampler
%6 = OpTypePointer Private %7
%5 = OpVariable %6 Private
%11 = OpTypeInt 32 1
%10 = OpTypeVector %11 2
%12 = OpConstant %11 1
%13 = OpConstant %11 2
%14 = OpConstantComposite %10 %12 %13
%16 = OpTypeFloat 32
%15 = OpTypeVector %16 4
%17 = OpConstant %16 3
%18 = OpConstant %16 4
%19 = OpConstant %16 5
%20 = OpConstant %16 6
%21 = OpConstantComposite %15 %17 %18 %19 %20
)",
R"(
%9 = OpLoad %3 %1
OpImageWrite %9 %14 %21
)"};
case ValidTextureOverload::kStoreWO2dArrayRgba32float:
return {R"(
%4 = OpTypeVoid
%3 = OpTypeImage %4 2D 0 1 0 2 Rgba32f
%2 = OpTypePointer Private %3
%1 = OpVariable %2 Private
%7 = OpTypeSampler
%6 = OpTypePointer Private %7
%5 = OpVariable %6 Private
%11 = OpTypeInt 32 1
%10 = OpTypeVector %11 3
%12 = OpConstant %11 1
%13 = OpConstant %11 2
%14 = OpConstant %11 3
%15 = OpConstantComposite %10 %12 %13 %14
%17 = OpTypeFloat 32
%16 = OpTypeVector %17 4
%18 = OpConstant %17 4
%19 = OpConstant %17 5
%20 = OpConstant %17 6
%21 = OpConstant %17 7
%22 = OpConstantComposite %16 %18 %19 %20 %21
)",
R"(
%9 = OpLoad %3 %1
OpImageWrite %9 %15 %22
)"};
case ValidTextureOverload::kStoreWO3dRgba32float:
return {R"(
%4 = OpTypeVoid
%3 = OpTypeImage %4 3D 0 0 0 2 Rgba32f
%2 = OpTypePointer Private %3
%1 = OpVariable %2 Private
%7 = OpTypeSampler
%6 = OpTypePointer Private %7
%5 = OpVariable %6 Private
%11 = OpTypeInt 32 1
%10 = OpTypeVector %11 3
%12 = OpConstant %11 1
%13 = OpConstant %11 2
%14 = OpConstant %11 3
%15 = OpConstantComposite %10 %12 %13 %14
%17 = OpTypeFloat 32
%16 = OpTypeVector %17 4
%18 = OpConstant %17 4
%19 = OpConstant %17 5
%20 = OpConstant %17 6
%21 = OpConstant %17 7
%22 = OpConstantComposite %16 %18 %19 %20 %21
)",
R"(
%9 = OpLoad %3 %1
OpImageWrite %9 %15 %22
)"};
}
return {"<unmatched texture overload>", "<unmatched texture overload>"};
} // NOLINT - Ignore the length of this function