// 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/intrinsic_table.h" #include "gmock/gmock.h" #include "src/program_builder.h" #include "src/type/access_control_type.h" #include "src/type/depth_texture_type.h" #include "src/type/multisampled_texture_type.h" #include "src/type/sampled_texture_type.h" #include "src/type/storage_texture_type.h" namespace tint { namespace { using ::testing::ElementsAre; using ::testing::HasSubstr; using IntrinsicType = semantic::IntrinsicType; using Parameter = semantic::Parameter; class IntrinsicTableTest : public testing::Test, public ProgramBuilder { public: std::unique_ptr table = IntrinsicTable::Create(); }; TEST_F(IntrinsicTableTest, MatchF32) { auto result = table->Lookup(*this, IntrinsicType::kCos, {ty.f32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCos); EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()})); } TEST_F(IntrinsicTableTest, MismatchF32) { auto result = table->Lookup(*this, IntrinsicType::kCos, {ty.i32()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchU32) { auto result = table->Lookup(*this, IntrinsicType::kUnpack2x16Float, {ty.u32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kUnpack2x16Float); EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec2()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.u32()})); } TEST_F(IntrinsicTableTest, MismatchU32) { auto result = table->Lookup(*this, IntrinsicType::kUnpack2x16Float, {ty.f32()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchI32) { auto* tex = create(type::TextureDimension::k1d, ty.f32()); auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, ty.i32(), ty.i32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{tex, Parameter::Usage::kTexture}, Parameter{ty.i32(), Parameter::Usage::kCoords}, Parameter{ty.i32(), Parameter::Usage::kLevel})); } TEST_F(IntrinsicTableTest, MismatchI32) { auto* tex = create(type::TextureDimension::k1d, ty.f32()); auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, ty.f32()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchIU32AsI32) { auto result = table->Lookup(*this, IntrinsicType::kCountOneBits, {ty.i32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCountOneBits); EXPECT_THAT(result.intrinsic->ReturnType(), ty.i32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.i32()})); } TEST_F(IntrinsicTableTest, MatchIU32AsU32) { auto result = table->Lookup(*this, IntrinsicType::kCountOneBits, {ty.u32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCountOneBits); EXPECT_THAT(result.intrinsic->ReturnType(), ty.u32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.u32()})); } TEST_F(IntrinsicTableTest, MismatchIU32) { auto result = table->Lookup(*this, IntrinsicType::kCountOneBits, {ty.f32()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchFIU32AsI32) { auto result = table->Lookup(*this, IntrinsicType::kClamp, {ty.i32(), ty.i32(), ty.i32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result.intrinsic->ReturnType(), ty.i32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.i32()}, Parameter{ty.i32()}, Parameter{ty.i32()})); } TEST_F(IntrinsicTableTest, MatchFIU32AsU32) { auto result = table->Lookup(*this, IntrinsicType::kClamp, {ty.u32(), ty.u32(), ty.u32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result.intrinsic->ReturnType(), ty.u32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.u32()}, Parameter{ty.u32()}, Parameter{ty.u32()})); } TEST_F(IntrinsicTableTest, MatchFIU32AsF32) { auto result = table->Lookup(*this, IntrinsicType::kClamp, {ty.f32(), ty.f32(), ty.f32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()}, Parameter{ty.f32()}, Parameter{ty.f32()})); } TEST_F(IntrinsicTableTest, MismatchFIU32) { auto result = table->Lookup(*this, IntrinsicType::kClamp, {ty.bool_(), ty.bool_(), ty.bool_()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchBool) { auto result = table->Lookup(*this, IntrinsicType::kSelect, {ty.f32(), ty.f32(), ty.bool_()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kSelect); EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()}, Parameter{ty.f32()}, Parameter{ty.bool_()})); } TEST_F(IntrinsicTableTest, MismatchBool) { auto result = table->Lookup(*this, IntrinsicType::kSelect, {ty.f32(), ty.f32(), ty.f32()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchPointer) { auto result = table->Lookup( *this, IntrinsicType::kModf, {ty.f32(), ty.pointer(ast::StorageClass::kNone)}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kModf); EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32()); EXPECT_THAT( result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()}, Parameter{ty.pointer(ast::StorageClass::kNone)})); } TEST_F(IntrinsicTableTest, MismatchPointer) { auto result = table->Lookup(*this, IntrinsicType::kModf, {ty.f32(), ty.f32()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchArray) { auto result = table->Lookup(*this, IntrinsicType::kArrayLength, {ty.array()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kArrayLength); EXPECT_THAT(result.intrinsic->ReturnType(), ty.u32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.array()})); } TEST_F(IntrinsicTableTest, MismatchArray) { auto result = table->Lookup(*this, IntrinsicType::kArrayLength, {ty.f32()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchSampler) { auto* tex = create(type::TextureDimension::k2d, ty.f32()); auto* sampler = create(type::SamplerKind::kSampler); auto result = table->Lookup(*this, IntrinsicType::kTextureSample, {tex, sampler, ty.vec2()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureSample); EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4()); EXPECT_THAT( result.intrinsic->Parameters(), ElementsAre(Parameter{tex, Parameter::Usage::kTexture}, Parameter{sampler, Parameter::Usage::kSampler}, Parameter{ty.vec2(), Parameter::Usage::kCoords})); } TEST_F(IntrinsicTableTest, MismatchSampler) { auto* tex = create(type::TextureDimension::k2d, ty.f32()); auto result = table->Lookup(*this, IntrinsicType::kTextureSample, {tex, ty.f32(), ty.vec2()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchSampledTexture) { auto* tex = create(type::TextureDimension::k2d, ty.f32()); auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, ty.vec2(), ty.i32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{tex, Parameter::Usage::kTexture}, Parameter{ty.vec2(), Parameter::Usage::kCoords}, Parameter{ty.i32(), Parameter::Usage::kLevel})); } TEST_F(IntrinsicTableTest, MatchMultisampledTexture) { auto* tex = create(type::TextureDimension::k2d, ty.f32()); auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, ty.vec2(), ty.i32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{tex, Parameter::Usage::kTexture}, Parameter{ty.vec2(), Parameter::Usage::kCoords}, Parameter{ty.i32(), Parameter::Usage::kSampleIndex})); } TEST_F(IntrinsicTableTest, MatchDepthTexture) { auto* tex = create(type::TextureDimension::k2d); auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, ty.vec2(), ty.i32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{tex, Parameter::Usage::kTexture}, Parameter{ty.vec2(), Parameter::Usage::kCoords}, Parameter{ty.i32(), Parameter::Usage::kLevel})); } TEST_F(IntrinsicTableTest, MatchROStorageTexture) { auto* tex = create( type::TextureDimension::k2d, type::ImageFormat::kR16Float, type::StorageTexture::SubtypeFor(type::ImageFormat::kR16Float, Types())); auto* tex_ac = create(ast::AccessControl::kReadOnly, tex); auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, {tex_ac, ty.vec2()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec4()); EXPECT_THAT( result.intrinsic->Parameters(), ElementsAre(Parameter{tex_ac, Parameter::Usage::kTexture}, Parameter{ty.vec2(), Parameter::Usage::kCoords})); } TEST_F(IntrinsicTableTest, MatchWOStorageTexture) { auto* tex = create( type::TextureDimension::k2d, type::ImageFormat::kR16Float, type::StorageTexture::SubtypeFor(type::ImageFormat::kR16Float, Types())); auto* tex_ac = create(ast::AccessControl::kWriteOnly, tex); auto result = table->Lookup(*this, IntrinsicType::kTextureStore, {tex_ac, ty.vec2(), ty.vec4()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureStore); EXPECT_THAT(result.intrinsic->ReturnType(), ty.void_()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{tex_ac, Parameter::Usage::kTexture}, Parameter{ty.vec2(), Parameter::Usage::kCoords}, Parameter{ty.vec4(), Parameter::Usage::kValue})); } TEST_F(IntrinsicTableTest, MismatchTexture) { auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, {ty.f32(), ty.vec2()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchAutoPointerDereference) { auto result = table->Lookup(*this, IntrinsicType::kCos, {ty.pointer(ast::StorageClass::kNone)}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCos); EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()})); } TEST_F(IntrinsicTableTest, MatchWithAliasUnwrapping) { auto* alias_a = ty.alias("alias_a", ty.f32()); auto* alias_b = ty.alias("alias_b", alias_a); auto* alias_c = ty.alias("alias_c", alias_b); auto result = table->Lookup(*this, IntrinsicType::kCos, {alias_c}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCos); EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()})); } TEST_F(IntrinsicTableTest, MatchOpenType) { auto result = table->Lookup(*this, IntrinsicType::kClamp, {ty.f32(), ty.f32(), ty.f32()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.f32()}, Parameter{ty.f32()}, Parameter{ty.f32()})); } TEST_F(IntrinsicTableTest, MismatchOpenType) { auto result = table->Lookup(*this, IntrinsicType::kClamp, {ty.f32(), ty.u32(), ty.f32()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchOpenSizeVector) { auto result = table->Lookup(*this, IntrinsicType::kClamp, {ty.vec2(), ty.vec2(), ty.vec2()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result.intrinsic->ReturnType(), ty.vec2()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.vec2()}, Parameter{ty.vec2()}, Parameter{ty.vec2()})); } TEST_F(IntrinsicTableTest, MismatchOpenSizeVector) { auto result = table->Lookup(*this, IntrinsicType::kClamp, {ty.vec2(), ty.vec2(), ty.vec2()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, MatchOpenSizeMatrix) { auto result = table->Lookup(*this, IntrinsicType::kDeterminant, {ty.mat3x3()}, Source{}); ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kDeterminant); EXPECT_THAT(result.intrinsic->ReturnType(), ty.f32()); EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{ty.mat3x3()})); } TEST_F(IntrinsicTableTest, MismatchOpenSizeMatrix) { auto result = table->Lookup(*this, IntrinsicType::kDeterminant, {ty.mat3x2()}, Source{}); ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); } TEST_F(IntrinsicTableTest, OverloadOrderByNumberOfParameters) { // None of the arguments match, so expect the overloads with 2 parameters to // come first auto result = table->Lookup(*this, IntrinsicType::kTextureDimensions, {ty.bool_(), ty.bool_()}, Source{}); ASSERT_EQ(result.diagnostics.str(), R"(error: no matching call to textureDimensions(bool, bool) 25 candidate functions: textureDimensions(texture : texture_2d, level : i32) -> vec2 textureDimensions(texture : texture_2d_array, level : i32) -> vec2 textureDimensions(texture : texture_3d, level : i32) -> vec3 textureDimensions(texture : texture_cube, level : i32) -> vec3 textureDimensions(texture : texture_cube_array, level : i32) -> vec3 textureDimensions(texture : texture_depth_2d, level : i32) -> vec2 textureDimensions(texture : texture_depth_2d_array, level : i32) -> vec2 textureDimensions(texture : texture_depth_cube, level : i32) -> vec3 textureDimensions(texture : texture_depth_cube_array, level : i32) -> vec3 textureDimensions(texture : texture_1d) -> i32 textureDimensions(texture : texture_2d) -> vec2 textureDimensions(texture : texture_2d_array) -> vec2 textureDimensions(texture : texture_3d) -> vec3 textureDimensions(texture : texture_cube) -> vec3 textureDimensions(texture : texture_cube_array) -> vec3 textureDimensions(texture : texture_multisampled_2d) -> vec2 textureDimensions(texture : texture_multisampled_2d_array) -> vec2 textureDimensions(texture : texture_depth_2d) -> vec2 textureDimensions(texture : texture_depth_2d_array) -> vec2 textureDimensions(texture : texture_depth_cube) -> vec3 textureDimensions(texture : texture_depth_cube_array) -> vec3 textureDimensions(texture : texture_storage_1d) -> i32 textureDimensions(texture : texture_storage_2d) -> vec2 textureDimensions(texture : texture_storage_2d_array) -> vec2 textureDimensions(texture : texture_storage_3d) -> vec3 )"); } TEST_F(IntrinsicTableTest, OverloadOrderByMatchingParameter) { auto* tex = create(type::TextureDimension::k2d); auto result = table->Lookup(*this, IntrinsicType::kTextureDimensions, {tex, ty.bool_()}, Source{}); ASSERT_EQ( result.diagnostics.str(), R"(error: no matching call to textureDimensions(texture_depth_2d, bool) 25 candidate functions: textureDimensions(texture : texture_depth_2d, level : i32) -> vec2 textureDimensions(texture : texture_depth_2d) -> vec2 textureDimensions(texture : texture_2d, level : i32) -> vec2 textureDimensions(texture : texture_2d_array, level : i32) -> vec2 textureDimensions(texture : texture_3d, level : i32) -> vec3 textureDimensions(texture : texture_cube, level : i32) -> vec3 textureDimensions(texture : texture_cube_array, level : i32) -> vec3 textureDimensions(texture : texture_depth_2d_array, level : i32) -> vec2 textureDimensions(texture : texture_depth_cube, level : i32) -> vec3 textureDimensions(texture : texture_depth_cube_array, level : i32) -> vec3 textureDimensions(texture : texture_1d) -> i32 textureDimensions(texture : texture_2d) -> vec2 textureDimensions(texture : texture_2d_array) -> vec2 textureDimensions(texture : texture_3d) -> vec3 textureDimensions(texture : texture_cube) -> vec3 textureDimensions(texture : texture_cube_array) -> vec3 textureDimensions(texture : texture_multisampled_2d) -> vec2 textureDimensions(texture : texture_multisampled_2d_array) -> vec2 textureDimensions(texture : texture_depth_2d_array) -> vec2 textureDimensions(texture : texture_depth_cube) -> vec3 textureDimensions(texture : texture_depth_cube_array) -> vec3 textureDimensions(texture : texture_storage_1d) -> i32 textureDimensions(texture : texture_storage_2d) -> vec2 textureDimensions(texture : texture_storage_2d_array) -> vec2 textureDimensions(texture : texture_storage_3d) -> vec3 )"); } } // namespace } // namespace tint