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
22 using namespace angle;
23
24 constexpr char kEGLExtName[] = "EGL_ANDROID_blob_cache";
25
26 enum class CacheOpResult
27 {
28 SetSuccess,
29 GetNotFound,
30 GetMemoryTooSmall,
31 GetSuccess,
32 ValueNotSet,
33 EnumCount
34 };
35
36 angle::PackedEnumMap<CacheOpResult, std::string> kCacheOpToString = {
37 {CacheOpResult::SetSuccess, "SetSuccess"},
38 {CacheOpResult::GetNotFound, "GetNotFound"},
39 {CacheOpResult::GetMemoryTooSmall, "GetMemoryTooSmall"},
40 {CacheOpResult::GetSuccess, "GetSuccess"},
41 {CacheOpResult::ValueNotSet, "ValueNotSet"},
42 };
43
operator <<(std::ostream & os,CacheOpResult result)44 std::ostream &operator<<(std::ostream &os, CacheOpResult result)
45 {
46 return os << kCacheOpToString[result];
47 }
48
49 namespace
50 {
51 std::map<std::vector<uint8_t>, std::vector<uint8_t>> gApplicationCache;
52 CacheOpResult gLastCacheOpResult = CacheOpResult::ValueNotSet;
53
SetBlob(const void * key,EGLsizeiANDROID keySize,const void * value,EGLsizeiANDROID valueSize)54 void SetBlob(const void *key, EGLsizeiANDROID keySize, const void *value, EGLsizeiANDROID valueSize)
55 {
56 std::vector<uint8_t> keyVec(keySize);
57 memcpy(keyVec.data(), key, keySize);
58
59 std::vector<uint8_t> valueVec(valueSize);
60 memcpy(valueVec.data(), value, valueSize);
61
62 gApplicationCache[keyVec] = valueVec;
63
64 gLastCacheOpResult = CacheOpResult::SetSuccess;
65 }
66
GetBlob(const void * key,EGLsizeiANDROID keySize,void * value,EGLsizeiANDROID valueSize)67 EGLsizeiANDROID GetBlob(const void *key,
68 EGLsizeiANDROID keySize,
69 void *value,
70 EGLsizeiANDROID valueSize)
71 {
72 std::vector<uint8_t> keyVec(keySize);
73 memcpy(keyVec.data(), key, keySize);
74
75 auto entry = gApplicationCache.find(keyVec);
76 if (entry == gApplicationCache.end())
77 {
78 // A compile+link operation can generate multiple queries to the cache; one per shader, one
79 // for link, and in the Vulkan backend, potentially also one for the pipeline cache. For
80 // the purposes of the test, make sure that any of these hitting the cache is considered a
81 // success, particularly because it's valid for the pipeline cache entry not to exist in the
82 // cache.
83 if (gLastCacheOpResult != CacheOpResult::GetSuccess)
84 {
85 gLastCacheOpResult = CacheOpResult::GetNotFound;
86 }
87 return 0;
88 }
89
90 if (entry->second.size() <= static_cast<size_t>(valueSize))
91 {
92 memcpy(value, entry->second.data(), entry->second.size());
93 gLastCacheOpResult = CacheOpResult::GetSuccess;
94 }
95 else
96 {
97 gLastCacheOpResult = CacheOpResult::GetMemoryTooSmall;
98 }
99
100 return entry->second.size();
101 }
102 } // anonymous namespace
103
104 class EGLBlobCacheTest : public ANGLETest<>
105 {
106 protected:
EGLBlobCacheTest()107 EGLBlobCacheTest() : mHasBlobCache(false)
108 {
109 // Force disply caching off. Blob cache functions require it.
110 forceNewDisplay();
111 }
112
testSetUp()113 void testSetUp() override
114 {
115 EGLDisplay display = getEGLWindow()->getDisplay();
116 mHasBlobCache = IsEGLDisplayExtensionEnabled(display, kEGLExtName);
117 }
118
testTearDown()119 void testTearDown() override { gApplicationCache.clear(); }
120
programBinaryAvailable()121 bool programBinaryAvailable() { return IsGLExtensionEnabled("GL_OES_get_program_binary"); }
122
123 bool mHasBlobCache;
124 };
125
126 // Makes sure the extension exists and works
TEST_P(EGLBlobCacheTest,Functional)127 TEST_P(EGLBlobCacheTest, Functional)
128 {
129 EGLDisplay display = getEGLWindow()->getDisplay();
130
131 EXPECT_TRUE(mHasBlobCache);
132 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
133 ASSERT_EGL_SUCCESS();
134
135 constexpr char kVertexShaderSrc[] = R"(attribute vec4 aTest;
136 attribute vec2 aPosition;
137 varying vec4 vTest;
138 void main()
139 {
140 vTest = aTest;
141 gl_Position = vec4(aPosition, 0.0, 1.0);
142 gl_PointSize = 1.0;
143 })";
144
145 constexpr char kFragmentShaderSrc[] = R"(precision mediump float;
146 varying vec4 vTest;
147 void main()
148 {
149 gl_FragColor = vTest;
150 })";
151
152 constexpr char kVertexShaderSrc2[] = R"(attribute vec4 aTest;
153 attribute vec2 aPosition;
154 varying vec4 vTest;
155 void main()
156 {
157 vTest = aTest;
158 gl_Position = vec4(aPosition, 1.0, 1.0);
159 gl_PointSize = 1.0;
160 })";
161
162 constexpr char kFragmentShaderSrc2[] = R"(precision mediump float;
163 varying vec4 vTest;
164 void main()
165 {
166 gl_FragColor = vTest - vec4(0.0, 1.0, 0.0, 0.0);
167 })";
168
169 // Compile a shader so it puts something in the cache
170 if (programBinaryAvailable())
171 {
172 ANGLE_GL_PROGRAM(program, kVertexShaderSrc, kFragmentShaderSrc);
173 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
174 gLastCacheOpResult = CacheOpResult::ValueNotSet;
175
176 // Compile the same shader again, so it would try to retrieve it from the cache
177 program.makeRaster(kVertexShaderSrc, kFragmentShaderSrc);
178 ASSERT_TRUE(program.valid());
179 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
180 gLastCacheOpResult = CacheOpResult::ValueNotSet;
181
182 // Compile another shader, which should create a new entry
183 program.makeRaster(kVertexShaderSrc2, kFragmentShaderSrc2);
184 ASSERT_TRUE(program.valid());
185 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
186 gLastCacheOpResult = CacheOpResult::ValueNotSet;
187
188 // Compile the first shader again, which should still reside in the cache
189 program.makeRaster(kVertexShaderSrc, kFragmentShaderSrc);
190 ASSERT_TRUE(program.valid());
191 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
192 gLastCacheOpResult = CacheOpResult::ValueNotSet;
193 }
194 }
195
196 // Tests error conditions of the APIs.
TEST_P(EGLBlobCacheTest,NegativeAPI)197 TEST_P(EGLBlobCacheTest, NegativeAPI)
198 {
199 EXPECT_TRUE(mHasBlobCache);
200
201 // Test bad display
202 eglSetBlobCacheFuncsANDROID(EGL_NO_DISPLAY, nullptr, nullptr);
203 EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
204
205 eglSetBlobCacheFuncsANDROID(EGL_NO_DISPLAY, SetBlob, GetBlob);
206 EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
207
208 EGLDisplay display = getEGLWindow()->getDisplay();
209
210 // Test bad arguments
211 eglSetBlobCacheFuncsANDROID(display, nullptr, nullptr);
212 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
213
214 eglSetBlobCacheFuncsANDROID(display, SetBlob, nullptr);
215 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
216
217 eglSetBlobCacheFuncsANDROID(display, nullptr, GetBlob);
218 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
219
220 // Set the arguments once and test setting them again (which should fail)
221 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
222 ASSERT_EGL_SUCCESS();
223
224 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
225 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
226
227 // Try again with bad parameters
228 eglSetBlobCacheFuncsANDROID(EGL_NO_DISPLAY, nullptr, nullptr);
229 EXPECT_EGL_ERROR(EGL_BAD_DISPLAY);
230
231 eglSetBlobCacheFuncsANDROID(display, nullptr, nullptr);
232 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
233
234 eglSetBlobCacheFuncsANDROID(display, SetBlob, nullptr);
235 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
236
237 eglSetBlobCacheFuncsANDROID(display, nullptr, GetBlob);
238 EXPECT_EGL_ERROR(EGL_BAD_PARAMETER);
239 }
240
241 // Regression test for including the fragment output locatins in the program key.
242 // http://anglebug.com/4535
TEST_P(EGLBlobCacheTest,FragmentOutputLocationKey)243 TEST_P(EGLBlobCacheTest, FragmentOutputLocationKey)
244 {
245 ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_blend_func_extended") ||
246 getClientMajorVersion() < 3);
247
248 EGLDisplay display = getEGLWindow()->getDisplay();
249
250 EXPECT_TRUE(mHasBlobCache);
251 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
252 ASSERT_EGL_SUCCESS();
253
254 // Compile a shader so it puts something in the cache
255 if (programBinaryAvailable())
256 {
257 constexpr char kFragmentShaderSrc[] = R"(#version 300 es
258 #extension GL_EXT_blend_func_extended : require
259 precision mediump float;
260 uniform vec4 src;
261 uniform vec4 src1;
262 out vec4 FragData;
263 out vec4 SecondaryFragData;
264 void main() {
265 FragData = src;
266 SecondaryFragData = src1;
267 })";
268
269 constexpr char kVertexShaderSrc[] = R"(#version 300 es
270 in vec4 position;
271 void main() {
272 gl_Position = position;
273 })";
274
275 GLuint program = CompileProgram(kVertexShaderSrc, kFragmentShaderSrc, [](GLuint p) {
276 glBindFragDataLocationEXT(p, 0, "FragData[0]");
277 glBindFragDataLocationIndexedEXT(p, 0, 1, "SecondaryFragData[0]");
278 });
279 ASSERT_NE(0u, program);
280 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
281 gLastCacheOpResult = CacheOpResult::ValueNotSet;
282
283 // Re-link the program with different fragment output bindings
284 program = CompileProgram(kVertexShaderSrc, kFragmentShaderSrc, [](GLuint p) {
285 glBindFragDataLocationEXT(p, 0, "FragData");
286 glBindFragDataLocationIndexedEXT(p, 0, 1, "SecondaryFragData");
287 });
288 ASSERT_NE(0u, program);
289 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
290 gLastCacheOpResult = CacheOpResult::ValueNotSet;
291 }
292 }
293
294 // Checks that the shader cache, which is used when this extension is available, is working
295 // properly.
TEST_P(EGLBlobCacheTest,ShaderCacheFunctional)296 TEST_P(EGLBlobCacheTest, ShaderCacheFunctional)
297 {
298 ANGLE_SKIP_TEST_IF(!IsVulkan());
299
300 EGLDisplay display = getEGLWindow()->getDisplay();
301
302 EXPECT_TRUE(mHasBlobCache);
303 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
304 ASSERT_EGL_SUCCESS();
305
306 constexpr char kVertexShaderSrc[] = R"(attribute vec4 aTest;
307 attribute vec2 aPosition;
308 varying vec4 vTest;
309 void main()
310 {
311 vTest = aTest;
312 gl_Position = vec4(aPosition, 0.0, 1.0);
313 gl_PointSize = 1.0;
314 })";
315
316 constexpr char kFragmentShaderSrc[] = R"(precision mediump float;
317 varying vec4 vTest;
318 void main()
319 {
320 gl_FragColor = vTest;
321 })";
322
323 // Compile a shader so it puts something in the cache
324 GLuint shaderID = CompileShader(GL_VERTEX_SHADER, kVertexShaderSrc);
325 ASSERT_TRUE(shaderID != 0);
326 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
327 gLastCacheOpResult = CacheOpResult::ValueNotSet;
328 glDeleteShader(shaderID);
329
330 // Compile the same shader again, so it would try to retrieve it from the cache
331 shaderID = CompileShader(GL_VERTEX_SHADER, kVertexShaderSrc);
332 ASSERT_TRUE(shaderID != 0);
333 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
334 gLastCacheOpResult = CacheOpResult::ValueNotSet;
335 glDeleteShader(shaderID);
336
337 // Compile another shader, which should create a new entry
338 shaderID = CompileShader(GL_FRAGMENT_SHADER, kFragmentShaderSrc);
339 ASSERT_TRUE(shaderID != 0);
340 EXPECT_EQ(CacheOpResult::SetSuccess, gLastCacheOpResult);
341 gLastCacheOpResult = CacheOpResult::ValueNotSet;
342 glDeleteShader(shaderID);
343
344 // Compile the first shader again, which should still reside in the cache
345 shaderID = CompileShader(GL_VERTEX_SHADER, kVertexShaderSrc);
346 ASSERT_TRUE(shaderID != 0);
347 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
348 gLastCacheOpResult = CacheOpResult::ValueNotSet;
349 glDeleteShader(shaderID);
350 }
351
352 // Tests compiling a program in multiple threads, then fetching the compiled program/shaders from
353 // the cache. We then perform a draw call and test the result to ensure nothing was corrupted.
TEST_P(EGLBlobCacheTest,ThreadSafety)354 TEST_P(EGLBlobCacheTest, ThreadSafety)
355 {
356 ANGLE_SKIP_TEST_IF(!IsVulkan());
357
358 EGLDisplay display = getEGLWindow()->getDisplay();
359
360 EXPECT_TRUE(mHasBlobCache);
361 eglSetBlobCacheFuncsANDROID(display, SetBlob, GetBlob);
362 ASSERT_EGL_SUCCESS();
363
364 auto threadFunc = [&](int threadID, EGLDisplay dpy, EGLSurface surface, EGLContext context) {
365 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
366
367 ANGLE_GL_PROGRAM(unusedProgramTemp1, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
368
369 // Insert a new entry into the cache unique to this thread.
370 std::stringstream ss;
371 ss << essl1_shaders::vs::Simple() << "//" << threadID;
372 std::string newEntryVSSource = ss.str().c_str();
373 ANGLE_GL_PROGRAM(unusedProgramTemp2, newEntryVSSource.c_str(), essl1_shaders::fs::Red());
374
375 // Clean up
376 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
377 };
378
379 constexpr int kNumThreads = 32;
380
381 std::vector<LockStepThreadFunc> threadFuncs(kNumThreads);
382 for (int i = 0; i < kNumThreads; ++i)
383 {
384 threadFuncs[i] = [=](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
385 return threadFunc(i, dpy, surface, context);
386 };
387 }
388
389 gLastCacheOpResult = CacheOpResult::ValueNotSet;
390
391 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
392
393 EXPECT_GL_NO_ERROR();
394
395 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
396 EXPECT_EQ(CacheOpResult::GetSuccess, gLastCacheOpResult);
397
398 drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
399
400 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
401 }
402
403 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(EGLBlobCacheTest);
404