tint: Add bgra8unorm storage texture support

Polyfill this for the SPIR-V, HLSL and GLSL backends by replacing bgra8unorm with rgba8unorm, and swizzling.

Bug: tint:1804
Change-Id: I36638202840d7313001dff6c5b60dcb948988c34
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/117204
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Ben Clayton
2023-01-18 19:42:03 +00:00
committed by Dawn LUCI CQ
parent e2be18a7fd
commit d03dceebf3
240 changed files with 9683 additions and 249 deletions

View File

@@ -833,160 +833,206 @@ Transform::ApplyResult BuiltinPolyfill::Apply(const Program* src,
bool made_changes = false;
for (auto* node : src->ASTNodes().Objects()) {
auto* expr = src->Sem().Get<sem::Expression>(node);
if (!expr || expr->Stage() == sem::EvaluationStage::kConstant ||
expr->Stage() == sem::EvaluationStage::kNotEvaluated) {
continue; // Don't polyfill @const expressions
}
if (auto* call = expr->As<sem::Call>()) {
auto* builtin = call->Target()->As<sem::Builtin>();
if (!builtin) {
continue;
}
Symbol fn;
switch (builtin->Type()) {
case sem::BuiltinType::kAcosh:
if (polyfill.acosh != Level::kNone) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.acosh(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kAsinh:
if (polyfill.asinh) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.asinh(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kAtanh:
if (polyfill.atanh != Level::kNone) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.atanh(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kClamp:
if (polyfill.clamp_int) {
auto& sig = builtin->Signature();
if (sig.parameters[0]->Type()->is_integer_scalar_or_vector()) {
Switch(
node,
[&](const ast::CallExpression* expr) {
auto* call = src->Sem().Get(expr)->UnwrapMaterialize()->As<sem::Call>();
if (!call || call->Stage() == sem::EvaluationStage::kConstant ||
call->Stage() == sem::EvaluationStage::kNotEvaluated) {
return; // Don't polyfill @const expressions
}
auto* builtin = call->Target()->As<sem::Builtin>();
if (!builtin) {
return;
}
Symbol fn;
switch (builtin->Type()) {
case sem::BuiltinType::kAcosh:
if (polyfill.acosh != Level::kNone) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.clampInteger(builtin->ReturnType()); });
builtin, [&] { return s.acosh(builtin->ReturnType()); });
}
}
break;
case sem::BuiltinType::kCountLeadingZeros:
if (polyfill.count_leading_zeros) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.countLeadingZeros(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kCountTrailingZeros:
if (polyfill.count_trailing_zeros) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.countTrailingZeros(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kExtractBits:
if (polyfill.extract_bits != Level::kNone) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.extractBits(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kFirstLeadingBit:
if (polyfill.first_leading_bit) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.firstLeadingBit(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kFirstTrailingBit:
if (polyfill.first_trailing_bit) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.firstTrailingBit(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kInsertBits:
if (polyfill.insert_bits != Level::kNone) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.insertBits(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kSaturate:
if (polyfill.saturate) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.saturate(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kSign:
if (polyfill.sign_int) {
auto* ty = builtin->ReturnType();
if (ty->is_signed_integer_scalar_or_vector()) {
fn = builtin_polyfills.GetOrCreate(builtin,
[&] { return s.sign_int(ty); });
break;
case sem::BuiltinType::kAsinh:
if (polyfill.asinh) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.asinh(builtin->ReturnType()); });
}
}
break;
case sem::BuiltinType::kTextureSampleBaseClampToEdge:
if (polyfill.texture_sample_base_clamp_to_edge_2d_f32) {
auto& sig = builtin->Signature();
auto* tex = sig.Parameter(sem::ParameterUsage::kTexture);
if (auto* stex = tex->Type()->As<type::SampledTexture>()) {
if (stex->type()->Is<type::F32>()) {
fn = builtin_polyfills.GetOrCreate(builtin, [&] {
return s.textureSampleBaseClampToEdge_2d_f32();
});
break;
case sem::BuiltinType::kAtanh:
if (polyfill.atanh != Level::kNone) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.atanh(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kClamp:
if (polyfill.clamp_int) {
auto& sig = builtin->Signature();
if (sig.parameters[0]->Type()->is_integer_scalar_or_vector()) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.clampInteger(builtin->ReturnType()); });
}
}
}
break;
case sem::BuiltinType::kQuantizeToF16:
if (polyfill.quantize_to_vec_f16) {
if (auto* vec = builtin->ReturnType()->As<type::Vector>()) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.quantizeToF16(vec); });
break;
case sem::BuiltinType::kCountLeadingZeros:
if (polyfill.count_leading_zeros) {
fn = builtin_polyfills.GetOrCreate(builtin, [&] {
return s.countLeadingZeros(builtin->ReturnType());
});
}
}
break;
break;
case sem::BuiltinType::kCountTrailingZeros:
if (polyfill.count_trailing_zeros) {
fn = builtin_polyfills.GetOrCreate(builtin, [&] {
return s.countTrailingZeros(builtin->ReturnType());
});
}
break;
case sem::BuiltinType::kExtractBits:
if (polyfill.extract_bits != Level::kNone) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.extractBits(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kFirstLeadingBit:
if (polyfill.first_leading_bit) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.firstLeadingBit(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kFirstTrailingBit:
if (polyfill.first_trailing_bit) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.firstTrailingBit(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kInsertBits:
if (polyfill.insert_bits != Level::kNone) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.insertBits(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kSaturate:
if (polyfill.saturate) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.saturate(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kSign:
if (polyfill.sign_int) {
auto* ty = builtin->ReturnType();
if (ty->is_signed_integer_scalar_or_vector()) {
fn = builtin_polyfills.GetOrCreate(builtin,
[&] { return s.sign_int(ty); });
}
}
break;
case sem::BuiltinType::kTextureSampleBaseClampToEdge:
if (polyfill.texture_sample_base_clamp_to_edge_2d_f32) {
auto& sig = builtin->Signature();
auto* tex = sig.Parameter(sem::ParameterUsage::kTexture);
if (auto* stex = tex->Type()->As<type::SampledTexture>()) {
if (stex->type()->Is<type::F32>()) {
fn = builtin_polyfills.GetOrCreate(builtin, [&] {
return s.textureSampleBaseClampToEdge_2d_f32();
});
}
}
}
break;
case sem::BuiltinType::kTextureStore:
if (polyfill.bgra8unorm) {
auto& sig = builtin->Signature();
auto* tex = sig.Parameter(sem::ParameterUsage::kTexture);
if (auto* stex = tex->Type()->As<type::StorageTexture>()) {
if (stex->texel_format() == ast::TexelFormat::kBgra8Unorm) {
size_t value_idx = static_cast<size_t>(
sig.IndexOf(sem::ParameterUsage::kValue));
ctx.Replace(expr, [&ctx, expr, value_idx] {
utils::Vector<const ast::Expression*, 3> args;
for (auto* arg : expr->args) {
arg = ctx.Clone(arg);
if (args.Length() == value_idx) { // value
arg = ctx.dst->MemberAccessor(arg, "bgra");
}
args.Push(arg);
}
return ctx.dst->Call(
utils::ToString(sem::BuiltinType::kTextureStore),
std::move(args));
});
made_changes = true;
}
}
}
break;
case sem::BuiltinType::kQuantizeToF16:
if (polyfill.quantize_to_vec_f16) {
if (auto* vec = builtin->ReturnType()->As<type::Vector>()) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.quantizeToF16(vec); });
}
}
break;
case sem::BuiltinType::kWorkgroupUniformLoad:
if (polyfill.workgroup_uniform_load) {
fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.workgroupUniformLoad(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kWorkgroupUniformLoad:
if (polyfill.workgroup_uniform_load) {
fn = builtin_polyfills.GetOrCreate(builtin, [&] {
return s.workgroupUniformLoad(builtin->ReturnType());
});
}
break;
default:
break;
}
if (fn.IsValid()) {
auto* replacement = b.Call(fn, ctx.Clone(call->Declaration()->args));
ctx.Replace(call->Declaration(), replacement);
made_changes = true;
}
} else if (auto* bin_op = node->As<ast::BinaryExpression>()) {
switch (bin_op->op) {
case ast::BinaryOp::kShiftLeft:
case ast::BinaryOp::kShiftRight: {
if (polyfill.bitshift_modulo) {
ctx.Replace(bin_op, [bin_op, &s] { return s.BitshiftModulo(bin_op); });
made_changes = true;
}
break;
default:
break;
}
case ast::BinaryOp::kDivide:
case ast::BinaryOp::kModulo: {
if (polyfill.int_div_mod) {
auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
if (lhs_ty->is_integer_scalar_or_vector()) {
ctx.Replace(bin_op, [bin_op, &s] { return s.IntDivMod(bin_op); });
if (fn.IsValid()) {
ctx.Replace(call->Declaration(), [&ctx, fn, expr] {
return ctx.dst->Call(fn, ctx.Clone(expr->args));
});
made_changes = true;
}
},
[&](const ast::BinaryExpression* bin_op) {
if (auto* sem = src->Sem().Get(bin_op);
!sem || sem->Stage() == sem::EvaluationStage::kConstant ||
sem->Stage() == sem::EvaluationStage::kNotEvaluated) {
return; // Don't polyfill @const expressions
}
switch (bin_op->op) {
case ast::BinaryOp::kShiftLeft:
case ast::BinaryOp::kShiftRight: {
if (polyfill.bitshift_modulo) {
ctx.Replace(bin_op, [bin_op, &s] { return s.BitshiftModulo(bin_op); });
made_changes = true;
}
break;
}
break;
case ast::BinaryOp::kDivide:
case ast::BinaryOp::kModulo: {
if (polyfill.int_div_mod) {
auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
if (lhs_ty->is_integer_scalar_or_vector()) {
ctx.Replace(bin_op, [bin_op, &s] { return s.IntDivMod(bin_op); });
made_changes = true;
}
}
break;
}
default:
break;
}
default:
break;
}
}
},
[&](const ast::StorageTexture* tex) {
if (polyfill.bgra8unorm && tex->format == ast::TexelFormat::kBgra8Unorm) {
ctx.Replace(tex, [&ctx, tex] {
return ctx.dst->ty.storage_texture(tex->dim, ast::TexelFormat::kRgba8Unorm,
tex->access);
});
made_changes = true;
}
});
}
if (!made_changes) {

View File

@@ -47,6 +47,8 @@ class BuiltinPolyfill final : public Castable<BuiltinPolyfill, Transform> {
bool asinh = false;
/// What level should `atanh` be polyfilled?
Level atanh = Level::kNone;
/// Should storage textures of format 'bgra8unorm' be replaced with 'rgba8unorm'?
bool bgra8unorm = false;
/// Should the RHS of `<<` and `>>` be wrapped in a modulo bit-width of LHS?
bool bitshift_modulo = false;
/// Should `clamp()` be polyfilled for integer values (scalar or vector)?

View File

@@ -380,6 +380,138 @@ fn f() {
EXPECT_EQ(expect, str(got));
}
////////////////////////////////////////////////////////////////////////////////
// bgra8unorm
////////////////////////////////////////////////////////////////////////////////
DataMap polyfillBgra8unorm() {
BuiltinPolyfill::Builtins builtins;
builtins.bgra8unorm = true;
DataMap data;
data.Add<BuiltinPolyfill::Config>(builtins);
return data;
}
TEST_F(BuiltinPolyfillTest, ShouldRunBgra8unorm_StorageTextureVar) {
auto* src = R"(
@group(0) @binding(0) var tex : texture_storage_3d<bgra8unorm, write>;
)";
EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(src, polyfillBgra8unorm()));
}
TEST_F(BuiltinPolyfillTest, ShouldRunBgra8unorm_StorageTextureParam) {
auto* src = R"(
fn f(tex : texture_storage_3d<bgra8unorm, write>) {
}
)";
EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(src, polyfillBgra8unorm()));
}
TEST_F(BuiltinPolyfillTest, Bgra8unorm_StorageTextureVar) {
auto* src = R"(
@group(0) @binding(0) var tex : texture_storage_3d<bgra8unorm, write>;
)";
auto* expect = R"(
@group(0) @binding(0) var tex : texture_storage_3d<rgba8unorm, write>;
)";
auto got = Run<BuiltinPolyfill>(src, polyfillBgra8unorm());
EXPECT_EQ(expect, str(got));
}
TEST_F(BuiltinPolyfillTest, Bgra8unorm_StorageTextureParam) {
auto* src = R"(
fn f(tex : texture_storage_3d<bgra8unorm, write>) {
}
)";
auto* expect = R"(
fn f(tex : texture_storage_3d<rgba8unorm, write>) {
}
)";
auto got = Run<BuiltinPolyfill>(src, polyfillBgra8unorm());
EXPECT_EQ(expect, str(got));
}
TEST_F(BuiltinPolyfillTest, Bgra8unorm_TextureStore) {
auto* src = R"(
@group(0) @binding(0) var tex : texture_storage_2d<bgra8unorm, write>;
fn f(coords : vec2<i32>, value : vec4<f32>) {
textureStore(tex, coords, value);
}
)";
auto* expect = R"(
@group(0) @binding(0) var tex : texture_storage_2d<rgba8unorm, write>;
fn f(coords : vec2<i32>, value : vec4<f32>) {
textureStore(tex, coords, value.bgra);
}
)";
auto got = Run<BuiltinPolyfill>(src, polyfillBgra8unorm());
EXPECT_EQ(expect, str(got));
}
TEST_F(BuiltinPolyfillTest, Bgra8unorm_TextureStore_Param) {
auto* src = R"(
fn f(tex : texture_storage_2d<bgra8unorm, write>, coords : vec2<i32>, value : vec4<f32>) {
textureStore(tex, coords, value);
}
)";
auto* expect = R"(
fn f(tex : texture_storage_2d<rgba8unorm, write>, coords : vec2<i32>, value : vec4<f32>) {
textureStore(tex, coords, value.bgra);
}
)";
auto got = Run<BuiltinPolyfill>(src, polyfillBgra8unorm());
EXPECT_EQ(expect, str(got));
}
TEST_F(BuiltinPolyfillTest, Bgra8unorm_TextureStore_WithAtanh) {
auto* src = R"(
@group(0) @binding(0) var tex : texture_storage_2d<bgra8unorm, write>;
fn f(coords : vec2<i32>, value : vec4<f32>) {
textureStore(tex, coords, atanh(value));
}
)";
auto* expect = R"(
fn tint_atanh(x : vec4<f32>) -> vec4<f32> {
return (log(((1 + x) / (1 - x))) * 0.5);
}
@group(0) @binding(0) var tex : texture_storage_2d<rgba8unorm, write>;
fn f(coords : vec2<i32>, value : vec4<f32>) {
textureStore(tex, coords, tint_atanh(value).bgra);
}
)";
BuiltinPolyfill::Builtins builtins;
builtins.atanh = BuiltinPolyfill::Level::kFull;
builtins.bgra8unorm = true;
DataMap data;
data.Add<BuiltinPolyfill::Config>(builtins);
auto got = Run<BuiltinPolyfill>(src, std::move(data));
EXPECT_EQ(expect, str(got));
}
////////////////////////////////////////////////////////////////////////////////
// bitshiftModulo
////////////////////////////////////////////////////////////////////////////////