blob: 943da0df8c6d85513d5a0fe097c8f2d6be3363ce [file] [log] [blame]
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/transform/demote_to_helper.h"
#include <utility>
#include "src/tint/transform/test_helper.h"
namespace tint::transform {
namespace {
using DemoteToHelperTest = TransformTest;
TEST_F(DemoteToHelperTest, ShouldRunEmptyModule) {
auto* src = R"()";
EXPECT_FALSE(ShouldRun<DemoteToHelper>(src));
}
TEST_F(DemoteToHelperTest, ShouldRunNoDiscard) {
auto* src = R"(
@group(0) @binding(0)
var<storage, read_write> v : f32;
@fragment
fn foo() {
v = 42;
}
)";
EXPECT_FALSE(ShouldRun<DemoteToHelper>(src));
}
TEST_F(DemoteToHelperTest, ShouldRunDiscardInEntryPoint) {
auto* src = R"(
@group(0) @binding(0)
var<storage, read_write> v : f32;
@fragment
fn foo() {
discard;
v = 42;
}
)";
EXPECT_TRUE(ShouldRun<DemoteToHelper>(src));
}
TEST_F(DemoteToHelperTest, ShouldRunDiscardInHelper) {
auto* src = R"(
@group(0) @binding(0)
var<storage, read_write> v : f32;
fn bar() {
discard;
}
@fragment
fn foo() {
bar();
v = 42;
}
)";
EXPECT_TRUE(ShouldRun<DemoteToHelper>(src));
}
TEST_F(DemoteToHelperTest, EmptyModule) {
auto* src = R"()";
auto* expect = src;
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, WriteInEntryPoint_DiscardInEntryPoint) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
discard;
}
let ret = textureSample(t, s, coord);
v = ret.x;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
tint_discarded = true;
}
let ret = textureSample(t, s, coord);
if (!(tint_discarded)) {
v = ret.x;
}
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, WriteInEntryPoint_DiscardInHelper) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
fn bar() {
discard;
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
bar();
}
let ret = textureSample(t, s, coord);
v = ret.x;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
fn bar() {
tint_discarded = true;
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
bar();
}
let ret = textureSample(t, s, coord);
if (!(tint_discarded)) {
v = ret.x;
}
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, WriteInHelper_DiscardInEntryPoint) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
fn bar(coord : vec2<f32>) {
let ret = textureSample(t, s, coord);
v = ret.x;
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
discard;
}
bar(coord);
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
fn bar(coord : vec2<f32>) {
let ret = textureSample(t, s, coord);
if (!(tint_discarded)) {
v = ret.x;
}
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
tint_discarded = true;
}
bar(coord);
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, WriteInHelper_DiscardInHelper) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
fn bar(in : f32, coord : vec2<f32>) {
if (in == 0.0) {
discard;
}
let ret = textureSample(t, s, coord);
v = ret.x;
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
bar(in, coord);
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
fn bar(in : f32, coord : vec2<f32>) {
if ((in == 0.0)) {
tint_discarded = true;
}
let ret = textureSample(t, s, coord);
if (!(tint_discarded)) {
v = ret.x;
}
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
bar(in, coord);
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, WriteInEntryPoint_NoDiscard) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
let ret = textureSample(t, s, coord);
v = ret.x;
}
)";
auto* expect = src;
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
// Test that no additional discards are inserted when the function unconditionally returns in a
// nested block.
TEST_F(DemoteToHelperTest, EntryPointReturn_NestedInBlock) {
auto* src = R"(
@fragment
fn foo() {
{
discard;
return;
}
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@fragment
fn foo() {
{
tint_discarded = true;
if (tint_discarded) {
discard;
}
return;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
// Test that a discard statement is inserted before every return statement in an entry point that
// contains a discard.
TEST_F(DemoteToHelperTest, EntryPointReturns_DiscardInEntryPoint) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) f32 {
if (in == 0.0) {
discard;
}
let ret = textureSample(t, s, coord);
if (in < 1.0) {
return ret.x;
}
return 2.0;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) f32 {
if ((in == 0.0)) {
tint_discarded = true;
}
let ret = textureSample(t, s, coord);
if ((in < 1.0)) {
if (tint_discarded) {
discard;
}
return ret.x;
}
if (tint_discarded) {
discard;
}
return 2.0;
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
// Test that a discard statement is inserted before every return statement in an entry point that
// calls a function that contains a discard.
TEST_F(DemoteToHelperTest, EntryPointReturns_DiscardInHelper) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
fn bar() {
discard;
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) f32 {
if (in == 0.0) {
bar();
}
let ret = textureSample(t, s, coord);
if (in < 1.0) {
return ret.x;
}
return 2.0;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
fn bar() {
tint_discarded = true;
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) f32 {
if ((in == 0.0)) {
bar();
}
let ret = textureSample(t, s, coord);
if ((in < 1.0)) {
if (tint_discarded) {
discard;
}
return ret.x;
}
if (tint_discarded) {
discard;
}
return 2.0;
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
// Test that no return statements are modified in an entry point that does not discard.
TEST_F(DemoteToHelperTest, EntryPointReturns_NoDiscard) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
fn bar() {
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) f32 {
if ((in == 0.0)) {
bar();
}
let ret = textureSample(t, s, coord);
if ((in < 1.0)) {
return ret.x;
}
return 2.0;
}
)";
auto* expect = src;
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
// Test that only functions that are part of a shader that discards are transformed.
// Functions in non-discarding stages should not have their writes masked, and non-discarding entry
// points should not have their return statements replaced.
TEST_F(DemoteToHelperTest, MultipleShaders) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v1 : f32;
@group(0) @binding(3) var<storage, read_write> v2 : f32;
fn bar_discard(in : f32, coord : vec2<f32>) -> f32 {
let ret = textureSample(t, s, coord);
v1 = ret.x * 2.0;
return ret.y * 2.0;
}
fn bar_no_discard(in : f32, coord : vec2<f32>) -> f32 {
let ret = textureSample(t, s, coord);
v1 = ret.x * 2.0;
return ret.y * 2.0;
}
@fragment
fn foo_discard(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
discard;
}
let ret = bar_discard(in, coord);
v2 = ret;
}
@fragment
fn foo_no_discard(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
return;
}
let ret = bar_no_discard(in, coord);
v2 = ret;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v1 : f32;
@group(0) @binding(3) var<storage, read_write> v2 : f32;
fn bar_discard(in : f32, coord : vec2<f32>) -> f32 {
let ret = textureSample(t, s, coord);
if (!(tint_discarded)) {
v1 = (ret.x * 2.0);
}
return (ret.y * 2.0);
}
fn bar_no_discard(in : f32, coord : vec2<f32>) -> f32 {
let ret = textureSample(t, s, coord);
v1 = (ret.x * 2.0);
return (ret.y * 2.0);
}
@fragment
fn foo_discard(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
tint_discarded = true;
}
let ret = bar_discard(in, coord);
if (!(tint_discarded)) {
v2 = ret;
}
if (tint_discarded) {
discard;
}
}
@fragment
fn foo_no_discard(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
return;
}
let ret = bar_no_discard(in, coord);
v2 = ret;
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
// Test that we do not mask writes to invocation-private address spaces.
TEST_F(DemoteToHelperTest, InvocationPrivateWrites) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
var<private> vp : f32;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
discard;
}
let ret = textureSample(t, s, coord);
var vf : f32;
vf = ret.x;
vp = ret.y;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
var<private> vp : f32;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
tint_discarded = true;
}
let ret = textureSample(t, s, coord);
var vf : f32;
vf = ret.x;
vp = ret.y;
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, TextureStoreInEntryPoint) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var t2 : texture_storage_2d<rgba8unorm, write>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
discard;
}
let ret = textureSample(t, s, coord);
textureStore(t2, vec2<u32>(coord), ret);
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var t2 : texture_storage_2d<rgba8unorm, write>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
tint_discarded = true;
}
let ret = textureSample(t, s, coord);
if (!(tint_discarded)) {
textureStore(t2, vec2<u32>(coord), ret);
}
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, TextureStoreInHelper) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var t2 : texture_storage_2d<rgba8unorm, write>;
fn bar(coord : vec2<f32>, value : vec4<f32>) {
textureStore(t2, vec2<u32>(coord), value);
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
discard;
}
let ret = textureSample(t, s, coord);
bar(coord, ret);
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var t2 : texture_storage_2d<rgba8unorm, write>;
fn bar(coord : vec2<f32>, value : vec4<f32>) {
if (!(tint_discarded)) {
textureStore(t2, vec2<u32>(coord), value);
}
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
tint_discarded = true;
}
let ret = textureSample(t, s, coord);
bar(coord, ret);
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, TextureStore_NoDiscard) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var t2 : texture_storage_2d<rgba8unorm, write>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
let ret = textureSample(t, s, coord);
textureStore(t2, vec2<u32>(coord), ret);
}
)";
auto* expect = src;
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, AtomicStoreInEntryPoint) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
discard;
}
let ret = textureSample(t, s, coord);
atomicStore(&a, i32(ret.x));
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
tint_discarded = true;
}
let ret = textureSample(t, s, coord);
if (!(tint_discarded)) {
atomicStore(&(a), i32(ret.x));
}
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, AtomicStoreInHelper) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var<storage, read_write> a : atomic<i32>;
fn bar(value : vec4<f32>) {
atomicStore(&a, i32(value.x));
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if (in == 0.0) {
discard;
}
let ret = textureSample(t, s, coord);
bar(ret);
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var<storage, read_write> a : atomic<i32>;
fn bar(value : vec4<f32>) {
if (!(tint_discarded)) {
atomicStore(&(a), i32(value.x));
}
}
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
if ((in == 0.0)) {
tint_discarded = true;
}
let ret = textureSample(t, s, coord);
bar(ret);
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, AtomicStore_NoDiscard) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) {
let ret = textureSample(t, s, coord);
atomicStore(&(a), i32(ret.x));
}
)";
auto* expect = src;
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, AtomicBuiltinExpression) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) i32 {
if (in == 0.0) {
discard;
}
let v = i32(textureSample(t, s, coord).x);
let result = v + atomicAdd(&a, v);
return result;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) i32 {
if ((in == 0.0)) {
tint_discarded = true;
}
let v = i32(textureSample(t, s, coord).x);
var tint_symbol : i32;
if (!(tint_discarded)) {
tint_symbol = atomicAdd(&(a), v);
}
let result = (v + tint_symbol);
if (tint_discarded) {
discard;
}
return result;
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, AtomicBuiltinExpression_InForLoopContinuing) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) i32 {
if (in == 0.0) {
discard;
}
var result = 0;
for (var i = 0; i < 10; i = atomicAdd(&a, 1)) {
result += i32(textureSample(t, s, coord).x);
}
return result;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) i32 {
if ((in == 0.0)) {
tint_discarded = true;
}
var result = 0;
{
var i = 0;
loop {
if (!((i < 10))) {
break;
}
{
result += i32(textureSample(t, s, coord).x);
}
continuing {
var tint_symbol : i32;
if (!(tint_discarded)) {
tint_symbol = atomicAdd(&(a), 1);
}
i = tint_symbol;
}
}
}
if (tint_discarded) {
discard;
}
return result;
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, AtomicCompareExchangeWeak) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) i32 {
if (in == 0.0) {
discard;
}
var result = 0;
if (!atomicCompareExchangeWeak(&a, i32(in), 42).exchanged) {
let xchg = atomicCompareExchangeWeak(&a, i32(in), 42);
result = i32(textureSample(t, s, coord).x) * xchg.old_value;
}
return result;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
struct tint_symbol_1 {
old_value : i32,
exchanged : bool,
}
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) i32 {
if ((in == 0.0)) {
tint_discarded = true;
}
var result = 0;
var tint_symbol : tint_symbol_1;
if (!(tint_discarded)) {
let tint_symbol_2 = atomicCompareExchangeWeak(&(a), i32(in), 42);
tint_symbol.old_value = tint_symbol_2.old_value;
tint_symbol.exchanged = tint_symbol_2.exchanged;
}
if (!(tint_symbol.exchanged)) {
var tint_symbol_3 : tint_symbol_1;
if (!(tint_discarded)) {
let tint_symbol_4 = atomicCompareExchangeWeak(&(a), i32(in), 42);
tint_symbol_3.old_value = tint_symbol_4.old_value;
tint_symbol_3.exchanged = tint_symbol_4.exchanged;
}
let xchg = tint_symbol_3;
result = (i32(textureSample(t, s, coord).x) * xchg.old_value);
}
if (tint_discarded) {
discard;
}
return result;
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
// Test that no masking is generated for calls to `atomicLoad()`.
TEST_F(DemoteToHelperTest, AtomicLoad) {
auto* src = R"(
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) i32 {
if (in == 0.0) {
discard;
}
let v = i32(textureSample(t, s, coord).x);
let result = v + atomicLoad(&a);
return result;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@group(0) @binding(0) var t : texture_2d<f32>;
@group(0) @binding(1) var s : sampler;
@group(0) @binding(2) var<storage, read_write> v : f32;
@group(0) @binding(3) var<storage, read_write> a : atomic<i32>;
@fragment
fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) -> @location(0) i32 {
if ((in == 0.0)) {
tint_discarded = true;
}
let v = i32(textureSample(t, s, coord).x);
let result = (v + atomicLoad(&(a)));
if (tint_discarded) {
discard;
}
return result;
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DemoteToHelperTest, PhonyAssignment) {
auto* src = R"(
@fragment
fn foo(@location(0) in : f32) {
if (in == 0.0) {
discard;
}
_ = in;
}
)";
auto* expect = R"(
var<private> tint_discarded = false;
@fragment
fn foo(@location(0) in : f32) {
if ((in == 0.0)) {
tint_discarded = true;
}
_ = in;
if (tint_discarded) {
discard;
}
}
)";
auto got = Run<DemoteToHelper>(src);
EXPECT_EQ(expect, str(got));
}
} // namespace
} // namespace tint::transform