| // Copyright 2022 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "src/tint/lang/wgsl/ast/transform/demote_to_helper.h" |
| |
| #include <utility> |
| |
| #include "src/tint/lang/wgsl/ast/transform/helper_test.h" |
| |
| namespace tint::ast::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 write via sugared pointer also discards |
| TEST_F(DemoteToHelperTest, WriteInEntryPoint_DiscardInEntryPoint_ViaPointerDot) { |
| 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 : vec4<f32>; |
| |
| @fragment |
| fn foo(@location(0) in : f32, @location(1) coord : vec2<f32>) { |
| if (in == 0.0) { |
| discard; |
| } |
| let ret = textureSample(t, s, coord); |
| let p = &v; |
| p.x = 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 : vec4<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); |
| let p = &(v); |
| if (!(tint_discarded)) { |
| p.x = ret.x; |
| } |
| if (tint_discarded) { |
| discard; |
| } |
| } |
| )"; |
| |
| 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>) { |
| let ret = bar_no_discard(in, coord); |
| if (in == 0.0) { |
| return; |
| } |
| 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>) { |
| let ret = bar_no_discard(in, coord); |
| if ((in == 0.0)) { |
| return; |
| } |
| 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 that we do not mask writes to invocation-private address spaces via a sugared pointer write |
| TEST_F(DemoteToHelperTest, InvocationPrivateWritesViaPointerDot) { |
| auto* src = R"( |
| @group(0) @binding(0) var t : texture_2d<f32>; |
| |
| @group(0) @binding(1) var s : sampler; |
| |
| var<private> vp : vec4<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; |
| let p = &vp; |
| p.x = 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; |
| |
| var<private> vp : vec4<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; |
| let p = &(vp); |
| p.x = ret.x; |
| 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 += i; |
| } |
| 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 += i; |
| } |
| |
| continuing { |
| var tint_symbol : i32; |
| if (!(tint_discarded)) { |
| tint_symbol = atomicAdd(&(a), 1); |
| } |
| i = tint_symbol; |
| } |
| } |
| } |
| result += i32(textureSample(t, s, coord).x); |
| 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 = xchg.old_value; |
| } |
| 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 tint_symbol : __atomic_compare_exchange_result_i32; |
| if (!(tint_discarded)) { |
| tint_symbol = atomicCompareExchangeWeak(&(a), i32(in), 42); |
| } |
| if (!(tint_symbol.exchanged)) { |
| var tint_symbol_1 : __atomic_compare_exchange_result_i32; |
| if (!(tint_discarded)) { |
| tint_symbol_1 = atomicCompareExchangeWeak(&(a), i32(in), 42); |
| } |
| let xchg = tint_symbol_1; |
| result = xchg.old_value; |
| } |
| result += i32(textureSample(t, s, coord).x); |
| 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::ast::transform |