1// Copyright (c) 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include <vector>
16
17#include "gmock/gmock.h"
18#include "source/opt/convert_to_sampled_image_pass.h"
19#include "test/opt/pass_fixture.h"
20#include "test/opt/pass_utils.h"
21
22namespace spvtools {
23namespace opt {
24namespace {
25
26using testing::Eq;
27using VectorOfDescriptorSetAndBindingPairs =
28    std::vector<DescriptorSetAndBinding>;
29
30struct DescriptorSetAndBindingStringParsingTestCase {
31  const char* descriptor_set_binding_str;
32  bool expect_success;
33  VectorOfDescriptorSetAndBindingPairs expected_descriptor_set_binding_pairs;
34};
35
36using DescriptorSetAndBindingStringParsingTest =
37    ::testing::TestWithParam<DescriptorSetAndBindingStringParsingTestCase>;
38
39TEST_P(DescriptorSetAndBindingStringParsingTest, TestCase) {
40  const auto& tc = GetParam();
41  auto actual_descriptor_set_binding_pairs =
42      ConvertToSampledImagePass::ParseDescriptorSetBindingPairsString(
43          tc.descriptor_set_binding_str);
44  if (tc.expect_success) {
45    EXPECT_NE(nullptr, actual_descriptor_set_binding_pairs);
46    if (actual_descriptor_set_binding_pairs) {
47      EXPECT_THAT(*actual_descriptor_set_binding_pairs,
48                  Eq(tc.expected_descriptor_set_binding_pairs));
49    }
50  } else {
51    EXPECT_EQ(nullptr, actual_descriptor_set_binding_pairs);
52  }
53}
54
55INSTANTIATE_TEST_SUITE_P(
56    ValidString, DescriptorSetAndBindingStringParsingTest,
57    ::testing::ValuesIn(std::vector<
58                        DescriptorSetAndBindingStringParsingTestCase>{
59        // 0. empty vector
60        {"", true, VectorOfDescriptorSetAndBindingPairs({})},
61        // 1. one pair
62        {"100:1024", true,
63         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{100,
64                                                                       1024}})},
65        // 2. two pairs
66        {"100:1024 200:2048", true,
67         VectorOfDescriptorSetAndBindingPairs(
68             {DescriptorSetAndBinding{100, 1024},
69              DescriptorSetAndBinding{200, 2048}})},
70        // 3. spaces between entries
71        {"100:1024 \n \r \t \v \f 200:2048", true,
72         VectorOfDescriptorSetAndBindingPairs(
73             {DescriptorSetAndBinding{100, 1024},
74              DescriptorSetAndBinding{200, 2048}})},
75        // 4. \t, \n, \r and spaces before spec id
76        {"   \n \r\t \t \v \f 100:1024", true,
77         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{100,
78                                                                       1024}})},
79        // 5. \t, \n, \r and spaces after value string
80        {"100:1024   \n \r\t \t \v \f ", true,
81         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{100,
82                                                                       1024}})},
83        // 6. maximum spec id
84        {"4294967295:0", true,
85         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{
86             4294967295, 0}})},
87        // 7. minimum spec id
88        {"0:100", true,
89         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{0,
90                                                                       100}})},
91        // 8. multiple entries
92        {"101:1 102:2 103:3 104:4 200:201 9999:1000", true,
93         VectorOfDescriptorSetAndBindingPairs(
94             {DescriptorSetAndBinding{101, 1}, DescriptorSetAndBinding{102, 2},
95              DescriptorSetAndBinding{103, 3}, DescriptorSetAndBinding{104, 4},
96              DescriptorSetAndBinding{200, 201},
97              DescriptorSetAndBinding{9999, 1000}})},
98    }));
99
100INSTANTIATE_TEST_SUITE_P(
101    InvalidString, DescriptorSetAndBindingStringParsingTest,
102    ::testing::ValuesIn(
103        std::vector<DescriptorSetAndBindingStringParsingTestCase>{
104            // 0. missing default value
105            {"100:", false, VectorOfDescriptorSetAndBindingPairs{}},
106            // 1. descriptor set is not an integer
107            {"100.0:200", false, VectorOfDescriptorSetAndBindingPairs{}},
108            // 2. descriptor set is not a number
109            {"something_not_a_number:1", false,
110             VectorOfDescriptorSetAndBindingPairs{}},
111            // 3. only descriptor set number
112            {"100", false, VectorOfDescriptorSetAndBindingPairs{}},
113            // 4. empty descriptor set
114            {":3", false, VectorOfDescriptorSetAndBindingPairs{}},
115            // 5. only colon
116            {":", false, VectorOfDescriptorSetAndBindingPairs{}},
117            // 6. descriptor set overflow
118            {"4294967296:200", false, VectorOfDescriptorSetAndBindingPairs{}},
119            // 7. descriptor set less than 0
120            {"-1:200", false, VectorOfDescriptorSetAndBindingPairs{}},
121            // 8. nullptr
122            {nullptr, false, VectorOfDescriptorSetAndBindingPairs{}},
123            // 9. only a number is invalid
124            {"1234", false, VectorOfDescriptorSetAndBindingPairs{}},
125            // 10. invalid entry separator
126            {"12:34;23:14", false, VectorOfDescriptorSetAndBindingPairs{}},
127            // 11. invalid descriptor set and default value separator
128            {"12@34", false, VectorOfDescriptorSetAndBindingPairs{}},
129            // 12. spaces before colon
130            {"100   :1024", false, VectorOfDescriptorSetAndBindingPairs{}},
131            // 13. spaces after colon
132            {"100:   1024", false, VectorOfDescriptorSetAndBindingPairs{}},
133            // 14. descriptor set represented in hex float format is invalid
134            {"0x3p10:200", false, VectorOfDescriptorSetAndBindingPairs{}},
135        }));
136
137std::string BuildShader(const char* shader_decorate_instructions,
138                        const char* shader_image_and_sampler_variables,
139                        const char* shader_body) {
140  // Base HLSL code:
141  //
142  // SamplerState sam : register(s2);
143  // Texture2D <float4> texture : register(t5);
144  //
145  // float4 main() : SV_TARGET {
146  //     return texture.SampleLevel(sam, float2(1, 2), 10, 2);
147  // }
148  std::stringstream ss;
149  ss << R"(
150               OpCapability Shader
151               OpMemoryModel Logical GLSL450
152               OpEntryPoint Fragment %main "main" %out_var_SV_TARGET
153               OpExecutionMode %main OriginUpperLeft
154               OpSource HLSL 600
155               OpName %type_sampler "type.sampler"
156               OpName %type_2d_image "type.2d.image"
157               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
158               OpName %main "main"
159               OpName %type_sampled_image "type.sampled.image"
160               OpDecorate %out_var_SV_TARGET Location 0
161               )";
162  ss << shader_decorate_instructions;
163  ss << R"(
164      %float = OpTypeFloat 32
165    %float_1 = OpConstant %float 1
166    %float_2 = OpConstant %float 2
167    %v2float = OpTypeVector %float 2
168         %12 = OpConstantComposite %v2float %float_1 %float_2
169   %float_10 = OpConstant %float 10
170        %int = OpTypeInt 32 1
171      %int_2 = OpConstant %int 2
172      %v2int = OpTypeVector %int 2
173         %17 = OpConstantComposite %v2int %int_2 %int_2
174%type_sampler = OpTypeSampler
175%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
176%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
177%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
178    %v4float = OpTypeVector %float 4
179%_ptr_Output_v4float = OpTypePointer Output %v4float
180       %void = OpTypeVoid
181         %23 = OpTypeFunction %void
182%type_sampled_image = OpTypeSampledImage %type_2d_image
183               )";
184  ss << shader_image_and_sampler_variables;
185  ss << R"(
186%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
187       %main = OpFunction %void None %23
188         %24 = OpLabel
189  )";
190  ss << shader_body;
191  ss << R"(
192               OpReturn
193               OpFunctionEnd
194  )";
195  return ss.str();
196}
197
198using ConvertToSampledImageTest = PassTest<::testing::Test>;
199
200TEST_F(ConvertToSampledImageTest, Texture2DAndSamplerToSampledImage) {
201  const std::string shader = BuildShader(
202      R"(
203               OpDecorate %sam DescriptorSet 0
204               OpDecorate %sam Binding 5
205               OpDecorate %texture DescriptorSet 0
206               OpDecorate %texture Binding 5
207               )",
208      R"(
209            ; CHECK-NOT: OpVariable %_ptr_UniformConstant_type_2d_image
210
211            ; CHECK: [[tex:%\w+]] = OpVariable %_ptr_UniformConstant_type_sampled_image UniformConstant
212        %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
213    %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
214               )",
215      R"(
216            ; CHECK: [[load:%\w+]] = OpLoad %type_sampled_image [[tex]]
217            ; CHECK: OpImageSampleExplicitLod %v4float [[load]]
218         %25 = OpLoad %type_2d_image %texture
219         %26 = OpLoad %type_sampler %sam
220         %27 = OpSampledImage %type_sampled_image %25 %26
221         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
222               OpStore %out_var_SV_TARGET %28
223               )");
224
225  auto result = SinglePassRunAndMatch<ConvertToSampledImagePass>(
226      shader, /* do_validate = */ true,
227      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 5}});
228
229  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
230}
231
232TEST_F(ConvertToSampledImageTest, Texture2DToSampledImage) {
233  const std::string shader = BuildShader(
234      R"(
235               OpDecorate %sam DescriptorSet 0
236               OpDecorate %sam Binding 2
237               OpDecorate %texture DescriptorSet 0
238               OpDecorate %texture Binding 5
239               )",
240      R"(
241            ; CHECK: [[tex:%\w+]] = OpVariable %_ptr_UniformConstant_type_sampled_image UniformConstant
242        %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
243    %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
244               )",
245      R"(
246            ; CHECK: [[load:%\w+]] = OpLoad %type_sampled_image [[tex]]
247            ; CHECK: [[image_extraction:%\w+]] = OpImage %type_2d_image [[load]]
248            ; CHECK: OpSampledImage %type_sampled_image [[image_extraction]]
249         %25 = OpLoad %type_2d_image %texture
250         %26 = OpLoad %type_sampler %sam
251         %27 = OpSampledImage %type_sampled_image %25 %26
252         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
253               OpStore %out_var_SV_TARGET %28
254               )");
255
256  auto result = SinglePassRunAndMatch<ConvertToSampledImagePass>(
257      shader, /* do_validate = */ true,
258      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 5}});
259
260  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
261}
262
263TEST_F(ConvertToSampledImageTest, SamplerToSampledImage) {
264  const std::string shader = BuildShader(
265      R"(
266               OpDecorate %sam DescriptorSet 0
267               OpDecorate %sam Binding 2
268               OpDecorate %texture DescriptorSet 0
269               OpDecorate %texture Binding 5
270               )",
271      R"(
272        %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
273    %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
274               )",
275      R"(
276         %25 = OpLoad %type_2d_image %texture
277         %26 = OpLoad %type_sampler %sam
278         %27 = OpSampledImage %type_sampled_image %25 %26
279         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
280               OpStore %out_var_SV_TARGET %28
281               )");
282
283  auto result = SinglePassRunToBinary<ConvertToSampledImagePass>(
284      shader, /* skip_nop = */ false,
285      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 2}});
286
287  EXPECT_EQ(std::get<1>(result), Pass::Status::Failure);
288}
289
290TEST_F(ConvertToSampledImageTest, TwoImagesWithDuplicatedDescriptorSetBinding) {
291  const std::string shader = BuildShader(
292      R"(
293               OpDecorate %sam DescriptorSet 0
294               OpDecorate %sam Binding 2
295               OpDecorate %texture0 DescriptorSet 0
296               OpDecorate %texture0 Binding 5
297               OpDecorate %texture1 DescriptorSet 0
298               OpDecorate %texture1 Binding 5
299               )",
300      R"(
301        %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
302   %texture0 = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
303   %texture1 = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
304               )",
305      R"(
306         %25 = OpLoad %type_2d_image %texture0
307         %26 = OpLoad %type_sampler %sam
308         %27 = OpSampledImage %type_sampled_image %25 %26
309         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
310               OpStore %out_var_SV_TARGET %28
311               )");
312
313  auto result = SinglePassRunToBinary<ConvertToSampledImagePass>(
314      shader, /* skip_nop = */ false,
315      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 5}});
316
317  EXPECT_EQ(std::get<1>(result), Pass::Status::Failure);
318}
319
320TEST_F(ConvertToSampledImageTest,
321       TwoSamplersWithDuplicatedDescriptorSetBinding) {
322  const std::string shader = BuildShader(
323      R"(
324               OpDecorate %sam0 DescriptorSet 0
325               OpDecorate %sam0 Binding 2
326               OpDecorate %sam1 DescriptorSet 0
327               OpDecorate %sam1 Binding 2
328               OpDecorate %texture DescriptorSet 0
329               OpDecorate %texture Binding 5
330               )",
331      R"(
332       %sam0 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
333       %sam1 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
334    %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
335               )",
336      R"(
337         %25 = OpLoad %type_2d_image %texture
338         %26 = OpLoad %type_sampler %sam0
339         %27 = OpSampledImage %type_sampled_image %25 %26
340         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
341               OpStore %out_var_SV_TARGET %28
342               )");
343
344  auto result = SinglePassRunToBinary<ConvertToSampledImagePass>(
345      shader, /* skip_nop = */ false,
346      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 2}});
347
348  EXPECT_EQ(std::get<1>(result), Pass::Status::Failure);
349}
350
351}  // namespace
352}  // namespace opt
353}  // namespace spvtools
354