268 lines
9.5 KiB
C++
268 lines
9.5 KiB
C++
// 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<ast::CallExpression>(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<ast::CallExpression>(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<TextureOverloadCase> ValidCases() {
|
|
std::vector<TextureOverloadCase> 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<OffsetCase> 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<std::tuple<TextureOverloadCase, // texture info
|
|
OffsetCase // offset info
|
|
>>;
|
|
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<i32>(), offset.x, offset.y));
|
|
} else if (NumCoordinateAxes(param.texture_dimension) == 3) {
|
|
args.push_back(Construct(Source{{12, 34}}, ty.vec3<i32>(), offset.x,
|
|
offset.y, offset.z));
|
|
}
|
|
|
|
auto* call = Call(param.function, args);
|
|
Func("func", {}, ty.void_(), {Ignore(call)},
|
|
{create<ast::StageDecoration>(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<i32>(),
|
|
Construct(ty.i32(), offset.x), offset.y));
|
|
} else if (NumCoordinateAxes(param.texture_dimension) == 3) {
|
|
args.push_back(Construct(Source{{12, 34}}, ty.vec3<i32>(), offset.x,
|
|
Construct(ty.vec2<i32>(), offset.y, offset.z)));
|
|
}
|
|
auto* call = Call(param.function, args);
|
|
Func("func", {}, ty.void_(), {Ignore(call)},
|
|
{create<ast::StageDecoration>(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<i32>()));
|
|
} else if (NumCoordinateAxes(param.texture_dimension) == 3) {
|
|
args.push_back(Construct(Source{{12, 34}}, ty.vec3<i32>()));
|
|
}
|
|
|
|
auto* call = Call(param.function, args);
|
|
Func("func", {}, ty.void_(), {Ignore(call)},
|
|
{create<ast::StageDecoration>(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<i32>(), vec2<i32>(offset.x, offset.y));
|
|
GlobalConst("offset_3d", ty.vec3<i32>(),
|
|
vec3<i32>(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::StageDecoration>(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<i32>(), x, offset.y));
|
|
} else if (NumCoordinateAxes(param.texture_dimension) == 3) {
|
|
args.push_back(
|
|
Construct(Source{{12, 34}}, ty.vec3<i32>(), x, offset.y, offset.z));
|
|
}
|
|
|
|
auto* call = Call(param.function, args);
|
|
Func("func", {}, ty.void_(), {Decl(x), Ignore(call)},
|
|
{create<ast::StageDecoration>(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
|