// Copyright 2021 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/ast/intrinsic_texture_helper_test.h" #include "src/resolver/resolver_test_helper.h" namespace tint { namespace resolver { namespace { using ResolverIntrinsicValidationTest = ResolverTest; TEST_F(ResolverIntrinsicValidationTest, InvalidPipelineStageDirect) { // [[stage(compute), workgroup_size(1)]] fn func { return dpdx(1.0); } auto* dpdx = create(Source{{3, 4}}, Expr("dpdx"), ast::ExpressionList{Expr(1.0f)}); Func(Source{{1, 2}}, "func", ast::VariableList{}, ty.void_(), {Ignore(dpdx)}, {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), "3:4 error: built-in cannot be used by compute pipeline stage"); } TEST_F(ResolverIntrinsicValidationTest, InvalidPipelineStageIndirect) { // fn f0 { return dpdx(1.0); } // fn f1 { f0(); } // fn f2 { f1(); } // [[stage(compute), workgroup_size(1)]] fn main { return f2(); } auto* dpdx = create(Source{{3, 4}}, Expr("dpdx"), ast::ExpressionList{Expr(1.0f)}); Func(Source{{1, 2}}, "f0", ast::VariableList{}, ty.void_(), {Ignore(dpdx)}); Func(Source{{3, 4}}, "f1", ast::VariableList{}, ty.void_(), {Ignore(Call("f0"))}); Func(Source{{5, 6}}, "f2", ast::VariableList{}, ty.void_(), {Ignore(Call("f1"))}); Func(Source{{7, 8}}, "main", ast::VariableList{}, ty.void_(), {Ignore(Call("f2"))}, {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}); EXPECT_FALSE(r()->Resolve()); EXPECT_EQ(r()->error(), R"(3:4 error: built-in cannot be used by compute pipeline stage 1:2 note: called by function 'f0' 3:4 note: called by function 'f1' 5:6 note: called by function 'f2' 7:8 note: called by entry point 'main')"); } namespace TextureSamplerOffset { using TextureOverloadCase = ast::intrinsic::test::TextureOverloadCase; using ValidTextureOverload = ast::intrinsic::test::ValidTextureOverload; using TextureKind = ast::intrinsic::test::TextureKind; using TextureDataType = ast::intrinsic::test::TextureDataType; using u32 = ProgramBuilder::u32; using i32 = ProgramBuilder::i32; using f32 = ProgramBuilder::f32; static std::vector ValidCases() { std::vector cases; for (auto c : TextureOverloadCase::ValidCases()) { if (std::string(c.function).find("textureSample") == 0) { if (std::string(c.description).find("offset ") != std::string::npos) { cases.push_back(c); } } } return cases; } struct OffsetCase { bool is_valid; int32_t x; int32_t y; int32_t z; int32_t illegal_value = 0; }; static std::vector OffsetCases() { return { {true, 0, 1, 2}, // {true, 7, -8, 7}, // {false, 10, 10, 20, 10}, // {false, -9, 0, 0, -9}, // {false, 0, 8, 0, 8}, // }; } using IntrinsicTextureSamplerValidationTest = ResolverTestWithParam>; TEST_P(IntrinsicTextureSamplerValidationTest, ConstExpr) { auto& p = GetParam(); auto param = std::get<0>(p); auto offset = std::get<1>(p); param.buildTextureVariable(this); param.buildSamplerVariable(this); auto args = param.args(this); // Make Resolver visit the Node about to be removed WrapInFunction(args.back()); args.pop_back(); if (NumCoordinateAxes(param.texture_dimension) == 2) { args.push_back( Construct(Source{{12, 34}}, ty.vec2(), offset.x, offset.y)); } else if (NumCoordinateAxes(param.texture_dimension) == 3) { args.push_back(Construct(Source{{12, 34}}, ty.vec3(), offset.x, offset.y, offset.z)); } auto* call = Call(param.function, args); Func("func", {}, ty.void_(), {Ignore(call)}, {create(ast::PipelineStage::kFragment)}); if (offset.is_valid) { EXPECT_TRUE(r()->Resolve()) << r()->error(); } else { EXPECT_FALSE(r()->Resolve()); std::stringstream err; err << "12:34 error: each offset component of '" << param.function << "' must be at least -8 and at most 7. found: '" << std::to_string(offset.illegal_value) << "'"; EXPECT_EQ(r()->error(), err.str()); } } TEST_P(IntrinsicTextureSamplerValidationTest, ConstExprOfConstExpr) { auto& p = GetParam(); auto param = std::get<0>(p); auto offset = std::get<1>(p); param.buildTextureVariable(this); param.buildSamplerVariable(this); auto args = param.args(this); // Make Resolver visit the Node about to be removed WrapInFunction(args.back()); args.pop_back(); if (NumCoordinateAxes(param.texture_dimension) == 2) { args.push_back(Construct(Source{{12, 34}}, ty.vec2(), Construct(ty.i32(), offset.x), offset.y)); } else if (NumCoordinateAxes(param.texture_dimension) == 3) { args.push_back(Construct(Source{{12, 34}}, ty.vec3(), offset.x, Construct(ty.vec2(), offset.y, offset.z))); } auto* call = Call(param.function, args); Func("func", {}, ty.void_(), {Ignore(call)}, {create(ast::PipelineStage::kFragment)}); if (offset.is_valid) { EXPECT_TRUE(r()->Resolve()) << r()->error(); } else { EXPECT_FALSE(r()->Resolve()); std::stringstream err; err << "12:34 error: each offset component of '" << param.function << "' must be at least -8 and at most 7. found: '" << std::to_string(offset.illegal_value) << "'"; EXPECT_EQ(r()->error(), err.str()); } } TEST_P(IntrinsicTextureSamplerValidationTest, EmptyVectorConstructor) { auto& p = GetParam(); auto param = std::get<0>(p); param.buildTextureVariable(this); param.buildSamplerVariable(this); auto args = param.args(this); // Make Resolver visit the Node about to be removed WrapInFunction(args.back()); args.pop_back(); if (NumCoordinateAxes(param.texture_dimension) == 2) { args.push_back(Construct(Source{{12, 34}}, ty.vec2())); } else if (NumCoordinateAxes(param.texture_dimension) == 3) { args.push_back(Construct(Source{{12, 34}}, ty.vec3())); } auto* call = Call(param.function, args); Func("func", {}, ty.void_(), {Ignore(call)}, {create(ast::PipelineStage::kFragment)}); EXPECT_TRUE(r()->Resolve()) << r()->error(); } TEST_P(IntrinsicTextureSamplerValidationTest, GlobalConst) { auto& p = GetParam(); auto param = std::get<0>(p); auto offset = std::get<1>(p); param.buildTextureVariable(this); param.buildSamplerVariable(this); auto args = param.args(this); // Make Resolver visit the Node about to be removed WrapInFunction(args.back()); args.pop_back(); GlobalConst("offset_2d", ty.vec2(), vec2(offset.x, offset.y)); GlobalConst("offset_3d", ty.vec3(), vec3(offset.x, offset.y, offset.z)); if (NumCoordinateAxes(param.texture_dimension) == 2) { args.push_back(Expr(Source{{12, 34}}, "offset_2d")); } else if (NumCoordinateAxes(param.texture_dimension) == 3) { args.push_back(Expr(Source{{12, 34}}, "offset_3d")); } auto* call = Call(param.function, args); Func("func", {}, ty.void_(), {Ignore(call)}, {create(ast::PipelineStage::kFragment)}); EXPECT_FALSE(r()->Resolve()); std::stringstream err; err << "12:34 error: '" << param.function << "' offset parameter must be a const_expression"; EXPECT_EQ(r()->error(), err.str()); } TEST_P(IntrinsicTextureSamplerValidationTest, ScalarConst) { auto& p = GetParam(); auto param = std::get<0>(p); auto offset = std::get<1>(p); param.buildTextureVariable(this); param.buildSamplerVariable(this); auto* x = Const("x", ty.i32(), Construct(ty.i32(), offset.x)); auto args = param.args(this); // Make Resolver visit the Node about to be removed WrapInFunction(args.back()); args.pop_back(); if (NumCoordinateAxes(param.texture_dimension) == 2) { args.push_back(Construct(Source{{12, 34}}, ty.vec2(), x, offset.y)); } else if (NumCoordinateAxes(param.texture_dimension) == 3) { args.push_back( Construct(Source{{12, 34}}, ty.vec3(), x, offset.y, offset.z)); } auto* call = Call(param.function, args); Func("func", {}, ty.void_(), {Decl(x), Ignore(call)}, {create(ast::PipelineStage::kFragment)}); EXPECT_FALSE(r()->Resolve()); std::stringstream err; err << "12:34 error: '" << param.function << "' offset parameter must be a const_expression"; EXPECT_EQ(r()->error(), err.str()); } INSTANTIATE_TEST_SUITE_P(IntrinsicTextureSamplerValidationTest, IntrinsicTextureSamplerValidationTest, testing::Combine(testing::ValuesIn(ValidCases()), testing::ValuesIn(OffsetCases()))); } // namespace TextureSamplerOffset } // namespace } // namespace resolver } // namespace tint