[ir] Add robustness for texture builtins
Clamp the coordinates, level, and array index arguments to
`textureLoad`, `textureStore`, and `textureDimensions`.
Bug: tint:1718
Change-Id: I77b17b9aae05a7ec31ea57b65dad0c17c9959985
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/151120
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/core/ir/transform/robustness.cc b/src/tint/lang/core/ir/transform/robustness.cc
index 43c25ff3..ed03c80 100644
--- a/src/tint/lang/core/ir/transform/robustness.cc
+++ b/src/tint/lang/core/ir/transform/robustness.cc
@@ -20,6 +20,9 @@
#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/module.h"
#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/lang/core/type/depth_texture.h"
+#include "src/tint/lang/core/type/sampled_texture.h"
+#include "src/tint/lang/core/type/texture.h"
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
@@ -48,6 +51,7 @@
Vector<ir::Access*, 64> accesses;
Vector<ir::LoadVectorElement*, 64> vector_loads;
Vector<ir::StoreVectorElement*, 64> vector_stores;
+ Vector<ir::CoreBuiltinCall*, 64> texture_calls;
for (auto* inst : ir->instructions.Objects()) {
if (inst->Alive()) {
tint::Switch(
@@ -78,6 +82,16 @@
if (ShouldClamp(ptr->AddressSpace())) {
vector_stores.Push(sve);
}
+ },
+ [&](ir::CoreBuiltinCall* call) {
+ // Check if this is a texture builtin that needs to be clamped.
+ if (config.clamp_texture) {
+ if (call->Func() == core::Function::kTextureDimensions ||
+ call->Func() == core::Function::kTextureLoad ||
+ call->Func() == core::Function::kTextureStore) {
+ texture_calls.Push(call);
+ }
+ }
});
}
}
@@ -107,7 +121,13 @@
});
}
- // TODO(jrprice): Handle texture builtins.
+ // Clamp indices and coordinates for texture builtins calls.
+ for (auto* call : texture_calls) {
+ b.InsertBefore(call, [&] { //
+ ClampTextureCallArgs(call);
+ });
+ }
+
// TODO(jrprice): Handle config.bindings_ignored.
// TODO(jrprice): Handle config.disable_runtime_sized_array_index_clamping.
}
@@ -139,6 +159,21 @@
return false;
}
+ /// Convert a value to a u32 if needed.
+ /// @param value the value to convert
+ /// @returns the converted value, or @p value if it is already a u32
+ ir::Value* CastToU32(ir::Value* value) {
+ if (value->Type()->is_unsigned_integer_scalar_or_vector()) {
+ return value;
+ }
+
+ const type::Type* type = ty.u32();
+ if (auto* vec = value->Type()->As<type::Vector>()) {
+ type = ty.vec(type, vec->Width());
+ }
+ return b.Convert(type, value)->Result();
+ }
+
/// Clamp operand @p op_idx of @p inst to ensure it is within @p limit.
/// @param inst the instruction
/// @param op_idx the index of the operand that should be clamped
@@ -154,13 +189,8 @@
clamped_idx = b.Constant(u32(std::min(const_idx->Value()->ValueAs<uint32_t>(),
const_limit->Value()->ValueAs<uint32_t>())));
} else {
- // Convert the index to u32 if needed.
- if (idx->Type()->is_signed_integer_scalar()) {
- idx = b.Convert(ty.u32(), idx)->Result();
- }
-
// Clamp it to the dynamic limit.
- clamped_idx = b.Call(ty.u32(), core::Function::kMin, idx, limit)->Result();
+ clamped_idx = b.Call(ty.u32(), core::Function::kMin, CastToU32(idx), limit)->Result();
}
// Replace the index operand with the clamped version.
@@ -219,6 +249,81 @@
: type->Elements().type;
}
}
+
+ /// Clamp the indices and coordinates of a texture builtin call instruction to ensure they are
+ /// within the limits of the texture that they are accessing.
+ /// @param call the texture builtin call instruction
+ void ClampTextureCallArgs(ir::CoreBuiltinCall* call) {
+ const auto& args = call->Args();
+ auto* texture = args[0]->Type()->As<type::Texture>();
+
+ // Helper for clamping the level argument.
+ // Keep hold of the clamped value to use for clamping the coordinates.
+ Value* clamped_level = nullptr;
+ auto clamp_level = [&](uint32_t idx) {
+ auto* num_levels = b.Call(ty.u32(), core::Function::kTextureNumLevels, args[0]);
+ auto* limit = b.Subtract(ty.u32(), num_levels, 1_u);
+ clamped_level =
+ b.Call(ty.u32(), core::Function::kMin, CastToU32(args[idx]), limit)->Result();
+ call->SetOperand(CoreBuiltinCall::kArgsOperandOffset + idx, clamped_level);
+ };
+
+ // Helper for clamping the coordinates.
+ auto clamp_coords = [&](uint32_t idx) {
+ const type::Type* type = ty.u32();
+ auto* one = b.Constant(1_u);
+ if (auto* vec = args[idx]->Type()->As<type::Vector>()) {
+ type = ty.vec(type, vec->Width());
+ one = b.Splat(type, one, vec->Width());
+ }
+ auto* dims = clamped_level ? b.Call(type, core::Function::kTextureDimensions, args[0],
+ clamped_level)
+ : b.Call(type, core::Function::kTextureDimensions, args[0]);
+ auto* limit = b.Subtract(type, dims, one);
+ call->SetOperand(
+ CoreBuiltinCall::kArgsOperandOffset + idx,
+ b.Call(type, core::Function::kMin, CastToU32(args[idx]), limit)->Result());
+ };
+
+ // Helper for clamping the array index.
+ auto clamp_array_index = [&](uint32_t idx) {
+ auto* num_layers = b.Call(ty.u32(), core::Function::kTextureNumLayers, args[0]);
+ auto* limit = b.Subtract(ty.u32(), num_layers, 1_u);
+ call->SetOperand(
+ CoreBuiltinCall::kArgsOperandOffset + idx,
+ b.Call(ty.u32(), core::Function::kMin, CastToU32(args[idx]), limit)->Result());
+ };
+
+ // Select which arguments to clamp based on the function overload.
+ switch (call->Func()) {
+ case core::Function::kTextureDimensions: {
+ if (args.Length() > 1) {
+ clamp_level(1u);
+ }
+ break;
+ }
+ case core::Function::kTextureLoad: {
+ clamp_coords(1u);
+ uint32_t next_arg = 2u;
+ if (type::IsTextureArray(texture->dim())) {
+ clamp_array_index(next_arg++);
+ }
+ if (texture->IsAnyOf<type::SampledTexture, type::DepthTexture>()) {
+ clamp_level(next_arg++);
+ }
+ break;
+ }
+ case core::Function::kTextureStore: {
+ clamp_coords(1u);
+ if (type::IsTextureArray(texture->dim())) {
+ clamp_array_index(2u);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
};
} // namespace
diff --git a/src/tint/lang/core/ir/transform/robustness_test.cc b/src/tint/lang/core/ir/transform/robustness_test.cc
index 6bda7fd..6bb4be0 100644
--- a/src/tint/lang/core/ir/transform/robustness_test.cc
+++ b/src/tint/lang/core/ir/transform/robustness_test.cc
@@ -18,8 +18,14 @@
#include "src/tint/lang/core/ir/transform/helper_test.h"
#include "src/tint/lang/core/type/array.h"
+#include "src/tint/lang/core/type/depth_multisampled_texture.h"
+#include "src/tint/lang/core/type/depth_texture.h"
+#include "src/tint/lang/core/type/external_texture.h"
#include "src/tint/lang/core/type/matrix.h"
+#include "src/tint/lang/core/type/multisampled_texture.h"
#include "src/tint/lang/core/type/pointer.h"
+#include "src/tint/lang/core/type/sampled_texture.h"
+#include "src/tint/lang/core/type/storage_texture.h"
#include "src/tint/lang/core/type/struct.h"
#include "src/tint/lang/core/type/vector.h"
@@ -1870,5 +1876,1711 @@
EXPECT_EQ(GetParam() ? expect : src, str());
}
+////////////////////////////////////////////////////////////////
+// Test clamping texture builtin calls.
+////////////////////////////////////////////////////////////////
+
+TEST_P(IR_RobustnessTest, TextureDimensions) {
+ auto* texture = b.Var(
+ "texture",
+ ty.ptr(handle, ty.Get<type::SampledTexture>(type::TextureDimension::k2d, ty.f32()), read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ auto* func = b.Function("foo", ty.vec2<u32>());
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* dims = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, handle);
+ b.Return(func, dims);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_2d<f32>, read> = var @binding_point(0, 0)
+}
+
+%foo = func():vec2<u32> -> %b2 {
+ %b2 = block {
+ %3:texture_2d<f32> = load %texture
+ %4:vec2<u32> = textureDimensions %3
+ ret %4
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureDimensions_WithLevel) {
+ auto* texture = b.Var(
+ "texture",
+ ty.ptr(handle, ty.Get<type::SampledTexture>(type::TextureDimension::k2d, ty.f32()), read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ auto* func = b.Function("foo", ty.vec2<u32>());
+ auto* level = b.FunctionParam("level", ty.u32());
+ func->SetParams({level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* dims = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, handle, level);
+ b.Return(func, dims);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_2d<f32>, read> = var @binding_point(0, 0)
+}
+
+%foo = func(%level:u32):vec2<u32> -> %b2 {
+ %b2 = block {
+ %4:texture_2d<f32> = load %texture
+ %5:vec2<u32> = textureDimensions %4, %level
+ ret %5
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_2d<f32>, read> = var @binding_point(0, 0)
+}
+
+%foo = func(%level:u32):vec2<u32> -> %b2 {
+ %b2 = block {
+ %4:texture_2d<f32> = load %texture
+ %5:u32 = textureNumLevels %4
+ %6:u32 = sub %5, 1u
+ %7:u32 = min %level, %6
+ %8:vec2<u32> = textureDimensions %4, %7
+ ret %8
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Sampled1D) {
+ auto* texture = b.Var(
+ "texture",
+ ty.ptr(handle, ty.Get<type::SampledTexture>(type::TextureDimension::k1d, ty.f32()), read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.i32());
+ auto* level = b.FunctionParam("level", ty.i32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.u32());
+ auto* level = b.FunctionParam("level", ty.u32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_1d<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:i32, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_1d<f32> = load %texture
+ %6:vec4<f32> = textureLoad %5, %coords, %level
+ ret %6
+ }
+}
+%load_unsigned = func(%coords_1:u32, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %10:texture_1d<f32> = load %texture
+ %11:vec4<f32> = textureLoad %10, %coords_1, %level_1
+ ret %11
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_1d<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:i32, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_1d<f32> = load %texture
+ %6:u32 = textureDimensions %5
+ %7:u32 = sub %6, 1u
+ %8:u32 = convert %coords
+ %9:u32 = min %8, %7
+ %10:u32 = textureNumLevels %5
+ %11:u32 = sub %10, 1u
+ %12:u32 = convert %level
+ %13:u32 = min %12, %11
+ %14:vec4<f32> = textureLoad %5, %9, %13
+ ret %14
+ }
+}
+%load_unsigned = func(%coords_1:u32, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %18:texture_1d<f32> = load %texture
+ %19:u32 = textureDimensions %18
+ %20:u32 = sub %19, 1u
+ %21:u32 = min %coords_1, %20
+ %22:u32 = textureNumLevels %18
+ %23:u32 = sub %22, 1u
+ %24:u32 = min %level_1, %23
+ %25:vec4<f32> = textureLoad %18, %21, %24
+ ret %25
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Sampled2D) {
+ auto* texture = b.Var(
+ "texture",
+ ty.ptr(handle, ty.Get<type::SampledTexture>(type::TextureDimension::k2d, ty.f32()), read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ auto* level = b.FunctionParam("level", ty.i32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* level = b.FunctionParam("level", ty.u32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_2d<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_2d<f32> = load %texture
+ %6:vec4<f32> = textureLoad %5, %coords, %level
+ ret %6
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %10:texture_2d<f32> = load %texture
+ %11:vec4<f32> = textureLoad %10, %coords_1, %level_1
+ ret %11
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_2d<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_2d<f32> = load %texture
+ %6:vec2<u32> = textureDimensions %5
+ %7:vec2<u32> = sub %6, vec2<u32>(1u)
+ %8:vec2<u32> = convert %coords
+ %9:vec2<u32> = min %8, %7
+ %10:u32 = textureNumLevels %5
+ %11:u32 = sub %10, 1u
+ %12:u32 = convert %level
+ %13:u32 = min %12, %11
+ %14:vec4<f32> = textureLoad %5, %9, %13
+ ret %14
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %18:texture_2d<f32> = load %texture
+ %19:vec2<u32> = textureDimensions %18
+ %20:vec2<u32> = sub %19, vec2<u32>(1u)
+ %21:vec2<u32> = min %coords_1, %20
+ %22:u32 = textureNumLevels %18
+ %23:u32 = sub %22, 1u
+ %24:u32 = min %level_1, %23
+ %25:vec4<f32> = textureLoad %18, %21, %24
+ ret %25
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Sampled2DArray) {
+ auto* texture = b.Var(
+ "texture",
+ ty.ptr(handle, ty.Get<type::SampledTexture>(type::TextureDimension::k2dArray, ty.f32()),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ auto* layer = b.FunctionParam("layer", ty.i32());
+ auto* level = b.FunctionParam("level", ty.i32());
+ func->SetParams({coords, layer, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, layer, level);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* layer = b.FunctionParam("layer", ty.u32());
+ auto* level = b.FunctionParam("level", ty.u32());
+ func->SetParams({coords, layer, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, layer, level);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_2d_array<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %layer:i32, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %6:texture_2d_array<f32> = load %texture
+ %7:vec4<f32> = textureLoad %6, %coords, %layer, %level
+ ret %7
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %layer_1: 'layer', %level_1: 'level'
+ %b3 = block {
+ %12:texture_2d_array<f32> = load %texture
+ %13:vec4<f32> = textureLoad %12, %coords_1, %layer_1, %level_1
+ ret %13
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_2d_array<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %layer:i32, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %6:texture_2d_array<f32> = load %texture
+ %7:vec2<u32> = textureDimensions %6
+ %8:vec2<u32> = sub %7, vec2<u32>(1u)
+ %9:vec2<u32> = convert %coords
+ %10:vec2<u32> = min %9, %8
+ %11:u32 = textureNumLayers %6
+ %12:u32 = sub %11, 1u
+ %13:u32 = convert %layer
+ %14:u32 = min %13, %12
+ %15:u32 = textureNumLevels %6
+ %16:u32 = sub %15, 1u
+ %17:u32 = convert %level
+ %18:u32 = min %17, %16
+ %19:vec4<f32> = textureLoad %6, %10, %14, %18
+ ret %19
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %layer_1: 'layer', %level_1: 'level'
+ %b3 = block {
+ %24:texture_2d_array<f32> = load %texture
+ %25:vec2<u32> = textureDimensions %24
+ %26:vec2<u32> = sub %25, vec2<u32>(1u)
+ %27:vec2<u32> = min %coords_1, %26
+ %28:u32 = textureNumLayers %24
+ %29:u32 = sub %28, 1u
+ %30:u32 = min %layer_1, %29
+ %31:u32 = textureNumLevels %24
+ %32:u32 = sub %31, 1u
+ %33:u32 = min %level_1, %32
+ %34:vec4<f32> = textureLoad %24, %27, %30, %33
+ ret %34
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Sampled3D) {
+ auto* texture = b.Var(
+ "texture",
+ ty.ptr(handle, ty.Get<type::SampledTexture>(type::TextureDimension::k3d, ty.f32()), read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec3<i32>());
+ auto* level = b.FunctionParam("level", ty.i32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec3<u32>());
+ auto* level = b.FunctionParam("level", ty.u32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_3d<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec3<i32>, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_3d<f32> = load %texture
+ %6:vec4<f32> = textureLoad %5, %coords, %level
+ ret %6
+ }
+}
+%load_unsigned = func(%coords_1:vec3<u32>, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %10:texture_3d<f32> = load %texture
+ %11:vec4<f32> = textureLoad %10, %coords_1, %level_1
+ ret %11
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_3d<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec3<i32>, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_3d<f32> = load %texture
+ %6:vec3<u32> = textureDimensions %5
+ %7:vec3<u32> = sub %6, vec3<u32>(1u)
+ %8:vec3<u32> = convert %coords
+ %9:vec3<u32> = min %8, %7
+ %10:u32 = textureNumLevels %5
+ %11:u32 = sub %10, 1u
+ %12:u32 = convert %level
+ %13:u32 = min %12, %11
+ %14:vec4<f32> = textureLoad %5, %9, %13
+ ret %14
+ }
+}
+%load_unsigned = func(%coords_1:vec3<u32>, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %18:texture_3d<f32> = load %texture
+ %19:vec3<u32> = textureDimensions %18
+ %20:vec3<u32> = sub %19, vec3<u32>(1u)
+ %21:vec3<u32> = min %coords_1, %20
+ %22:u32 = textureNumLevels %18
+ %23:u32 = sub %22, 1u
+ %24:u32 = min %level_1, %23
+ %25:vec4<f32> = textureLoad %18, %21, %24
+ ret %25
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Multisampled2D) {
+ auto* texture = b.Var(
+ "texture",
+ ty.ptr(handle, ty.Get<type::MultisampledTexture>(type::TextureDimension::k2d, ty.f32()),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ auto* level = b.FunctionParam("level", ty.i32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* level = b.FunctionParam("level", ty.u32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_multisampled_2d<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_multisampled_2d<f32> = load %texture
+ %6:vec4<f32> = textureLoad %5, %coords, %level
+ ret %6
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %10:texture_multisampled_2d<f32> = load %texture
+ %11:vec4<f32> = textureLoad %10, %coords_1, %level_1
+ ret %11
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_multisampled_2d<f32>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %level:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_multisampled_2d<f32> = load %texture
+ %6:vec2<u32> = textureDimensions %5
+ %7:vec2<u32> = sub %6, vec2<u32>(1u)
+ %8:vec2<u32> = convert %coords
+ %9:vec2<u32> = min %8, %7
+ %10:vec4<f32> = textureLoad %5, %9, %level
+ ret %10
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %14:texture_multisampled_2d<f32> = load %texture
+ %15:vec2<u32> = textureDimensions %14
+ %16:vec2<u32> = sub %15, vec2<u32>(1u)
+ %17:vec2<u32> = min %coords_1, %16
+ %18:vec4<f32> = textureLoad %14, %17, %level_1
+ ret %18
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Depth2D) {
+ auto* texture = b.Var(
+ "texture", ty.ptr(handle, ty.Get<type::DepthTexture>(type::TextureDimension::k2d), read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.f32());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ auto* level = b.FunctionParam("level", ty.i32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.f32());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* level = b.FunctionParam("level", ty.u32());
+ func->SetParams({coords, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, level);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_depth_2d, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %level:i32):f32 -> %b2 {
+ %b2 = block {
+ %5:texture_depth_2d = load %texture
+ %6:f32 = textureLoad %5, %coords, %level
+ ret %6
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):f32 -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %10:texture_depth_2d = load %texture
+ %11:f32 = textureLoad %10, %coords_1, %level_1
+ ret %11
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_depth_2d, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %level:i32):f32 -> %b2 {
+ %b2 = block {
+ %5:texture_depth_2d = load %texture
+ %6:vec2<u32> = textureDimensions %5
+ %7:vec2<u32> = sub %6, vec2<u32>(1u)
+ %8:vec2<u32> = convert %coords
+ %9:vec2<u32> = min %8, %7
+ %10:u32 = textureNumLevels %5
+ %11:u32 = sub %10, 1u
+ %12:u32 = convert %level
+ %13:u32 = min %12, %11
+ %14:f32 = textureLoad %5, %9, %13
+ ret %14
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):f32 -> %b3 { # %coords_1: 'coords', %level_1: 'level'
+ %b3 = block {
+ %18:texture_depth_2d = load %texture
+ %19:vec2<u32> = textureDimensions %18
+ %20:vec2<u32> = sub %19, vec2<u32>(1u)
+ %21:vec2<u32> = min %coords_1, %20
+ %22:u32 = textureNumLevels %18
+ %23:u32 = sub %22, 1u
+ %24:u32 = min %level_1, %23
+ %25:f32 = textureLoad %18, %21, %24
+ ret %25
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Depth2DArray) {
+ auto* texture =
+ b.Var("texture",
+ ty.ptr(handle, ty.Get<type::DepthTexture>(type::TextureDimension::k2dArray), read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.f32());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ auto* layer = b.FunctionParam("layer", ty.i32());
+ auto* level = b.FunctionParam("level", ty.i32());
+ func->SetParams({coords, layer, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, layer, level);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.f32());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* layer = b.FunctionParam("layer", ty.u32());
+ auto* level = b.FunctionParam("level", ty.u32());
+ func->SetParams({coords, layer, level});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, layer, level);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_depth_2d_array, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %layer:i32, %level:i32):f32 -> %b2 {
+ %b2 = block {
+ %6:texture_depth_2d_array = load %texture
+ %7:f32 = textureLoad %6, %coords, %layer, %level
+ ret %7
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %level_1:u32):f32 -> %b3 { # %coords_1: 'coords', %layer_1: 'layer', %level_1: 'level'
+ %b3 = block {
+ %12:texture_depth_2d_array = load %texture
+ %13:f32 = textureLoad %12, %coords_1, %layer_1, %level_1
+ ret %13
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_depth_2d_array, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %layer:i32, %level:i32):f32 -> %b2 {
+ %b2 = block {
+ %6:texture_depth_2d_array = load %texture
+ %7:vec2<u32> = textureDimensions %6
+ %8:vec2<u32> = sub %7, vec2<u32>(1u)
+ %9:vec2<u32> = convert %coords
+ %10:vec2<u32> = min %9, %8
+ %11:u32 = textureNumLayers %6
+ %12:u32 = sub %11, 1u
+ %13:u32 = convert %layer
+ %14:u32 = min %13, %12
+ %15:u32 = textureNumLevels %6
+ %16:u32 = sub %15, 1u
+ %17:u32 = convert %level
+ %18:u32 = min %17, %16
+ %19:f32 = textureLoad %6, %10, %14, %18
+ ret %19
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %level_1:u32):f32 -> %b3 { # %coords_1: 'coords', %layer_1: 'layer', %level_1: 'level'
+ %b3 = block {
+ %24:texture_depth_2d_array = load %texture
+ %25:vec2<u32> = textureDimensions %24
+ %26:vec2<u32> = sub %25, vec2<u32>(1u)
+ %27:vec2<u32> = min %coords_1, %26
+ %28:u32 = textureNumLayers %24
+ %29:u32 = sub %28, 1u
+ %30:u32 = min %layer_1, %29
+ %31:u32 = textureNumLevels %24
+ %32:u32 = sub %31, 1u
+ %33:u32 = min %level_1, %32
+ %34:f32 = textureLoad %24, %27, %30, %33
+ ret %34
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_DepthMultisampled2D) {
+ auto* texture = b.Var(
+ "texture",
+ ty.ptr(handle, ty.Get<type::DepthMultisampledTexture>(type::TextureDimension::k2d), read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.f32());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ auto* index = b.FunctionParam("index", ty.i32());
+ func->SetParams({coords, index});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, index);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.f32());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* index = b.FunctionParam("index", ty.u32());
+ func->SetParams({coords, index});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.f32(), core::Function::kTextureLoad, handle, coords, index);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_depth_multisampled_2d, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %index:i32):f32 -> %b2 {
+ %b2 = block {
+ %5:texture_depth_multisampled_2d = load %texture
+ %6:f32 = textureLoad %5, %coords, %index
+ ret %6
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %index_1:u32):f32 -> %b3 { # %coords_1: 'coords', %index_1: 'index'
+ %b3 = block {
+ %10:texture_depth_multisampled_2d = load %texture
+ %11:f32 = textureLoad %10, %coords_1, %index_1
+ ret %11
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_depth_multisampled_2d, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %index:i32):f32 -> %b2 {
+ %b2 = block {
+ %5:texture_depth_multisampled_2d = load %texture
+ %6:vec2<u32> = textureDimensions %5
+ %7:vec2<u32> = sub %6, vec2<u32>(1u)
+ %8:vec2<u32> = convert %coords
+ %9:vec2<u32> = min %8, %7
+ %10:f32 = textureLoad %5, %9, %index
+ ret %10
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %index_1:u32):f32 -> %b3 { # %coords_1: 'coords', %index_1: 'index'
+ %b3 = block {
+ %14:texture_depth_multisampled_2d = load %texture
+ %15:vec2<u32> = textureDimensions %14
+ %16:vec2<u32> = sub %15, vec2<u32>(1u)
+ %17:vec2<u32> = min %coords_1, %16
+ %18:f32 = textureLoad %14, %17, %index_1
+ ret %18
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_External) {
+ auto* texture = b.Var("texture", ty.ptr(handle, ty.Get<type::ExternalTexture>(), read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ func->SetParams({coords});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ func->SetParams({coords});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_external, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>):vec4<f32> -> %b2 {
+ %b2 = block {
+ %4:texture_external = load %texture
+ %5:vec4<f32> = textureLoad %4, %coords
+ ret %5
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>):vec4<f32> -> %b3 { # %coords_1: 'coords'
+ %b3 = block {
+ %8:texture_external = load %texture
+ %9:vec4<f32> = textureLoad %8, %coords_1
+ ret %9
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_external, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>):vec4<f32> -> %b2 {
+ %b2 = block {
+ %4:texture_external = load %texture
+ %5:vec2<u32> = textureDimensions %4
+ %6:vec2<u32> = sub %5, vec2<u32>(1u)
+ %7:vec2<u32> = convert %coords
+ %8:vec2<u32> = min %7, %6
+ %9:vec4<f32> = textureLoad %4, %8
+ ret %9
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>):vec4<f32> -> %b3 { # %coords_1: 'coords'
+ %b3 = block {
+ %12:texture_external = load %texture
+ %13:vec2<u32> = textureDimensions %12
+ %14:vec2<u32> = sub %13, vec2<u32>(1u)
+ %15:vec2<u32> = min %coords_1, %14
+ %16:vec4<f32> = textureLoad %12, %15
+ ret %16
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Storage1D) {
+ auto format = core::TexelFormat::kRgba8Unorm;
+ auto* texture =
+ b.Var("texture",
+ ty.ptr(handle,
+ ty.Get<type::StorageTexture>(type::TextureDimension::k1d, format, read_write,
+ type::StorageTexture::SubtypeFor(format, ty)),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.i32());
+ func->SetParams({coords});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.u32());
+ func->SetParams({coords});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_1d<rgba8unorm, read_write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %4:texture_storage_1d<rgba8unorm, read_write> = load %texture
+ %5:vec4<f32> = textureLoad %4, %coords
+ ret %5
+ }
+}
+%load_unsigned = func(%coords_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords'
+ %b3 = block {
+ %8:texture_storage_1d<rgba8unorm, read_write> = load %texture
+ %9:vec4<f32> = textureLoad %8, %coords_1
+ ret %9
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_1d<rgba8unorm, read_write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %4:texture_storage_1d<rgba8unorm, read_write> = load %texture
+ %5:u32 = textureDimensions %4
+ %6:u32 = sub %5, 1u
+ %7:u32 = convert %coords
+ %8:u32 = min %7, %6
+ %9:vec4<f32> = textureLoad %4, %8
+ ret %9
+ }
+}
+%load_unsigned = func(%coords_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords'
+ %b3 = block {
+ %12:texture_storage_1d<rgba8unorm, read_write> = load %texture
+ %13:u32 = textureDimensions %12
+ %14:u32 = sub %13, 1u
+ %15:u32 = min %coords_1, %14
+ %16:vec4<f32> = textureLoad %12, %15
+ ret %16
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Storage2D) {
+ auto format = core::TexelFormat::kRgba8Unorm;
+ auto* texture =
+ b.Var("texture",
+ ty.ptr(handle,
+ ty.Get<type::StorageTexture>(type::TextureDimension::k2d, format, read_write,
+ type::StorageTexture::SubtypeFor(format, ty)),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ func->SetParams({coords});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ func->SetParams({coords});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_2d<rgba8unorm, read_write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>):vec4<f32> -> %b2 {
+ %b2 = block {
+ %4:texture_storage_2d<rgba8unorm, read_write> = load %texture
+ %5:vec4<f32> = textureLoad %4, %coords
+ ret %5
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>):vec4<f32> -> %b3 { # %coords_1: 'coords'
+ %b3 = block {
+ %8:texture_storage_2d<rgba8unorm, read_write> = load %texture
+ %9:vec4<f32> = textureLoad %8, %coords_1
+ ret %9
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_2d<rgba8unorm, read_write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>):vec4<f32> -> %b2 {
+ %b2 = block {
+ %4:texture_storage_2d<rgba8unorm, read_write> = load %texture
+ %5:vec2<u32> = textureDimensions %4
+ %6:vec2<u32> = sub %5, vec2<u32>(1u)
+ %7:vec2<u32> = convert %coords
+ %8:vec2<u32> = min %7, %6
+ %9:vec4<f32> = textureLoad %4, %8
+ ret %9
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>):vec4<f32> -> %b3 { # %coords_1: 'coords'
+ %b3 = block {
+ %12:texture_storage_2d<rgba8unorm, read_write> = load %texture
+ %13:vec2<u32> = textureDimensions %12
+ %14:vec2<u32> = sub %13, vec2<u32>(1u)
+ %15:vec2<u32> = min %coords_1, %14
+ %16:vec4<f32> = textureLoad %12, %15
+ ret %16
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Storage2DArray) {
+ auto format = core::TexelFormat::kRgba8Unorm;
+ auto* texture = b.Var(
+ "texture",
+ ty.ptr(handle,
+ ty.Get<type::StorageTexture>(type::TextureDimension::k2dArray, format, read_write,
+ type::StorageTexture::SubtypeFor(format, ty)),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ auto* layer = b.FunctionParam("layer", ty.i32());
+ func->SetParams({coords, layer});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, layer);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* layer = b.FunctionParam("layer", ty.u32());
+ func->SetParams({coords, layer});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel =
+ b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords, layer);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_2d_array<rgba8unorm, read_write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %layer:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_storage_2d_array<rgba8unorm, read_write> = load %texture
+ %6:vec4<f32> = textureLoad %5, %coords, %layer
+ ret %6
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %layer_1: 'layer'
+ %b3 = block {
+ %10:texture_storage_2d_array<rgba8unorm, read_write> = load %texture
+ %11:vec4<f32> = textureLoad %10, %coords_1, %layer_1
+ ret %11
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_2d_array<rgba8unorm, read_write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %layer:i32):vec4<f32> -> %b2 {
+ %b2 = block {
+ %5:texture_storage_2d_array<rgba8unorm, read_write> = load %texture
+ %6:vec2<u32> = textureDimensions %5
+ %7:vec2<u32> = sub %6, vec2<u32>(1u)
+ %8:vec2<u32> = convert %coords
+ %9:vec2<u32> = min %8, %7
+ %10:u32 = textureNumLayers %5
+ %11:u32 = sub %10, 1u
+ %12:u32 = convert %layer
+ %13:u32 = min %12, %11
+ %14:vec4<f32> = textureLoad %5, %9, %13
+ ret %14
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32):vec4<f32> -> %b3 { # %coords_1: 'coords', %layer_1: 'layer'
+ %b3 = block {
+ %18:texture_storage_2d_array<rgba8unorm, read_write> = load %texture
+ %19:vec2<u32> = textureDimensions %18
+ %20:vec2<u32> = sub %19, vec2<u32>(1u)
+ %21:vec2<u32> = min %coords_1, %20
+ %22:u32 = textureNumLayers %18
+ %23:u32 = sub %22, 1u
+ %24:u32 = min %layer_1, %23
+ %25:vec4<f32> = textureLoad %18, %21, %24
+ ret %25
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureLoad_Storage3D) {
+ auto format = core::TexelFormat::kRgba8Unorm;
+ auto* texture =
+ b.Var("texture",
+ ty.ptr(handle,
+ ty.Get<type::StorageTexture>(type::TextureDimension::k3d, format, read_write,
+ type::StorageTexture::SubtypeFor(format, ty)),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec3<i32>());
+ func->SetParams({coords});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+ b.Return(func, texel);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.vec4<f32>());
+ auto* coords = b.FunctionParam("coords", ty.vec3<u32>());
+ func->SetParams({coords});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ auto* texel = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, handle, coords);
+ b.Return(func, texel);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_3d<rgba8unorm, read_write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec3<i32>):vec4<f32> -> %b2 {
+ %b2 = block {
+ %4:texture_storage_3d<rgba8unorm, read_write> = load %texture
+ %5:vec4<f32> = textureLoad %4, %coords
+ ret %5
+ }
+}
+%load_unsigned = func(%coords_1:vec3<u32>):vec4<f32> -> %b3 { # %coords_1: 'coords'
+ %b3 = block {
+ %8:texture_storage_3d<rgba8unorm, read_write> = load %texture
+ %9:vec4<f32> = textureLoad %8, %coords_1
+ ret %9
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_3d<rgba8unorm, read_write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec3<i32>):vec4<f32> -> %b2 {
+ %b2 = block {
+ %4:texture_storage_3d<rgba8unorm, read_write> = load %texture
+ %5:vec3<u32> = textureDimensions %4
+ %6:vec3<u32> = sub %5, vec3<u32>(1u)
+ %7:vec3<u32> = convert %coords
+ %8:vec3<u32> = min %7, %6
+ %9:vec4<f32> = textureLoad %4, %8
+ ret %9
+ }
+}
+%load_unsigned = func(%coords_1:vec3<u32>):vec4<f32> -> %b3 { # %coords_1: 'coords'
+ %b3 = block {
+ %12:texture_storage_3d<rgba8unorm, read_write> = load %texture
+ %13:vec3<u32> = textureDimensions %12
+ %14:vec3<u32> = sub %13, vec3<u32>(1u)
+ %15:vec3<u32> = min %coords_1, %14
+ %16:vec4<f32> = textureLoad %12, %15
+ ret %16
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureStore_Storage1D) {
+ auto format = core::TexelFormat::kRgba8Unorm;
+ auto* texture =
+ b.Var("texture",
+ ty.ptr(handle,
+ ty.Get<type::StorageTexture>(type::TextureDimension::k1d, format, write,
+ type::StorageTexture::SubtypeFor(format, ty)),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.void_());
+ auto* coords = b.FunctionParam("coords", ty.i32());
+ auto* value = b.FunctionParam("value", ty.vec4<f32>());
+ func->SetParams({coords, value});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ b.Call(ty.vec4<f32>(), core::Function::kTextureStore, handle, coords, value);
+ b.Return(func);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.void_());
+ auto* coords = b.FunctionParam("coords", ty.u32());
+ auto* value = b.FunctionParam("value", ty.vec4<f32>());
+ func->SetParams({coords, value});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ b.Call(ty.vec4<f32>(), core::Function::kTextureStore, handle, coords, value);
+ b.Return(func);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_1d<rgba8unorm, write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:i32, %value:vec4<f32>):void -> %b2 {
+ %b2 = block {
+ %5:texture_storage_1d<rgba8unorm, write> = load %texture
+ %6:vec4<f32> = textureStore %5, %coords, %value
+ ret
+ }
+}
+%load_unsigned = func(%coords_1:u32, %value_1:vec4<f32>):void -> %b3 { # %coords_1: 'coords', %value_1: 'value'
+ %b3 = block {
+ %10:texture_storage_1d<rgba8unorm, write> = load %texture
+ %11:vec4<f32> = textureStore %10, %coords_1, %value_1
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_1d<rgba8unorm, write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:i32, %value:vec4<f32>):void -> %b2 {
+ %b2 = block {
+ %5:texture_storage_1d<rgba8unorm, write> = load %texture
+ %6:u32 = textureDimensions %5
+ %7:u32 = sub %6, 1u
+ %8:u32 = convert %coords
+ %9:u32 = min %8, %7
+ %10:vec4<f32> = textureStore %5, %9, %value
+ ret
+ }
+}
+%load_unsigned = func(%coords_1:u32, %value_1:vec4<f32>):void -> %b3 { # %coords_1: 'coords', %value_1: 'value'
+ %b3 = block {
+ %14:texture_storage_1d<rgba8unorm, write> = load %texture
+ %15:u32 = textureDimensions %14
+ %16:u32 = sub %15, 1u
+ %17:u32 = min %coords_1, %16
+ %18:vec4<f32> = textureStore %14, %17, %value_1
+ ret
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureStore_Storage2D) {
+ auto format = core::TexelFormat::kRgba8Unorm;
+ auto* texture =
+ b.Var("texture",
+ ty.ptr(handle,
+ ty.Get<type::StorageTexture>(type::TextureDimension::k2d, format, write,
+ type::StorageTexture::SubtypeFor(format, ty)),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.void_());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ auto* value = b.FunctionParam("value", ty.vec4<f32>());
+ func->SetParams({coords, value});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ b.Call(ty.vec4<f32>(), core::Function::kTextureStore, handle, coords, value);
+ b.Return(func);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.void_());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* value = b.FunctionParam("value", ty.vec4<f32>());
+ func->SetParams({coords, value});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ b.Call(ty.vec4<f32>(), core::Function::kTextureStore, handle, coords, value);
+ b.Return(func);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_2d<rgba8unorm, write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %value:vec4<f32>):void -> %b2 {
+ %b2 = block {
+ %5:texture_storage_2d<rgba8unorm, write> = load %texture
+ %6:vec4<f32> = textureStore %5, %coords, %value
+ ret
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %value_1:vec4<f32>):void -> %b3 { # %coords_1: 'coords', %value_1: 'value'
+ %b3 = block {
+ %10:texture_storage_2d<rgba8unorm, write> = load %texture
+ %11:vec4<f32> = textureStore %10, %coords_1, %value_1
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_2d<rgba8unorm, write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %value:vec4<f32>):void -> %b2 {
+ %b2 = block {
+ %5:texture_storage_2d<rgba8unorm, write> = load %texture
+ %6:vec2<u32> = textureDimensions %5
+ %7:vec2<u32> = sub %6, vec2<u32>(1u)
+ %8:vec2<u32> = convert %coords
+ %9:vec2<u32> = min %8, %7
+ %10:vec4<f32> = textureStore %5, %9, %value
+ ret
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %value_1:vec4<f32>):void -> %b3 { # %coords_1: 'coords', %value_1: 'value'
+ %b3 = block {
+ %14:texture_storage_2d<rgba8unorm, write> = load %texture
+ %15:vec2<u32> = textureDimensions %14
+ %16:vec2<u32> = sub %15, vec2<u32>(1u)
+ %17:vec2<u32> = min %coords_1, %16
+ %18:vec4<f32> = textureStore %14, %17, %value_1
+ ret
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureStore_Storage2DArray) {
+ auto format = core::TexelFormat::kRgba8Unorm;
+ auto* texture =
+ b.Var("texture",
+ ty.ptr(handle,
+ ty.Get<type::StorageTexture>(type::TextureDimension::k2dArray, format, write,
+ type::StorageTexture::SubtypeFor(format, ty)),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.void_());
+ auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
+ auto* layer = b.FunctionParam("layer", ty.i32());
+ auto* value = b.FunctionParam("value", ty.vec4<f32>());
+ func->SetParams({coords, layer, value});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ b.Call(ty.vec4<f32>(), core::Function::kTextureStore, handle, coords, layer, value);
+ b.Return(func);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.void_());
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* layer = b.FunctionParam("layer", ty.u32());
+ auto* value = b.FunctionParam("value", ty.vec4<f32>());
+ func->SetParams({coords, layer, value});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ b.Call(ty.vec4<f32>(), core::Function::kTextureStore, handle, coords, layer, value);
+ b.Return(func);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_2d_array<rgba8unorm, write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %layer:i32, %value:vec4<f32>):void -> %b2 {
+ %b2 = block {
+ %6:texture_storage_2d_array<rgba8unorm, write> = load %texture
+ %7:vec4<f32> = textureStore %6, %coords, %layer, %value
+ ret
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %value_1:vec4<f32>):void -> %b3 { # %coords_1: 'coords', %layer_1: 'layer', %value_1: 'value'
+ %b3 = block {
+ %12:texture_storage_2d_array<rgba8unorm, write> = load %texture
+ %13:vec4<f32> = textureStore %12, %coords_1, %layer_1, %value_1
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_2d_array<rgba8unorm, write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec2<i32>, %layer:i32, %value:vec4<f32>):void -> %b2 {
+ %b2 = block {
+ %6:texture_storage_2d_array<rgba8unorm, write> = load %texture
+ %7:vec2<u32> = textureDimensions %6
+ %8:vec2<u32> = sub %7, vec2<u32>(1u)
+ %9:vec2<u32> = convert %coords
+ %10:vec2<u32> = min %9, %8
+ %11:u32 = textureNumLayers %6
+ %12:u32 = sub %11, 1u
+ %13:u32 = convert %layer
+ %14:u32 = min %13, %12
+ %15:vec4<f32> = textureStore %6, %10, %14, %value
+ ret
+ }
+}
+%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %value_1:vec4<f32>):void -> %b3 { # %coords_1: 'coords', %layer_1: 'layer', %value_1: 'value'
+ %b3 = block {
+ %20:texture_storage_2d_array<rgba8unorm, write> = load %texture
+ %21:vec2<u32> = textureDimensions %20
+ %22:vec2<u32> = sub %21, vec2<u32>(1u)
+ %23:vec2<u32> = min %coords_1, %22
+ %24:u32 = textureNumLayers %20
+ %25:u32 = sub %24, 1u
+ %26:u32 = min %layer_1, %25
+ %27:vec4<f32> = textureStore %20, %23, %26, %value_1
+ ret
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
+TEST_P(IR_RobustnessTest, TextureStore_Storage3D) {
+ auto format = core::TexelFormat::kRgba8Unorm;
+ auto* texture =
+ b.Var("texture",
+ ty.ptr(handle,
+ ty.Get<type::StorageTexture>(type::TextureDimension::k3d, format, write,
+ type::StorageTexture::SubtypeFor(format, ty)),
+ read));
+ texture->SetBindingPoint(0, 0);
+ b.RootBlock()->Append(texture);
+
+ {
+ auto* func = b.Function("load_signed", ty.void_());
+ auto* coords = b.FunctionParam("coords", ty.vec3<i32>());
+ auto* value = b.FunctionParam("value", ty.vec4<f32>());
+ func->SetParams({coords, value});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ b.Call(ty.vec4<f32>(), core::Function::kTextureStore, handle, coords, value);
+ b.Return(func);
+ });
+ }
+
+ {
+ auto* func = b.Function("load_unsigned", ty.void_());
+ auto* coords = b.FunctionParam("coords", ty.vec3<u32>());
+ auto* value = b.FunctionParam("value", ty.vec4<f32>());
+ func->SetParams({coords, value});
+ b.Append(func->Block(), [&] {
+ auto* handle = b.Load(texture);
+ b.Call(ty.vec4<f32>(), core::Function::kTextureStore, handle, coords, value);
+ b.Return(func);
+ });
+ }
+
+ auto* src = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_3d<rgba8unorm, write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec3<i32>, %value:vec4<f32>):void -> %b2 {
+ %b2 = block {
+ %5:texture_storage_3d<rgba8unorm, write> = load %texture
+ %6:vec4<f32> = textureStore %5, %coords, %value
+ ret
+ }
+}
+%load_unsigned = func(%coords_1:vec3<u32>, %value_1:vec4<f32>):void -> %b3 { # %coords_1: 'coords', %value_1: 'value'
+ %b3 = block {
+ %10:texture_storage_3d<rgba8unorm, write> = load %texture
+ %11:vec4<f32> = textureStore %10, %coords_1, %value_1
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %texture:ptr<handle, texture_storage_3d<rgba8unorm, write>, read> = var @binding_point(0, 0)
+}
+
+%load_signed = func(%coords:vec3<i32>, %value:vec4<f32>):void -> %b2 {
+ %b2 = block {
+ %5:texture_storage_3d<rgba8unorm, write> = load %texture
+ %6:vec3<u32> = textureDimensions %5
+ %7:vec3<u32> = sub %6, vec3<u32>(1u)
+ %8:vec3<u32> = convert %coords
+ %9:vec3<u32> = min %8, %7
+ %10:vec4<f32> = textureStore %5, %9, %value
+ ret
+ }
+}
+%load_unsigned = func(%coords_1:vec3<u32>, %value_1:vec4<f32>):void -> %b3 { # %coords_1: 'coords', %value_1: 'value'
+ %b3 = block {
+ %14:texture_storage_3d<rgba8unorm, write> = load %texture
+ %15:vec3<u32> = textureDimensions %14
+ %16:vec3<u32> = sub %15, vec3<u32>(1u)
+ %17:vec3<u32> = min %coords_1, %16
+ %18:vec4<f32> = textureStore %14, %17, %value_1
+ ret
+ }
+}
+)";
+
+ RobustnessConfig cfg;
+ cfg.clamp_texture = GetParam();
+ Run(Robustness, cfg);
+
+ EXPECT_EQ(GetParam() ? expect : src, str());
+}
+
} // namespace
} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/spirv/writer/common/helper_test.h b/src/tint/lang/spirv/writer/common/helper_test.h
index b1e7f5c..5e727a9 100644
--- a/src/tint/lang/spirv/writer/common/helper_test.h
+++ b/src/tint/lang/spirv/writer/common/helper_test.h
@@ -100,9 +100,10 @@
/// Run the specified writer on the IR module and validate the result.
/// @param writer the writer to use for SPIR-V generation
+ /// @param options the optional writer options to use when raising the IR
/// @returns true if generation and validation succeeded
- bool Generate(Printer& writer) {
- auto raised = raise::Raise(&mod, {});
+ bool Generate(Printer& writer, Options options = {}) {
+ auto raised = raise::Raise(&mod, options);
if (!raised) {
err_ = raised.Failure();
return false;
@@ -126,8 +127,9 @@
}
/// Run the writer on the IR module and validate the result.
+ /// @param options the optional writer options to use when raising the IR
/// @returns true if generation and validation succeeded
- bool Generate() { return Generate(writer_); }
+ bool Generate(Options options = {}) { return Generate(writer_, options); }
/// Validate the generated SPIR-V using the SPIR-V Tools Validator.
/// @param binary the SPIR-V binary module to validate
diff --git a/src/tint/lang/spirv/writer/texture_builtin_test.cc b/src/tint/lang/spirv/writer/texture_builtin_test.cc
index c257bf7..1b228ec 100644
--- a/src/tint/lang/spirv/writer/texture_builtin_test.cc
+++ b/src/tint/lang/spirv/writer/texture_builtin_test.cc
@@ -191,7 +191,9 @@
}
});
- ASSERT_TRUE(Generate()) << Error() << output_;
+ Options options;
+ options.disable_image_robustness = true;
+ ASSERT_TRUE(Generate(options)) << Error() << output_;
for (auto& inst : params.instructions) {
EXPECT_INST(inst);
}
@@ -1928,12 +1930,103 @@
b.Return(func);
});
- ASSERT_TRUE(Generate()) << Error() << output_;
+ Options options;
+ options.disable_image_robustness = true;
+ ASSERT_TRUE(Generate(options)) << Error() << output_;
EXPECT_INST(R"(
%13 = OpVectorShuffle %v4float %value %value 2 1 0 3
OpImageWrite %texture %coords %13 None
)");
}
+////////////////////////////////////////////////////////////////
+//// Texture robustness enabled.
+////////////////////////////////////////////////////////////////
+
+TEST_F(SpirvWriterTest, TextureDimensions_WithRobustness) {
+ auto* texture_ty =
+ ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
+
+ auto* texture = b.FunctionParam("texture", texture_ty);
+ auto* level = b.FunctionParam("level", ty.i32());
+ auto* func = b.Function("foo", ty.vec2<u32>());
+ func->SetParams({texture, level});
+ b.Append(func->Block(), [&] {
+ auto* dims = b.Call(ty.vec2<u32>(), core::Function::kTextureDimensions, texture, level);
+ b.Return(func, dims);
+ mod.SetName(dims, "dims");
+ });
+
+ ASSERT_TRUE(Generate()) << Error() << output_;
+ EXPECT_INST(R"(
+ %11 = OpImageQueryLevels %uint %texture
+ %12 = OpISub %uint %11 %uint_1
+ %14 = OpBitcast %uint %level
+ %15 = OpExtInst %uint %16 UMin %14 %12
+ %dims = OpImageQuerySizeLod %v2uint %texture %15
+)");
+}
+
+TEST_F(SpirvWriterTest, TextureLoad_WithRobustness) {
+ auto* texture_ty =
+ ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
+
+ auto* texture = b.FunctionParam("texture", texture_ty);
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* level = b.FunctionParam("level", ty.i32());
+ auto* func = b.Function("foo", ty.vec4<f32>());
+ func->SetParams({texture, coords, level});
+ b.Append(func->Block(), [&] {
+ auto* result = b.Call(ty.vec4<f32>(), core::Function::kTextureLoad, texture, coords, level);
+ b.Return(func, result);
+ mod.SetName(result, "result");
+ });
+
+ ASSERT_TRUE(Generate()) << Error() << output_;
+ EXPECT_INST(R"(
+ %13 = OpImageQuerySizeLod %v2uint %texture %uint_0
+ %15 = OpISub %v2uint %13 %16
+ %18 = OpExtInst %v2uint %19 UMin %coords %15
+ %20 = OpImageQueryLevels %uint %texture
+ %21 = OpISub %uint %20 %uint_1
+ %22 = OpBitcast %uint %level
+ %23 = OpExtInst %uint %19 UMin %22 %21
+ %result = OpImageFetch %v4float %texture %18 Lod %23
+)");
+}
+
+TEST_F(SpirvWriterTest, TextureStore_WithRobustness) {
+ auto format = core::TexelFormat::kRgba8Unorm;
+ auto* texture_ty = ty.Get<core::type::StorageTexture>(
+ core::type::TextureDimension::k2dArray, format, core::Access::kWrite,
+ core::type::StorageTexture::SubtypeFor(format, ty));
+
+ auto* texture = b.FunctionParam("texture", texture_ty);
+ auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
+ auto* layer = b.FunctionParam("layer", ty.i32());
+ auto* value = b.FunctionParam("value", ty.vec4<f32>());
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({texture, coords, layer, value});
+ b.Append(func->Block(), [&] {
+ b.Call(ty.void_(), core::Function::kTextureStore, texture, coords, layer, value);
+ b.Return(func);
+ });
+
+ ASSERT_TRUE(Generate()) << Error() << output_;
+ EXPECT_INST(R"(
+ %15 = OpImageQuerySize %v3uint %texture
+ %17 = OpVectorShuffle %v2uint %15 %15 0 1
+ %18 = OpISub %v2uint %17 %19
+ %21 = OpExtInst %v2uint %22 UMin %coords %18
+ %23 = OpImageQuerySize %v3uint %texture
+ %24 = OpCompositeExtract %uint %23 2
+ %25 = OpISub %uint %24 %uint_1
+ %26 = OpBitcast %uint %layer
+ %27 = OpExtInst %uint %22 UMin %26 %25
+ %28 = OpCompositeConstruct %v3uint %21 %27
+ OpImageWrite %texture %28 %value None
+)");
+}
+
} // namespace
} // namespace tint::spirv::writer
diff --git a/src/tint/lang/wgsl/ast/transform/robustness_test.cc b/src/tint/lang/wgsl/ast/transform/robustness_test.cc
index c2d3fce..e2074ef 100644
--- a/src/tint/lang/wgsl/ast/transform/robustness_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/robustness_test.cc
@@ -1788,7 +1788,7 @@
}
////////////////////////////////////////////////////////////////////////////////
-// Texture
+// Texture builtin calls.
////////////////////////////////////////////////////////////////////////////////
TEST_P(RobustnessTest, TextureDimensions) {