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 // EGLBlobCacheTest:
7 // Unit tests for the EGL_ANDROID_blob_cache extension.
8
9 // Must be included first to prevent errors with "None".
10 #include "test_utils/ANGLETest.h"
11
12 #include <map>
13 #include <vector>
14
15 #include "common/PackedEnums.h"
16 #include "common/angleutils.h"
17 #include "test_utils/ANGLETest.h"
18 #include "test_utils/MultiThreadSteps.h"
19 #include "test_utils/gl_raii.h"
20 #include "util/EGLWindow.h"
21 #include "util/test_utils.h"
22
23 using namespace angle;
24
25 constexpr char kEGLExtName[] = "EGL_ANDROID_blob_cache";
26
27 enum class CacheOpResult
28 {
29 SetSuccess,
30 GetNotFound,
31 GetMemoryTooSmall,
32 GetSuccess,
33 ValueNotSet,
34 EnumCount
35 };
36
37 angle::PackedEnumMap<CacheOpResult, std::string> kCacheOpToString = {
38 {CacheOpResult::SetSuccess, "SetSuccess"},
39 {CacheOpResult::GetNotFound, "GetNotFound"},
40 {CacheOpResult::GetMemoryTooSmall, "GetMemoryTooSmall"},
41 {CacheOpResult::GetSuccess, "GetSuccess"},
42 {CacheOpResult::ValueNotSet, "ValueNotSet"},
43 };
44
operator <<(std::ostream & os,CacheOpResult result)45 std::ostream &operator<<(std::ostream &os, CacheOpResult result)
46 {
47 return os << kCacheOpToString[result];
48 }
49
50 namespace
51 {
52 std::map<std::vector<uint8_t>, std::vector<uint8_t>> gApplicationCache;
53 CacheOpResult gLastCacheOpResult = CacheOpResult::ValueNotSet;
54
SetBlob(const void * key,EGLsizeiANDROID keySize,const void * value,EGLsizeiANDROID valueSize)55 void SetBlob(const void *key, EGLsizeiANDROID keySize, const void *value, EGLsizeiANDROID valueSize)
56 {
57 std::vector<uint8_t> keyVec(keySize);
58 memcpy(keyVec.data(), key, keySize);
59
60 std::vector<uint8_t> valueVec(valueSize);
61 memcpy(valueVec.data(), value, valueSize);
62
63 gApplicationCache[keyVec] = valueVec;
64
65 gLastCacheOpResult = CacheOpResult::SetSuccess;
66 }
67
SetCorruptedBlob(const void * key,EGLsizeiANDROID keySize,const void * value,EGLsizeiANDROID valueSize)68 void SetCorruptedBlob(const void *key,
69 EGLsizeiANDROID keySize,
70 const void *value,
71 EGLsizeiANDROID valueSize)
72 {
73 std::vector<uint8_t> keyVec(keySize);
74 memcpy(keyVec.data(), key, keySize);
75
76 std::vector<uint8_t> valueVec(valueSize);
77 memcpy(valueVec.data(), value, valueSize);
78
79 // Corrupt the data
80 ++valueVec[valueVec.size() / 2];
81 ++valueVec[valueVec.size() / 3];
82 ++valueVec[valueVec.size() / 4];
83 ++valueVec[2 * valueVec.size() / 3];
84 ++valueVec[3 * valueVec.size() / 4];
85
86 gApplicationCache[keyVec] = valueVec;
87
88 gLastCacheOpResult = CacheOpResult::SetSuccess;
89 }
90
GetBlob(const void * key,EGLsizeiANDROID keySize,void * value,EGLsizeiANDROID valueSize)91 EGLsizeiANDROID GetBlob(const void *key,
92 EGLsizeiANDROID keySize,
93 void *value,
94 EGLsizeiANDROID valueSize)
95 {
96 std::vector<uint8_t> keyVec(keySize);
97 memcpy(keyVec.data(), key, keySize);
98
99 auto entry = gApplicationCache.find(keyVec);
100 if (entry == gApplicationCache.end())
101 {
102 // A compile+link operation can generate multiple queries to the cache; one per shader and
103 // one for link. For the purposes of the test, make sure that any of these hitting the
104 // cache is considered a success, particularly because it's valid for the pipeline cache
105 // entry not to exist in the cache.
106 if (gLastCacheOpResult != CacheOpResult::GetSuccess)
107 {
108 gLastCacheOpResult = CacheOpResult::GetNotFound;
109 }
110 return 0;
111 }
112
113 if (entry->second.size() <= static_cast<size_t>(valueSize))
114 {
115 memcpy(value, entry->second.data(), entry->second.size());
116 gLastCacheOpResult = CacheOpResult::GetSuccess;
117 }
118 else
119 {
120 gLastCacheOpResult = CacheOpResult::GetMemoryTooSmall;
121 }
122
123 return entry->second.size();
124 }
125
WaitProgramBinaryReady(GLuint program)126 void WaitProgramBinaryReady(GLuint program)
127 {
128 // Using GL_ANGLE_program_binary_readiness_query, wait for post-link tasks to finish.
129 // Otherwise, the program binary may not yet be cached. Only needed when a |set| operation is
130 // expected.
131 if (!IsGLExtensionEnabled("GL_ANGLE_program_binary_readiness_query"))
132 {
133 return;
134 }
135
136 GLint ready = false;
137 while (!ready)
138 {
139 glGetProgramiv(program, GL_PROGRAM_BINARY_READY_ANGLE, &ready);
140 angle::Sleep(0);
141 }
142 }
143 } // anonymous namespace
144
145 class EGLBlobCacheTest : public ANGLETest<>
146 {
147 protected:
EGLBlobCacheTest()148 EGLBlobCacheTest() : mHasBlobCache(false)
149 {
150 // Force disply caching off. Blob cache functions require it.
151 forceNewDisplay();
152 }
153
testSetUp()154 void testSetUp() override
155 {
156 EGLDisplay display = getEGLWindow()->getDisplay();
157 mHasBlobCache = IsEGLDisplayExtensionEnabled(display, kEGLExtName);
158 }
159
testTearDown()160 void testTearDown() override { gApplicationCache.clear(); }
161
programBinaryAvailable()162 bool programBinaryAvailable() { return IsGLExtensionEnabled("GL_OES_get_program_binary"); }
163
164 bool mHasBlobCache;
165 };
166
167 // Makes sure the extension exists and works
TEST_P(EGLBlobCacheTest,Functional)168 TEST_P(EGLBlobCacheTest, Functional)
169 {
170 ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::CacheCompiledShader));
171 ANGLE_SKIP_TEST_IF(getEGLWindow()->isFeatureEnabled(Feature::DisableProgramCaching));
172
173 EGLDisplay display = getEGLWindow()->getDisplay();
174
175 EXPECT_TRUE(mHasBlobCache);
176 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
177 ASSERT_EGL_SUCCESS();
178
179 constexpr char kVertexShaderSrc[] = R"(attribute vec4 aTest;
180 attribute vec2 aPosition;
181 varying vec4 vTest;
182 void main()
183 {
184 vTest = aTest;
185 gl_Position = vec4(aPosition, 0.0, 1.0);
186 gl_PointSize = 1.0;
187 })";
188
189 constexpr char kFragmentShaderSrc[] = R"(precision mediump float;
190 varying vec4 vTest;
191 void main()
192 {
193 gl_FragColor = vTest;
194 })";
195
196 constexpr char kVertexShaderSrc2[] = R"(attribute vec4 aTest;
197 attribute vec2 aPosition;
198 varying vec4 vTest;
199 void main()
200 {
201 vTest = aTest;
202 gl_Position = vec4(aPosition, 1.0, 1.0);
203 gl_PointSize = 1.0;
204 })";
205
206 constexpr char kFragmentShaderSrc2[] = R"(precision mediump float;
207 varying vec4 vTest;
208 void main()
209 {
210 gl_FragColor = vTest - vec4(0.0, 1.0, 0.0, 0.0);
211 })";
212
213 // Compile a shader so it puts something in the cache. Note that with Vulkan, some optional
214 // link subtasks may run beyond link, and so the caching is delayed. An explicit wait on these
215 // tasks is done for this reason.
216 if (programBinaryAvailable())
217 {
218 ANGLE_GL_PROGRAM(program, kVertexShaderSrc, kFragmentShaderSrc);
219 WaitProgramBinaryReady(program);
220 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
221 gLastCacheOpResult = CacheOpResult::ValueNotSet;
222
223 // Compile the same shader again, so it would try to retrieve it from the cache
224 program.makeRaster(kVertexShaderSrc, kFragmentShaderSrc);
225 ASSERT_TRUE(program.valid());
226 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
227 gLastCacheOpResult = CacheOpResult::ValueNotSet;
228
229 // Compile another shader, which should create a new entry
230 program.makeRaster(kVertexShaderSrc2, kFragmentShaderSrc2);
231 ASSERT_TRUE(program.valid());
232 WaitProgramBinaryReady(program);
233 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
234 gLastCacheOpResult = CacheOpResult::ValueNotSet;
235
236 // Compile the first shader again, which should still reside in the cache
237 program.makeRaster(kVertexShaderSrc, kFragmentShaderSrc);
238 ASSERT_TRUE(program.valid());
239 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
240 gLastCacheOpResult = CacheOpResult::ValueNotSet;
241
242 // Make sure deleting the program doesn't result in a binary save. Regression test for a
243 // bug where the binary was re-cached after being loaded.
244 glUseProgram(0);
245 program.reset();
246
247 EXPECT_EQ(CacheOpResult::ValueNotSet, gLastCacheOpResult);
248 }
249 }
250
251 // Makes sure the caching is always done without an explicit wait for post-link events (if any)
TEST_P(EGLBlobCacheTest,FunctionalWithoutWait)252 TEST_P(EGLBlobCacheTest, FunctionalWithoutWait)
253 {
254 ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::CacheCompiledShader));
255 ANGLE_SKIP_TEST_IF(getEGLWindow()->isFeatureEnabled(Feature::DisableProgramCaching));
256
257 EGLDisplay display = getEGLWindow()->getDisplay();
258
259 EXPECT_TRUE(mHasBlobCache);
260 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
261 ASSERT_EGL_SUCCESS();
262
263 constexpr char kVertexShaderSrc[] = R"(attribute vec4 aTest;
264 attribute vec2 aPosition;
265 varying vec4 vTest;
266 varying vec4 vTest2;
267 void main()
268 {
269 vTest = aTest;
270 vTest2 = aTest;
271 gl_Position = vec4(aPosition, 1.0, 1.0);
272 gl_PointSize = 1.0;
273 })";
274
275 constexpr char kFragmentShaderSrc[] = R"(precision mediump float;
276 varying vec4 vTest;
277 varying vec4 vTest2;
278 void main()
279 {
280 gl_FragColor = vTest + vTest2 - vec4(0.0, 1.0, 0.0, 0.0);
281 })";
282
283 if (programBinaryAvailable())
284 {
285 // Make the conditions ideal for Vulkan's warm up task to match the draw call.
286 constexpr uint32_t kSize = 1;
287 GLTexture color;
288 glBindTexture(GL_TEXTURE_2D, color);
289 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
290 nullptr);
291
292 GLFramebuffer fbo;
293 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
294 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
295 ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
296
297 ANGLE_GL_PROGRAM(program, kVertexShaderSrc, kFragmentShaderSrc);
298
299 // First, draw with the program. In the Vulkan backend, this can lead to a wait on the warm
300 // up task since the description matches the one needed for the draw.
301 glUseProgram(program);
302 glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);
303
304 // Delete the program to make sure caching the binary can no longer be delayed.
305 glUseProgram(0);
306 program.reset();
307
308 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
309 gLastCacheOpResult = CacheOpResult::ValueNotSet;
310 }
311 }
312
313 // Tests error conditions of the APIs.
TEST_P(EGLBlobCacheTest,NegativeAPI)314 TEST_P(EGLBlobCacheTest, NegativeAPI)
315 {
316 ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::CacheCompiledShader));
317 ANGLE_SKIP_TEST_IF(getEGLWindow()->isFeatureEnabled(Feature::DisableProgramCaching));
318
319 EXPECT_TRUE(mHasBlobCache);
320
321 // Test bad display
322 eglSetBlobCacheFuncsANDROID(EGL_NO_DISPLAY, nullptr, nullptr);
323 EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
324
325 eglSetBlobCacheFuncsANDROID(EGL_NO_DISPLAY, SetBlob, GetBlob);
326 EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
327
328 EGLDisplay display = getEGLWindow()->getDisplay();
329
330 // Test bad arguments
331 eglSetBlobCacheFuncsANDROID(display, nullptr, nullptr);
332 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
333
334 eglSetBlobCacheFuncsANDROID(display, SetBlob, nullptr);
335 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
336
337 eglSetBlobCacheFuncsANDROID(display, nullptr, GetBlob);
338 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
339
340 // Set the arguments once and test setting them again (which should fail)
341 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
342 ASSERT_EGL_SUCCESS();
343
344 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
345 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
346
347 // Try again with bad parameters
348 eglSetBlobCacheFuncsANDROID(EGL_NO_DISPLAY, nullptr, nullptr);
349 EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
350
351 eglSetBlobCacheFuncsANDROID(display, nullptr, nullptr);
352 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
353
354 eglSetBlobCacheFuncsANDROID(display, SetBlob, nullptr);
355 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
356
357 eglSetBlobCacheFuncsANDROID(display, nullptr, GetBlob);
358 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
359 }
360
361 // Regression test for including the fragment output locatins in the program key.
362 // http://anglebug.com/42263144
TEST_P(EGLBlobCacheTest,FragmentOutputLocationKey)363 TEST_P(EGLBlobCacheTest, FragmentOutputLocationKey)
364 {
365 ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::CacheCompiledShader));
366 ANGLE_SKIP_TEST_IF(getEGLWindow()->isFeatureEnabled(Feature::DisableProgramCaching));
367
368 ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended") ||
369 getClientMajorVersion() < 3);
370
371 EGLDisplay display = getEGLWindow()->getDisplay();
372
373 EXPECT_TRUE(mHasBlobCache);
374 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
375 ASSERT_EGL_SUCCESS();
376
377 // Compile a shader so it puts something in the cache
378 if (programBinaryAvailable())
379 {
380 glEnable(GL_SCISSOR_TEST);
381 glScissor(0, 0, 1, 1);
382
383 constexpr char kFragmentShaderSrc[] = R"(#version 300 es
384 #extension GL_EXT_blend_func_extended : require
385 precision mediump float;
386 uniform vec4 src;
387 uniform vec4 src1;
388 out vec4 FragData;
389 out vec4 SecondaryFragData;
390 void main() {
391 FragData = src;
392 SecondaryFragData = src1;
393 })";
394
395 constexpr char kVertexShaderSrc[] = R"(#version 300 es
396 in vec4 position;
397 void main() {
398 gl_Position = position;
399 })";
400
401 GLuint program = CompileProgram(kVertexShaderSrc, kFragmentShaderSrc, [](GLuint p) {
402 glBindFragDataLocationEXT(p, 0, "FragData[0]");
403 glBindFragDataLocationIndexedEXT(p, 0, 1, "SecondaryFragData[0]");
404 });
405 ASSERT_NE(0u, program);
406 glUseProgram(program);
407 glDrawArrays(GL_TRIANGLES, 0, 3);
408 WaitProgramBinaryReady(program);
409 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
410 gLastCacheOpResult = CacheOpResult::ValueNotSet;
411
412 // Re-link the program with different fragment output bindings
413 program = CompileProgram(kVertexShaderSrc, kFragmentShaderSrc, [](GLuint p) {
414 glBindFragDataLocationEXT(p, 0, "FragData");
415 glBindFragDataLocationIndexedEXT(p, 0, 1, "SecondaryFragData");
416 });
417 ASSERT_NE(0u, program);
418 glUseProgram(program);
419 glDrawArrays(GL_TRIANGLES, 0, 3);
420 WaitProgramBinaryReady(program);
421 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
422 gLastCacheOpResult = CacheOpResult::ValueNotSet;
423 }
424 }
425
426 // Checks that the shader cache, which is used when this extension is available, is working
427 // properly.
TEST_P(EGLBlobCacheTest,ShaderCacheFunctional)428 TEST_P(EGLBlobCacheTest, ShaderCacheFunctional)
429 {
430 ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::CacheCompiledShader));
431 ANGLE_SKIP_TEST_IF(getEGLWindow()->isFeatureEnabled(Feature::DisableProgramCaching));
432
433 ANGLE_SKIP_TEST_IF(!IsVulkan());
434
435 EGLDisplay display = getEGLWindow()->getDisplay();
436
437 EXPECT_TRUE(mHasBlobCache);
438 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
439 ASSERT_EGL_SUCCESS();
440
441 constexpr char kVertexShaderSrc[] = R"(attribute vec4 aTest;
442 attribute vec2 aPosition;
443 varying vec4 vTest;
444 void main()
445 {
446 vTest = aTest;
447 gl_Position = vec4(aPosition, 0.0, 1.0);
448 gl_PointSize = 1.0;
449 })";
450
451 constexpr char kFragmentShaderSrc[] = R"(precision mediump float;
452 varying vec4 vTest;
453 void main()
454 {
455 gl_FragColor = vTest;
456 })";
457
458 // Compile a shader so it puts something in the cache
459 GLuint shaderID = CompileShader(GL_VERTEX_SHADER, kVertexShaderSrc);
460 ASSERT_TRUE(shaderID != 0);
461 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
462 gLastCacheOpResult = CacheOpResult::ValueNotSet;
463 glDeleteShader(shaderID);
464
465 // Compile the same shader again, so it would try to retrieve it from the cache
466 shaderID = CompileShader(GL_VERTEX_SHADER, kVertexShaderSrc);
467 ASSERT_TRUE(shaderID != 0);
468 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
469 gLastCacheOpResult = CacheOpResult::ValueNotSet;
470 glDeleteShader(shaderID);
471
472 // Compile another shader, which should create a new entry
473 shaderID = CompileShader(GL_FRAGMENT_SHADER, kFragmentShaderSrc);
474 ASSERT_TRUE(shaderID != 0);
475 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
476 gLastCacheOpResult = CacheOpResult::ValueNotSet;
477 glDeleteShader(shaderID);
478
479 // Compile the first shader again, which should still reside in the cache
480 shaderID = CompileShader(GL_VERTEX_SHADER, kVertexShaderSrc);
481 ASSERT_TRUE(shaderID != 0);
482 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
483 gLastCacheOpResult = CacheOpResult::ValueNotSet;
484 glDeleteShader(shaderID);
485 }
486
487 // Tests compiling a program in multiple threads, then fetching the compiled program/shaders from
488 // the cache. We then perform a draw call and test the result to ensure nothing was corrupted.
TEST_P(EGLBlobCacheTest,ThreadSafety)489 TEST_P(EGLBlobCacheTest, ThreadSafety)
490 {
491 ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::CacheCompiledShader));
492 ANGLE_SKIP_TEST_IF(getEGLWindow()->isFeatureEnabled(Feature::DisableProgramCaching));
493
494 ANGLE_SKIP_TEST_IF(!IsVulkan());
495
496 EGLDisplay display = getEGLWindow()->getDisplay();
497
498 EXPECT_TRUE(mHasBlobCache);
499 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
500 ASSERT_EGL_SUCCESS();
501
502 auto threadFunc = [&](int threadID, EGLDisplay dpy, EGLSurface surface, EGLContext context) {
503 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
504
505 ANGLE_GL_PROGRAM(unusedProgramTemp1, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
506
507 // Insert a new entry into the cache unique to this thread.
508 std::stringstream ss;
509 ss << essl1_shaders::vs::Simple() << "//" << threadID;
510 std::string newEntryVSSource = ss.str().c_str();
511 ANGLE_GL_PROGRAM(unusedProgramTemp2, newEntryVSSource.c_str(), essl1_shaders::fs::Red());
512
513 // Clean up
514 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
515 };
516
517 constexpr int kNumThreads = 32;
518
519 std::vector<LockStepThreadFunc> threadFuncs(kNumThreads);
520 for (int i = 0; i < kNumThreads; ++i)
521 {
522 threadFuncs[i] = [=](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
523 return threadFunc(i, dpy, surface, context);
524 };
525 }
526
527 gLastCacheOpResult = CacheOpResult::ValueNotSet;
528
529 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
530
531 EXPECT_GL_NO_ERROR();
532
533 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
534 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
535
536 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
537
538 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
539 }
540
541 // Makes sure ANGLE recovers from corrupted cache.
TEST_P(EGLBlobCacheTest,CacheCorruption)542 TEST_P(EGLBlobCacheTest, CacheCorruption)
543 {
544 ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::CacheCompiledShader));
545 ANGLE_SKIP_TEST_IF(getEGLWindow()->isFeatureEnabled(Feature::DisableProgramCaching));
546
547 EGLDisplay display = getEGLWindow()->getDisplay();
548
549 EXPECT_TRUE(mHasBlobCache);
550 eglSetBlobCacheFuncsANDROID(display, SetCorruptedBlob, GetBlob);
551 ASSERT_EGL_SUCCESS();
552
553 ANGLE_SKIP_TEST_IF(!programBinaryAvailable());
554
555 // Compile the program once and draw with it
556 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
557 glUseProgram(program);
558
559 const GLint colorUniformLocation =
560 glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
561 ASSERT_NE(colorUniformLocation, -1);
562
563 glUniform4f(colorUniformLocation, 1, 0, 0, 1);
564 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
565 EXPECT_GL_NO_ERROR();
566 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
567
568 WaitProgramBinaryReady(program);
569 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
570 gLastCacheOpResult = CacheOpResult::ValueNotSet;
571
572 // Compile/link the same program again, so it would try to retrieve it from the cache. GetBlob
573 // should return success, but because the cache is corrupt, ANGLE should redo the compile/link
574 // and set the blob again.
575 program.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
576 ASSERT_TRUE(program.valid());
577 glUseProgram(program);
578
579 glUniform4f(colorUniformLocation, 0, 1, 0, 1);
580 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
581 EXPECT_GL_NO_ERROR();
582 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
583
584 WaitProgramBinaryReady(program);
585 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
586 }
587
588 class EGLBlobCacheInternalRejectionTest : public EGLBlobCacheTest
589 {};
590
591 // Makes sure ANGLE recovers from internal (backend) rejection of the program blob, while everything
592 // seems fine to ANGLE.
TEST_P(EGLBlobCacheInternalRejectionTest,Functional)593 TEST_P(EGLBlobCacheInternalRejectionTest, Functional)
594 {
595 EGLDisplay display = getEGLWindow()->getDisplay();
596
597 EXPECT_TRUE(mHasBlobCache);
598 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
599 ASSERT_EGL_SUCCESS();
600
601 ANGLE_SKIP_TEST_IF(!programBinaryAvailable());
602
603 // Compile the program once and draw with it
604 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
605 glUseProgram(program);
606
607 const GLint colorUniformLocation =
608 glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
609 ASSERT_NE(colorUniformLocation, -1);
610
611 glUniform4f(colorUniformLocation, 1, 0, 0, 1);
612 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
613 EXPECT_GL_NO_ERROR();
614 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
615
616 WaitProgramBinaryReady(program);
617 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
618 gLastCacheOpResult = CacheOpResult::ValueNotSet;
619
620 // Compile/link the same program again, so it would try to retrieve it from the cache. GetBlob
621 // should return success, and ANGLE would think the program is fine. After ANGLE internal
622 // updates, the backend should reject the program binary, at which point ANGLE should redo the
623 // compile/link and set the blob again.
624 program.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
625 ASSERT_TRUE(program.valid());
626 glUseProgram(program);
627
628 glUniform4f(colorUniformLocation, 0, 1, 0, 1);
629 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
630 EXPECT_GL_NO_ERROR();
631 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
632
633 WaitProgramBinaryReady(program);
634 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
635 }
636
637 // Makes sure ANGLE recovers from internal (backend) rejection of the shader blob, while everything
638 // seems fine to ANGLE.
TEST_P(EGLBlobCacheInternalRejectionTest,ShaderCacheFunctional)639 TEST_P(EGLBlobCacheInternalRejectionTest, ShaderCacheFunctional)
640 {
641 ANGLE_SKIP_TEST_IF(!IsVulkan());
642
643 EGLDisplay display = getEGLWindow()->getDisplay();
644
645 EXPECT_TRUE(mHasBlobCache);
646 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
647 ASSERT_EGL_SUCCESS();
648
649 // Compile a shader so it puts something in the cache
650 GLuint shaderID = CompileShader(GL_VERTEX_SHADER, essl1_shaders::vs::Simple());
651 ASSERT_TRUE(shaderID != 0);
652 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
653 gLastCacheOpResult = CacheOpResult::ValueNotSet;
654 glDeleteShader(shaderID);
655
656 // Compile another shader, which should create a new entry
657 shaderID = CompileShader(GL_FRAGMENT_SHADER, essl1_shaders::fs::UniformColor());
658 ASSERT_TRUE(shaderID != 0);
659 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
660 gLastCacheOpResult = CacheOpResult::ValueNotSet;
661 glDeleteShader(shaderID);
662
663 // Compile the first shader again, which should still reside in the cache, but is corrupted.
664 // The cached entry should be discarded and compilation performed again (which sets another
665 // entry in the cache).
666 shaderID = CompileShader(GL_VERTEX_SHADER, essl1_shaders::vs::Simple());
667 ASSERT_TRUE(shaderID != 0);
668 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
669 gLastCacheOpResult = CacheOpResult::ValueNotSet;
670 glDeleteShader(shaderID);
671 }
672
673 ANGLE_INSTANTIATE_TEST(EGLBlobCacheTest,
674 ES2_D3D9(),
675 ES2_D3D11(),
676 ES3_D3D11(),
677 ES2_OPENGL(),
678 ES3_OPENGL(),
679 ES3_OPENGLES(),
680 ES2_OPENGLES(),
681 ES2_METAL(),
682 ES3_METAL(),
683 // Note: For the Vulkan backend, disable reads and writes for the global
684 // pipeline cache, so it does not interfere with the test's expectations of
685 // when the cache should and shouldn't be hit.
686 ES2_VULKAN()
687 .enable(Feature::DisablePipelineCacheLoadForTesting)
688 .disable(Feature::SyncMonolithicPipelinesToBlobCache),
689 ES3_VULKAN_SWIFTSHADER()
690 .enable(Feature::DisablePipelineCacheLoadForTesting)
691 .disable(Feature::SyncMonolithicPipelinesToBlobCache),
692 ES3_VULKAN()
693 .enable(Feature::AsyncCommandQueue)
694 .enable(Feature::DisablePipelineCacheLoadForTesting)
695 .disable(Feature::SyncMonolithicPipelinesToBlobCache),
696 ES2_VULKAN_SWIFTSHADER()
697 .enable(Feature::AsyncCommandQueue)
698 .enable(Feature::DisablePipelineCacheLoadForTesting)
699 .disable(Feature::SyncMonolithicPipelinesToBlobCache),
700 ES2_VULKAN_SWIFTSHADER()
701 .enable(Feature::EnableParallelCompileAndLink)
702 .enable(Feature::DisablePipelineCacheLoadForTesting)
703 .disable(Feature::SyncMonolithicPipelinesToBlobCache),
704 ES3_VULKAN()
705 .enable(Feature::EnableParallelCompileAndLink)
706 .enable(Feature::DisablePipelineCacheLoadForTesting)
707 .disable(Feature::SyncMonolithicPipelinesToBlobCache),
708 ES2_VULKAN_SWIFTSHADER()
709 .enable(Feature::EnableParallelCompileAndLink)
710 .enable(Feature::AsyncCommandQueue)
711 .enable(Feature::DisablePipelineCacheLoadForTesting)
712 .disable(Feature::SyncMonolithicPipelinesToBlobCache));
713
714 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLBlobCacheInternalRejectionTest);
715 ANGLE_INSTANTIATE_TEST(EGLBlobCacheInternalRejectionTest,
716 ES2_OPENGL().enable(Feature::CorruptProgramBinaryForTesting),
717 ES2_OPENGLES().enable(Feature::CorruptProgramBinaryForTesting));
718