1 //
2 // Copyright 2018 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 // VulkanUniformUpdatesTest:
7 // Tests to validate our Vulkan dynamic uniform updates are working as expected.
8 //
9
10 #include "test_utils/ANGLETest.h"
11 #include "test_utils/angle_test_instantiate.h"
12 // 'None' is defined as 'struct None {};' in
13 // third_party/googletest/src/googletest/include/gtest/internal/gtest-type-util.h.
14 // But 'None' is also defined as a numeric constant 0L in <X11/X.h>.
15 // So we need to include ANGLETest.h first to avoid this conflict.
16
17 #include "libANGLE/Context.h"
18 #include "libANGLE/angletypes.h"
19 #include "libANGLE/renderer/vulkan/ContextVk.h"
20 #include "libANGLE/renderer/vulkan/ProgramVk.h"
21 #include "libANGLE/renderer/vulkan/TextureVk.h"
22 #include "test_utils/gl_raii.h"
23 #include "util/EGLWindow.h"
24 #include "util/shader_utils.h"
25
26 using namespace angle;
27
28 namespace
29 {
30
31 class VulkanUniformUpdatesTest : public ANGLETest
32 {
33 protected:
hackANGLE() const34 rx::ContextVk *hackANGLE() const
35 {
36 // Hack the angle!
37 const gl::Context *context = static_cast<gl::Context *>(getEGLWindow()->getContext());
38 return rx::GetImplAs<rx::ContextVk>(context);
39 }
40
hackProgram(GLuint handle) const41 rx::ProgramVk *hackProgram(GLuint handle) const
42 {
43 // Hack the angle!
44 const gl::Context *context = static_cast<gl::Context *>(getEGLWindow()->getContext());
45 const gl::Program *program = context->getProgramResolveLink({handle});
46 return rx::vk::GetImpl(program);
47 }
48
hackTexture(GLuint handle) const49 rx::TextureVk *hackTexture(GLuint handle) const
50 {
51 // Hack the angle!
52 const gl::Context *context = static_cast<gl::Context *>(getEGLWindow()->getContext());
53 const gl::Texture *texture = context->getTexture({handle});
54 return rx::vk::GetImpl(texture);
55 }
56
57 static constexpr uint32_t kMaxSetsForTesting = 32;
58
limitMaxSets(GLuint program)59 void limitMaxSets(GLuint program)
60 {
61 rx::ContextVk *contextVk = hackANGLE();
62 rx::ProgramVk *programVk = hackProgram(program);
63
64 // Force a small limit on the max sets per pool to more easily trigger a new allocation.
65 rx::vk::DynamicDescriptorPool *uniformPool =
66 programVk->getDynamicDescriptorPool(rx::kUniformsAndXfbDescriptorSetIndex);
67 uniformPool->setMaxSetsPerPoolForTesting(kMaxSetsForTesting);
68 VkDescriptorPoolSize uniformSetSize = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
69 rx::kReservedDefaultUniformBindingCount};
70 (void)uniformPool->init(contextVk, &uniformSetSize, 1);
71
72 uint32_t textureCount =
73 static_cast<uint32_t>(programVk->getState().getSamplerBindings().size());
74
75 // To support the bindEmptyForUnusedDescriptorSets workaround.
76 textureCount = std::max(textureCount, 1u);
77
78 rx::vk::DynamicDescriptorPool *texturePool =
79 programVk->getDynamicDescriptorPool(rx::kTextureDescriptorSetIndex);
80 texturePool->setMaxSetsPerPoolForTesting(kMaxSetsForTesting);
81 VkDescriptorPoolSize textureSetSize = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
82 textureCount};
83 (void)texturePool->init(contextVk, &textureSetSize, 1);
84 }
85
86 static constexpr size_t kTextureStagingBufferSizeForTesting = 128;
87
limitTextureStagingBufferSize(GLuint texture)88 void limitTextureStagingBufferSize(GLuint texture)
89 {
90 rx::TextureVk *textureVk = hackTexture(texture);
91 textureVk->overrideStagingBufferSizeForTesting(kTextureStagingBufferSizeForTesting);
92 }
93 };
94
95 // This test updates a uniform until a new buffer is allocated and then make sure the uniform
96 // updates still work.
TEST_P(VulkanUniformUpdatesTest,UpdateUntilNewBufferIsAllocated)97 TEST_P(VulkanUniformUpdatesTest, UpdateUntilNewBufferIsAllocated)
98 {
99 ASSERT_TRUE(IsVulkan());
100
101 constexpr char kPositionUniformVertexShader[] = R"(attribute vec2 position;
102 uniform vec2 uniPosModifier;
103 void main()
104 {
105 gl_Position = vec4(position + uniPosModifier, 0, 1);
106 })";
107
108 constexpr char kColorUniformFragmentShader[] = R"(precision mediump float;
109 uniform vec4 uniColor;
110 void main()
111 {
112 gl_FragColor = uniColor;
113 })";
114
115 ANGLE_GL_PROGRAM(program, kPositionUniformVertexShader, kColorUniformFragmentShader);
116 glUseProgram(program);
117
118 limitMaxSets(program);
119
120 rx::ProgramVk *programVk = hackProgram(program);
121
122 // Set a really small min size so that uniform updates often allocates a new buffer.
123 programVk->setDefaultUniformBlocksMinSizeForTesting(128);
124
125 GLint posUniformLocation = glGetUniformLocation(program, "uniPosModifier");
126 ASSERT_NE(posUniformLocation, -1);
127 GLint colorUniformLocation = glGetUniformLocation(program, "uniColor");
128 ASSERT_NE(colorUniformLocation, -1);
129
130 // Sets both uniforms 10 times, it should certainly trigger new buffers creations by the
131 // underlying StreamingBuffer.
132 for (int i = 0; i < 100; i++)
133 {
134 glUniform2f(posUniformLocation, -0.5, 0.0);
135 glUniform4f(colorUniformLocation, 1.0, 0.0, 0.0, 1.0);
136 drawQuad(program, "position", 0.5f, 1.0f);
137 swapBuffers();
138 ASSERT_GL_NO_ERROR();
139 }
140 }
141
InitTexture(GLColor color,GLTexture * texture)142 void InitTexture(GLColor color, GLTexture *texture)
143 {
144 const std::vector<GLColor> colors(4, color);
145 glBindTexture(GL_TEXTURE_2D, *texture);
146 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
147 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
148 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
149 }
150
151 // Force uniform updates until the dynamic descriptor pool wraps into a new pool allocation.
TEST_P(VulkanUniformUpdatesTest,DescriptorPoolUpdates)152 TEST_P(VulkanUniformUpdatesTest, DescriptorPoolUpdates)
153 {
154 ASSERT_TRUE(IsVulkan());
155
156 // Initialize texture program.
157 GLuint program = get2DTexturedQuadProgram();
158 ASSERT_NE(0u, program);
159 glUseProgram(program);
160
161 // Force a small limit on the max sets per pool to more easily trigger a new allocation.
162 limitMaxSets(program);
163
164 GLint texLoc = glGetUniformLocation(program, "tex");
165 ASSERT_NE(-1, texLoc);
166
167 // Initialize basic red texture.
168 const std::vector<GLColor> redColors(4, GLColor::red);
169 GLTexture texture;
170 glBindTexture(GL_TEXTURE_2D, texture);
171 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, redColors.data());
172 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
173 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
174 ASSERT_GL_NO_ERROR();
175
176 // Draw multiple times, each iteration will create a new descriptor set.
177 for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 8; ++iteration)
178 {
179 glUniform1i(texLoc, 0);
180 drawQuad(program, "position", 0.5f, 1.0f, true);
181 swapBuffers();
182 ASSERT_GL_NO_ERROR();
183 }
184 }
185
186 // Uniform updates along with Texture updates.
TEST_P(VulkanUniformUpdatesTest,DescriptorPoolUniformAndTextureUpdates)187 TEST_P(VulkanUniformUpdatesTest, DescriptorPoolUniformAndTextureUpdates)
188 {
189 ASSERT_TRUE(IsVulkan());
190
191 // Initialize texture program.
192 constexpr char kFS[] = R"(varying mediump vec2 v_texCoord;
193 uniform sampler2D tex;
194 uniform mediump vec4 colorMask;
195 void main()
196 {
197 gl_FragColor = texture2D(tex, v_texCoord) * colorMask;
198 })";
199
200 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), kFS);
201 glUseProgram(program);
202
203 limitMaxSets(program);
204
205 // Get uniform locations.
206 GLint texLoc = glGetUniformLocation(program, "tex");
207 ASSERT_NE(-1, texLoc);
208
209 GLint colorMaskLoc = glGetUniformLocation(program, "colorMask");
210 ASSERT_NE(-1, colorMaskLoc);
211
212 // Initialize white texture.
213 GLTexture whiteTexture;
214 InitTexture(GLColor::white, &whiteTexture);
215 ASSERT_GL_NO_ERROR();
216
217 glActiveTexture(GL_TEXTURE0);
218 glBindTexture(GL_TEXTURE_2D, whiteTexture);
219
220 // Initialize magenta texture.
221 GLTexture magentaTexture;
222 InitTexture(GLColor::magenta, &magentaTexture);
223 ASSERT_GL_NO_ERROR();
224
225 glActiveTexture(GL_TEXTURE1);
226 glBindTexture(GL_TEXTURE_2D, magentaTexture);
227
228 // Draw multiple times, each iteration will create a new descriptor set.
229 for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 2; ++iteration)
230 {
231 // Draw with white.
232 glUniform1i(texLoc, 0);
233 glUniform4f(colorMaskLoc, 1.0f, 1.0f, 1.0f, 1.0f);
234 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
235
236 // Draw with white masking out red.
237 glUniform4f(colorMaskLoc, 0.0f, 1.0f, 1.0f, 1.0f);
238 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
239
240 // Draw with magenta.
241 glUniform1i(texLoc, 1);
242 glUniform4f(colorMaskLoc, 1.0f, 1.0f, 1.0f, 1.0f);
243 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
244
245 // Draw with magenta masking out red.
246 glUniform4f(colorMaskLoc, 0.0f, 1.0f, 1.0f, 1.0f);
247 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
248
249 swapBuffers();
250 ASSERT_GL_NO_ERROR();
251 }
252 }
253
254 // Uniform updates along with Texture regeneration.
TEST_P(VulkanUniformUpdatesTest,DescriptorPoolUniformAndTextureRegeneration)255 TEST_P(VulkanUniformUpdatesTest, DescriptorPoolUniformAndTextureRegeneration)
256 {
257 ASSERT_TRUE(IsVulkan());
258
259 // Initialize texture program.
260 constexpr char kFS[] = R"(varying mediump vec2 v_texCoord;
261 uniform sampler2D tex;
262 uniform mediump vec4 colorMask;
263 void main()
264 {
265 gl_FragColor = texture2D(tex, v_texCoord) * colorMask;
266 })";
267
268 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), kFS);
269 glUseProgram(program);
270
271 limitMaxSets(program);
272
273 // Initialize large arrays of textures.
274 std::vector<GLTexture> whiteTextures;
275 std::vector<GLTexture> magentaTextures;
276
277 for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 2; ++iteration)
278 {
279 // Initialize white texture.
280 GLTexture whiteTexture;
281 InitTexture(GLColor::white, &whiteTexture);
282 ASSERT_GL_NO_ERROR();
283 whiteTextures.emplace_back(std::move(whiteTexture));
284
285 // Initialize magenta texture.
286 GLTexture magentaTexture;
287 InitTexture(GLColor::magenta, &magentaTexture);
288 ASSERT_GL_NO_ERROR();
289 magentaTextures.emplace_back(std::move(magentaTexture));
290 }
291
292 // Get uniform locations.
293 GLint texLoc = glGetUniformLocation(program, "tex");
294 ASSERT_NE(-1, texLoc);
295
296 GLint colorMaskLoc = glGetUniformLocation(program, "colorMask");
297 ASSERT_NE(-1, colorMaskLoc);
298
299 // Draw multiple times, each iteration will create a new descriptor set.
300 for (int outerIteration = 0; outerIteration < 2; ++outerIteration)
301 {
302 for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 2; ++iteration)
303 {
304 glActiveTexture(GL_TEXTURE0);
305 glBindTexture(GL_TEXTURE_2D, whiteTextures[iteration]);
306
307 glActiveTexture(GL_TEXTURE1);
308 glBindTexture(GL_TEXTURE_2D, magentaTextures[iteration]);
309
310 // Draw with white.
311 glUniform1i(texLoc, 0);
312 glUniform4f(colorMaskLoc, 1.0f, 1.0f, 1.0f, 1.0f);
313 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
314
315 // Draw with white masking out red.
316 glUniform4f(colorMaskLoc, 0.0f, 1.0f, 1.0f, 1.0f);
317 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
318
319 // Draw with magenta.
320 glUniform1i(texLoc, 1);
321 glUniform4f(colorMaskLoc, 1.0f, 1.0f, 1.0f, 1.0f);
322 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
323
324 // Draw with magenta masking out red.
325 glUniform4f(colorMaskLoc, 0.0f, 1.0f, 1.0f, 1.0f);
326 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
327
328 swapBuffers();
329 ASSERT_GL_NO_ERROR();
330 }
331 }
332 }
333
334 // Uniform updates along with Texture updates.
TEST_P(VulkanUniformUpdatesTest,DescriptorPoolUniformAndTextureUpdatesTwoShaders)335 TEST_P(VulkanUniformUpdatesTest, DescriptorPoolUniformAndTextureUpdatesTwoShaders)
336 {
337 ASSERT_TRUE(IsVulkan());
338
339 // Initialize program.
340 constexpr char kVS[] = R"(attribute vec2 position;
341 varying mediump vec2 texCoord;
342 void main()
343 {
344 gl_Position = vec4(position, 0, 1);
345 texCoord = position * 0.5 + vec2(0.5);
346 })";
347
348 constexpr char kFS[] = R"(varying mediump vec2 texCoord;
349 uniform mediump vec4 colorMask;
350 void main()
351 {
352 gl_FragColor = colorMask;
353 })";
354
355 ANGLE_GL_PROGRAM(program1, kVS, kFS);
356 ANGLE_GL_PROGRAM(program2, kVS, kFS);
357 glUseProgram(program1);
358
359 // Force a small limit on the max sets per pool to more easily trigger a new allocation.
360 limitMaxSets(program1);
361 limitMaxSets(program2);
362
363 rx::ProgramVk *program1Vk = hackProgram(program1);
364 rx::ProgramVk *program2Vk = hackProgram(program2);
365
366 // Set a really small min size so that uniform updates often allocates a new buffer.
367 program1Vk->setDefaultUniformBlocksMinSizeForTesting(128);
368 program2Vk->setDefaultUniformBlocksMinSizeForTesting(128);
369
370 // Get uniform locations.
371 GLint colorMaskLoc1 = glGetUniformLocation(program1, "colorMask");
372 ASSERT_NE(-1, colorMaskLoc1);
373 GLint colorMaskLoc2 = glGetUniformLocation(program2, "colorMask");
374 ASSERT_NE(-1, colorMaskLoc2);
375
376 // Draw with white using program1.
377 glUniform4f(colorMaskLoc1, 1.0f, 1.0f, 1.0f, 1.0f);
378 drawQuad(program1, "position", 0.5f, 1.0f, true);
379 swapBuffers();
380 ASSERT_GL_NO_ERROR();
381
382 // Now switch to use program2
383 glUseProgram(program2);
384 // Draw multiple times w/ program2, each iteration will create a new descriptor set.
385 // This will cause the first descriptor pool to be cleaned up
386 for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 2; ++iteration)
387 {
388 // Draw with white.
389 glUniform4f(colorMaskLoc2, 1.0f, 1.0f, 1.0f, 1.0f);
390 drawQuad(program2, "position", 0.5f, 1.0f, true);
391
392 // Draw with white masking out red.
393 glUniform4f(colorMaskLoc2, 0.0f, 1.0f, 1.0f, 1.0f);
394 drawQuad(program2, "position", 0.5f, 1.0f, true);
395
396 // Draw with magenta.
397 glUniform4f(colorMaskLoc2, 1.0f, 1.0f, 1.0f, 1.0f);
398 drawQuad(program2, "position", 0.5f, 1.0f, true);
399
400 // Draw with magenta masking out red.
401 glUniform4f(colorMaskLoc2, 0.0f, 1.0f, 1.0f, 1.0f);
402 drawQuad(program2, "position", 0.5f, 1.0f, true);
403
404 swapBuffers();
405 ASSERT_GL_NO_ERROR();
406 }
407 // Finally, attempt to draw again with program1, with original uniform values.
408 glUseProgram(program1);
409 drawQuad(program1, "position", 0.5f, 1.0f, true);
410 swapBuffers();
411 ASSERT_GL_NO_ERROR();
412 }
413
414 // Verify that overflowing a Texture's staging buffer doesn't overwrite current data.
TEST_P(VulkanUniformUpdatesTest,TextureStagingBufferRecycling)415 TEST_P(VulkanUniformUpdatesTest, TextureStagingBufferRecycling)
416 {
417 ASSERT_TRUE(IsVulkan());
418
419 // Fails on older MESA drivers. http://crbug.com/979349
420 ANGLE_SKIP_TEST_IF(IsAMD() && IsLinux());
421
422 GLTexture tex;
423 glBindTexture(GL_TEXTURE_2D, tex);
424 limitTextureStagingBufferSize(tex);
425 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
426 GL_UNSIGNED_BYTE, nullptr);
427 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
428 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
429
430 const GLColor kColors[4] = {GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow};
431
432 // Repeatedly update the staging buffer to trigger multiple recyclings.
433 const GLsizei kHalfX = getWindowWidth() / 2;
434 const GLsizei kHalfY = getWindowHeight() / 2;
435 constexpr int kIterations = 4;
436 for (int x = 0; x < 2; ++x)
437 {
438 for (int y = 0; y < 2; ++y)
439 {
440 const int kColorIndex = x + y * 2;
441 const GLColor kColor = kColors[kColorIndex];
442
443 for (int iteration = 0; iteration < kIterations; ++iteration)
444 {
445 for (int subX = 0; subX < kHalfX; ++subX)
446 {
447 for (int subY = 0; subY < kHalfY; ++subY)
448 {
449 const GLsizei xoffset = x * kHalfX + subX;
450 const GLsizei yoffset = y * kHalfY + subY;
451
452 // Update a single pixel.
453 glTexSubImage2D(GL_TEXTURE_2D, 0, xoffset, yoffset, 1, 1, GL_RGBA,
454 GL_UNSIGNED_BYTE, kColor.data());
455 }
456 }
457 }
458 }
459 }
460
461 draw2DTexturedQuad(0.5f, 1.0f, true);
462 ASSERT_GL_NO_ERROR();
463
464 // Verify pixels.
465 for (int x = 0; x < 2; ++x)
466 {
467 for (int y = 0; y < 2; ++y)
468 {
469 const GLsizei xoffset = x * kHalfX;
470 const GLsizei yoffset = y * kHalfY;
471 const int kColorIndex = x + y * 2;
472 const GLColor kColor = kColors[kColorIndex];
473 EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, kColor);
474 }
475 }
476 }
477
478 ANGLE_INSTANTIATE_TEST(VulkanUniformUpdatesTest, ES2_VULKAN(), ES3_VULKAN());
479
480 } // anonymous namespace
481