| // Copyright (c) 2021 Google LLC | |
| // | |
| // 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 <vector> | |
| #include "gmock/gmock.h" | |
| #include "source/opt/convert_to_sampled_image_pass.h" | |
| #include "test/opt/pass_fixture.h" | |
| #include "test/opt/pass_utils.h" | |
| namespace spvtools { | |
| namespace opt { | |
| namespace { | |
| using testing::Eq; | |
| using VectorOfDescriptorSetAndBindingPairs = | |
| std::vector<DescriptorSetAndBinding>; | |
| struct DescriptorSetAndBindingStringParsingTestCase { | |
| const char* descriptor_set_binding_str; | |
| bool expect_success; | |
| VectorOfDescriptorSetAndBindingPairs expected_descriptor_set_binding_pairs; | |
| }; | |
| using DescriptorSetAndBindingStringParsingTest = | |
| ::testing::TestWithParam<DescriptorSetAndBindingStringParsingTestCase>; | |
| TEST_P(DescriptorSetAndBindingStringParsingTest, TestCase) { | |
| const auto& tc = GetParam(); | |
| auto actual_descriptor_set_binding_pairs = | |
| ConvertToSampledImagePass::ParseDescriptorSetBindingPairsString( | |
| tc.descriptor_set_binding_str); | |
| if (tc.expect_success) { | |
| EXPECT_NE(nullptr, actual_descriptor_set_binding_pairs); | |
| if (actual_descriptor_set_binding_pairs) { | |
| EXPECT_THAT(*actual_descriptor_set_binding_pairs, | |
| Eq(tc.expected_descriptor_set_binding_pairs)); | |
| } | |
| } else { | |
| EXPECT_EQ(nullptr, actual_descriptor_set_binding_pairs); | |
| } | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| ValidString, DescriptorSetAndBindingStringParsingTest, | |
| ::testing::ValuesIn(std::vector< | |
| DescriptorSetAndBindingStringParsingTestCase>{ | |
| // 0. empty vector | |
| {"", true, VectorOfDescriptorSetAndBindingPairs({})}, | |
| // 1. one pair | |
| {"100:1024", true, | |
| VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{100, | |
| 1024}})}, | |
| // 2. two pairs | |
| {"100:1024 200:2048", true, | |
| VectorOfDescriptorSetAndBindingPairs( | |
| {DescriptorSetAndBinding{100, 1024}, | |
| DescriptorSetAndBinding{200, 2048}})}, | |
| // 3. spaces between entries | |
| {"100:1024 \n \r \t \v \f 200:2048", true, | |
| VectorOfDescriptorSetAndBindingPairs( | |
| {DescriptorSetAndBinding{100, 1024}, | |
| DescriptorSetAndBinding{200, 2048}})}, | |
| // 4. \t, \n, \r and spaces before spec id | |
| {" \n \r\t \t \v \f 100:1024", true, | |
| VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{100, | |
| 1024}})}, | |
| // 5. \t, \n, \r and spaces after value string | |
| {"100:1024 \n \r\t \t \v \f ", true, | |
| VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{100, | |
| 1024}})}, | |
| // 6. maximum spec id | |
| {"4294967295:0", true, | |
| VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{ | |
| 4294967295, 0}})}, | |
| // 7. minimum spec id | |
| {"0:100", true, | |
| VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{0, | |
| 100}})}, | |
| // 8. multiple entries | |
| {"101:1 102:2 103:3 104:4 200:201 9999:1000", true, | |
| VectorOfDescriptorSetAndBindingPairs( | |
| {DescriptorSetAndBinding{101, 1}, DescriptorSetAndBinding{102, 2}, | |
| DescriptorSetAndBinding{103, 3}, DescriptorSetAndBinding{104, 4}, | |
| DescriptorSetAndBinding{200, 201}, | |
| DescriptorSetAndBinding{9999, 1000}})}, | |
| })); | |
| INSTANTIATE_TEST_SUITE_P( | |
| InvalidString, DescriptorSetAndBindingStringParsingTest, | |
| ::testing::ValuesIn( | |
| std::vector<DescriptorSetAndBindingStringParsingTestCase>{ | |
| // 0. missing default value | |
| {"100:", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 1. descriptor set is not an integer | |
| {"100.0:200", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 2. descriptor set is not a number | |
| {"something_not_a_number:1", false, | |
| VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 3. only descriptor set number | |
| {"100", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 4. empty descriptor set | |
| {":3", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 5. only colon | |
| {":", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 6. descriptor set overflow | |
| {"4294967296:200", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 7. descriptor set less than 0 | |
| {"-1:200", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 8. nullptr | |
| {nullptr, false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 9. only a number is invalid | |
| {"1234", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 10. invalid entry separator | |
| {"12:34;23:14", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 11. invalid descriptor set and default value separator | |
| {"12@34", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 12. spaces before colon | |
| {"100 :1024", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 13. spaces after colon | |
| {"100: 1024", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| // 14. descriptor set represented in hex float format is invalid | |
| {"0x3p10:200", false, VectorOfDescriptorSetAndBindingPairs{}}, | |
| })); | |
| std::string BuildShader(const char* shader_decorate_instructions, | |
| const char* shader_image_and_sampler_variables, | |
| const char* shader_body) { | |
| // Base HLSL code: | |
| // | |
| // SamplerState sam : register(s2); | |
| // Texture2D <float4> texture : register(t5); | |
| // | |
| // float4 main() : SV_TARGET { | |
| // return texture.SampleLevel(sam, float2(1, 2), 10, 2); | |
| // } | |
| std::stringstream ss; | |
| ss << R"( | |
| OpCapability Shader | |
| OpMemoryModel Logical GLSL450 | |
| OpEntryPoint Fragment %main "main" %out_var_SV_TARGET | |
| OpExecutionMode %main OriginUpperLeft | |
| OpSource HLSL 600 | |
| OpName %type_sampler "type.sampler" | |
| OpName %type_2d_image "type.2d.image" | |
| OpName %out_var_SV_TARGET "out.var.SV_TARGET" | |
| OpName %main "main" | |
| OpName %type_sampled_image "type.sampled.image" | |
| OpDecorate %out_var_SV_TARGET Location 0 | |
| )"; | |
| ss << shader_decorate_instructions; | |
| ss << R"( | |
| %float = OpTypeFloat 32 | |
| %float_1 = OpConstant %float 1 | |
| %float_2 = OpConstant %float 2 | |
| %v2float = OpTypeVector %float 2 | |
| %12 = OpConstantComposite %v2float %float_1 %float_2 | |
| %float_10 = OpConstant %float 10 | |
| %int = OpTypeInt 32 1 | |
| %int_2 = OpConstant %int 2 | |
| %v2int = OpTypeVector %int 2 | |
| %17 = OpConstantComposite %v2int %int_2 %int_2 | |
| %type_sampler = OpTypeSampler | |
| %_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler | |
| %type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown | |
| %_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image | |
| %v4float = OpTypeVector %float 4 | |
| %_ptr_Output_v4float = OpTypePointer Output %v4float | |
| %void = OpTypeVoid | |
| %23 = OpTypeFunction %void | |
| %type_sampled_image = OpTypeSampledImage %type_2d_image | |
| )"; | |
| ss << shader_image_and_sampler_variables; | |
| ss << R"( | |
| %out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output | |
| %main = OpFunction %void None %23 | |
| %24 = OpLabel | |
| )"; | |
| ss << shader_body; | |
| ss << R"( | |
| OpReturn | |
| OpFunctionEnd | |
| )"; | |
| return ss.str(); | |
| } | |
| using ConvertToSampledImageTest = PassTest<::testing::Test>; | |
| TEST_F(ConvertToSampledImageTest, Texture2DAndSamplerToSampledImage) { | |
| const std::string shader = BuildShader( | |
| R"( | |
| OpDecorate %sam DescriptorSet 0 | |
| OpDecorate %sam Binding 5 | |
| OpDecorate %texture DescriptorSet 0 | |
| OpDecorate %texture Binding 5 | |
| )", | |
| R"( | |
| ; CHECK-NOT: OpVariable %_ptr_UniformConstant_type_2d_image | |
| ; CHECK: [[tex:%\w+]] = OpVariable %_ptr_UniformConstant_type_sampled_image UniformConstant | |
| %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant | |
| %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant | |
| )", | |
| R"( | |
| ; CHECK: [[load:%\w+]] = OpLoad %type_sampled_image [[tex]] | |
| ; CHECK: OpImageSampleExplicitLod %v4float [[load]] | |
| %25 = OpLoad %type_2d_image %texture | |
| %26 = OpLoad %type_sampler %sam | |
| %27 = OpSampledImage %type_sampled_image %25 %26 | |
| %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17 | |
| OpStore %out_var_SV_TARGET %28 | |
| )"); | |
| auto result = SinglePassRunAndMatch<ConvertToSampledImagePass>( | |
| shader, /* do_validate = */ true, | |
| VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 5}}); | |
| EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); | |
| } | |
| TEST_F(ConvertToSampledImageTest, Texture2DToSampledImage) { | |
| const std::string shader = BuildShader( | |
| R"( | |
| OpDecorate %sam DescriptorSet 0 | |
| OpDecorate %sam Binding 2 | |
| OpDecorate %texture DescriptorSet 0 | |
| OpDecorate %texture Binding 5 | |
| )", | |
| R"( | |
| ; CHECK: [[tex:%\w+]] = OpVariable %_ptr_UniformConstant_type_sampled_image UniformConstant | |
| %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant | |
| %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant | |
| )", | |
| R"( | |
| ; CHECK: [[load:%\w+]] = OpLoad %type_sampled_image [[tex]] | |
| ; CHECK: [[image_extraction:%\w+]] = OpImage %type_2d_image [[load]] | |
| ; CHECK: OpSampledImage %type_sampled_image [[image_extraction]] | |
| %25 = OpLoad %type_2d_image %texture | |
| %26 = OpLoad %type_sampler %sam | |
| %27 = OpSampledImage %type_sampled_image %25 %26 | |
| %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17 | |
| OpStore %out_var_SV_TARGET %28 | |
| )"); | |
| auto result = SinglePassRunAndMatch<ConvertToSampledImagePass>( | |
| shader, /* do_validate = */ true, | |
| VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 5}}); | |
| EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); | |
| } | |
| TEST_F(ConvertToSampledImageTest, SamplerToSampledImage) { | |
| const std::string shader = BuildShader( | |
| R"( | |
| OpDecorate %sam DescriptorSet 0 | |
| OpDecorate %sam Binding 2 | |
| OpDecorate %texture DescriptorSet 0 | |
| OpDecorate %texture Binding 5 | |
| )", | |
| R"( | |
| %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant | |
| %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant | |
| )", | |
| R"( | |
| %25 = OpLoad %type_2d_image %texture | |
| %26 = OpLoad %type_sampler %sam | |
| %27 = OpSampledImage %type_sampled_image %25 %26 | |
| %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17 | |
| OpStore %out_var_SV_TARGET %28 | |
| )"); | |
| auto result = SinglePassRunToBinary<ConvertToSampledImagePass>( | |
| shader, /* skip_nop = */ false, | |
| VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 2}}); | |
| EXPECT_EQ(std::get<1>(result), Pass::Status::Failure); | |
| } | |
| TEST_F(ConvertToSampledImageTest, TwoImagesWithDuplicatedDescriptorSetBinding) { | |
| const std::string shader = BuildShader( | |
| R"( | |
| OpDecorate %sam DescriptorSet 0 | |
| OpDecorate %sam Binding 2 | |
| OpDecorate %texture0 DescriptorSet 0 | |
| OpDecorate %texture0 Binding 5 | |
| OpDecorate %texture1 DescriptorSet 0 | |
| OpDecorate %texture1 Binding 5 | |
| )", | |
| R"( | |
| %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant | |
| %texture0 = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant | |
| %texture1 = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant | |
| )", | |
| R"( | |
| %25 = OpLoad %type_2d_image %texture0 | |
| %26 = OpLoad %type_sampler %sam | |
| %27 = OpSampledImage %type_sampled_image %25 %26 | |
| %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17 | |
| OpStore %out_var_SV_TARGET %28 | |
| )"); | |
| auto result = SinglePassRunToBinary<ConvertToSampledImagePass>( | |
| shader, /* skip_nop = */ false, | |
| VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 5}}); | |
| EXPECT_EQ(std::get<1>(result), Pass::Status::Failure); | |
| } | |
| TEST_F(ConvertToSampledImageTest, | |
| TwoSamplersWithDuplicatedDescriptorSetBinding) { | |
| const std::string shader = BuildShader( | |
| R"( | |
| OpDecorate %sam0 DescriptorSet 0 | |
| OpDecorate %sam0 Binding 2 | |
| OpDecorate %sam1 DescriptorSet 0 | |
| OpDecorate %sam1 Binding 2 | |
| OpDecorate %texture DescriptorSet 0 | |
| OpDecorate %texture Binding 5 | |
| )", | |
| R"( | |
| %sam0 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant | |
| %sam1 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant | |
| %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant | |
| )", | |
| R"( | |
| %25 = OpLoad %type_2d_image %texture | |
| %26 = OpLoad %type_sampler %sam0 | |
| %27 = OpSampledImage %type_sampled_image %25 %26 | |
| %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17 | |
| OpStore %out_var_SV_TARGET %28 | |
| )"); | |
| auto result = SinglePassRunToBinary<ConvertToSampledImagePass>( | |
| shader, /* skip_nop = */ false, | |
| VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 2}}); | |
| EXPECT_EQ(std::get<1>(result), Pass::Status::Failure); | |
| } | |
| } // namespace | |
| } // namespace opt | |
| } // namespace spvtools |