1 //
2 // Copyright 2021 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // Precise_test.cpp:
7 // Test that precise produces the right number of NoContraction decorations in the generated
8 // SPIR-V.
9 //
10
11 #include "GLSLANG/ShaderLang.h"
12 #include "angle_gl.h"
13 #include "common/spirv/spirv_instruction_parser_autogen.h"
14 #include "gtest/gtest.h"
15
16 namespace spirv = angle::spirv;
17
18 namespace
19 {
20 class PreciseTest : public testing::TestWithParam<bool>
21 {
22 public:
SetUp()23 void SetUp() override
24 {
25 std::map<ShShaderOutput, std::string> shaderOutputList = {
26 {SH_SPIRV_VULKAN_OUTPUT, "SH_SPIRV_VULKAN_OUTPUT"}};
27
28 Initialize(shaderOutputList);
29 }
30
TearDown()31 void TearDown() override
32 {
33 for (auto shaderOutputType : mShaderOutputList)
34 {
35 DestroyCompiler(shaderOutputType.first);
36 }
37 }
38
Initialize(std::map<ShShaderOutput,std::string> & shaderOutputList)39 void Initialize(std::map<ShShaderOutput, std::string> &shaderOutputList)
40 {
41 mShaderOutputList = std::move(shaderOutputList);
42
43 for (auto shaderOutputType : mShaderOutputList)
44 {
45 sh::InitBuiltInResources(&mResourceList[shaderOutputType.first]);
46 mCompilerList[shaderOutputType.first] = nullptr;
47 }
48 }
49
DestroyCompiler(ShShaderOutput shaderOutputType)50 void DestroyCompiler(ShShaderOutput shaderOutputType)
51 {
52 if (mCompilerList[shaderOutputType])
53 {
54 sh::Destruct(mCompilerList[shaderOutputType]);
55 mCompilerList[shaderOutputType] = nullptr;
56 }
57 }
58
InitializeCompiler()59 void InitializeCompiler()
60 {
61 for (auto shaderOutputType : mShaderOutputList)
62 {
63 InitializeCompiler(shaderOutputType.first);
64 }
65 }
66
InitializeCompiler(ShShaderOutput shaderOutputType)67 void InitializeCompiler(ShShaderOutput shaderOutputType)
68 {
69 DestroyCompiler(shaderOutputType);
70
71 mCompilerList[shaderOutputType] = sh::ConstructCompiler(
72 GL_VERTEX_SHADER, SH_GLES3_2_SPEC, shaderOutputType, &mResourceList[shaderOutputType]);
73 ASSERT_TRUE(mCompilerList[shaderOutputType] != nullptr)
74 << "Compiler for " << mShaderOutputList[shaderOutputType]
75 << " could not be constructed.";
76 }
77
TestShaderCompile(ShShaderOutput shaderOutputType,const char * shaderSource)78 testing::AssertionResult TestShaderCompile(ShShaderOutput shaderOutputType,
79 const char *shaderSource)
80 {
81 const char *shaderStrings[] = {shaderSource};
82
83 ShCompileOptions options = {};
84 options.objectCode = true;
85
86 bool success = sh::Compile(mCompilerList[shaderOutputType], shaderStrings, 1, options);
87 if (success)
88 {
89 return ::testing::AssertionSuccess()
90 << "Compilation success(" << mShaderOutputList[shaderOutputType] << ")";
91 }
92 return ::testing::AssertionFailure() << sh::GetInfoLog(mCompilerList[shaderOutputType]);
93 }
94
TestShaderCompile(const char * shaderSource,size_t expectedNoContractionDecorationCount)95 void TestShaderCompile(const char *shaderSource, size_t expectedNoContractionDecorationCount)
96 {
97 for (auto shaderOutputType : mShaderOutputList)
98 {
99 EXPECT_TRUE(TestShaderCompile(shaderOutputType.first, shaderSource));
100
101 const spirv::Blob &blob =
102 sh::GetObjectBinaryBlob(mCompilerList[shaderOutputType.first]);
103 ValidateDecorations(blob, expectedNoContractionDecorationCount);
104 }
105 }
106
107 void ValidateDecorations(const spirv::Blob &blob, size_t expectedNoContractionDecorationCount);
108
109 private:
110 std::map<ShShaderOutput, std::string> mShaderOutputList;
111 std::map<ShShaderOutput, ShHandle> mCompilerList;
112 std::map<ShShaderOutput, ShBuiltInResources> mResourceList;
113 };
114
115 // Parse the SPIR-V and verify that there are as many NoContraction decorations as expected.
ValidateDecorations(const spirv::Blob & blob,size_t expectedNoContractionDecorationCount)116 void PreciseTest::ValidateDecorations(const spirv::Blob &blob,
117 size_t expectedNoContractionDecorationCount)
118 {
119 size_t currentWord = spirv::kHeaderIndexInstructions;
120 size_t noContractionDecorationCount = 0;
121
122 while (currentWord < blob.size())
123 {
124 uint32_t wordCount;
125 spv::Op opCode;
126 const uint32_t *instruction = &blob[currentWord];
127 spirv::GetInstructionOpAndLength(instruction, &opCode, &wordCount);
128
129 currentWord += wordCount;
130
131 // Early out when the decorations section is visited.
132 if (opCode == spv::OpTypeVoid || opCode == spv::OpTypeInt || opCode == spv::OpTypeFloat ||
133 opCode == spv::OpTypeBool)
134 {
135 break;
136 }
137
138 if (opCode == spv::OpMemberDecorate)
139 {
140 spirv::IdRef type;
141 spirv::LiteralInteger member;
142 spv::Decoration decoration;
143 spirv::ParseMemberDecorate(instruction, &type, &member, &decoration, nullptr);
144
145 // NoContraction should be applied to arithmetic instructions, and should not be seen on
146 // block members.
147 EXPECT_NE(decoration, spv::DecorationNoContraction);
148 }
149 else if (opCode == spv::OpDecorate)
150 {
151 spirv::IdRef target;
152 spv::Decoration decoration;
153 spirv::ParseDecorate(instruction, &target, &decoration, nullptr);
154
155 if (decoration == spv::DecorationNoContraction)
156 {
157 ++noContractionDecorationCount;
158 }
159 }
160 }
161
162 EXPECT_EQ(noContractionDecorationCount, expectedNoContractionDecorationCount);
163 }
164
165 // Test that precise on a local variable works.
TEST_F(PreciseTest,LocalVariable)166 TEST_F(PreciseTest, LocalVariable)
167 {
168 constexpr char kVS[] = R"(#version 320 es
169
170 uniform float u;
171
172 void main()
173 {
174 float f1 = u, f2 = u; // f1 is precise, but f2 isn't.
175
176 f1 += 1.0; // NoContraction
177 f2 += 1.0;
178
179 float f3 = f1 * f1; // NoContraction
180 f3 /= 2.0; // NoContraction
181
182 int i1 = int(f3); // i1 is precise
183 ++i1; // NoContraction
184 --i1; // NoContraction
185 i1++; // NoContraction
186 i1--; // NoContraction
187
188 int i2 = i1 % 3;
189 f2 -= float(i2);
190
191 precise float p = float(i1) / 1.5; // NoContraction
192
193 gl_Position = vec4(p, f2, 0, 0);
194 })";
195
196 InitializeCompiler();
197 TestShaderCompile(kVS, 8);
198 }
199
200 // Test that precise on gl_Position works.
TEST_F(PreciseTest,Position)201 TEST_F(PreciseTest, Position)
202 {
203 constexpr char kVS[] = R"(#version 320 es
204
205 uniform float u;
206
207 out float o;
208
209 precise gl_Position;
210
211 void main()
212 {
213 mat4 m1 = mat4(u); // m1 is precise, even if not all components are used to determine the
214 // gl_Position.
215 vec4 v1 = vec4(u); // v1 is precise
216
217 vec4 v2 = m1 * v1;
218 v1 *= m1; // NoContraction
219 m1 *= m1; // NoContraction
220 m1 *= u; // NoContraction
221 v1 *= u; // NoContraction
222
223 float f1 = dot(v1, v1);
224 float f2 = dot(v1, v1); // NoContraction
225
226 gl_Position = vec4(m1[0][0], v1[0], f2, 0);
227 o = f1;
228 })";
229
230 InitializeCompiler();
231 TestShaderCompile(kVS, 5);
232 }
233
234 // Test that precise on function parameters and return value works.
TEST_F(PreciseTest,Functions)235 TEST_F(PreciseTest, Functions)
236 {
237 constexpr char kVS[] = R"(#version 320 es
238
239 uniform float u;
240
241 struct S1
242 {
243 float f;
244 int i;
245 };
246
247 precise float f1(S1 s, out int io)
248 {
249 float f = s.f; // f is precise
250 f *= float(s.i); // NoContraction
251
252 io = s.i;
253 ++io;
254
255 return s.f / f; // NoContraction
256 }
257
258 void f2(S1 s, precise out S1 so)
259 {
260 float f = s.f; // f is precise
261 f /= float(s.i); // NoContraction
262
263 int i = s.i; // i is precise
264 ++i; // NoContraction
265
266 so = S1(f, i);
267 }
268
269 void main()
270 {
271 precise S1 s1;
272 S1 s2;
273
274 int i;
275 float f = f1(s1, i); // f1's return value being precise doesn't affect f
276
277 f2(s1, s2); // f2's out parameter being precise doesn't affect s2
278
279 i /= 2;
280 f *= 2.0;
281 s2.f += float(s2.i);
282
283 gl_Position = vec4(s1.f);
284 })";
285
286 InitializeCompiler();
287 TestShaderCompile(kVS, 4);
288 }
289
290 } // anonymous namespace
291