• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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