1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <GrDirectContext.h>
18 #include <Properties.h>
19 #include <SkData.h>
20 #include <SkRefCnt.h>
21 #include <cutils/properties.h>
22 #include <dirent.h>
23 #include <errno.h>
24 #include <gtest/gtest.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <sys/types.h>
28 #include <utils/Log.h>
29
30 #include <cstdint>
31
32 #include "FileBlobCache.h"
33 #include "pipeline/skia/ShaderCache.h"
34 #include "tests/common/TestUtils.h"
35
36 using namespace android::uirenderer::skiapipeline;
37
38 namespace android {
39 namespace uirenderer {
40 namespace skiapipeline {
41
42 class ShaderCacheTestUtils {
43 public:
44 /**
45 * Hack to reset all member variables of the given cache to their default / initial values.
46 *
47 * WARNING: this must be kept up to date manually, since ShaderCache's parent disables just
48 * reassigning a new instance.
49 */
reinitializeAllFields(ShaderCache & cache)50 static void reinitializeAllFields(ShaderCache& cache) {
51 ShaderCache newCache = ShaderCache();
52 std::lock_guard lock(cache.mMutex), newLock(newCache.mMutex);
53 // By order of declaration
54 cache.mInitialized = newCache.mInitialized;
55 cache.mBlobCache.reset(nullptr);
56 cache.mFilename = newCache.mFilename;
57 cache.mIDHash.clear();
58 cache.mSavePending = newCache.mSavePending;
59 cache.mObservedBlobValueSize = newCache.mObservedBlobValueSize;
60 cache.mDeferredSaveDelayMs = newCache.mDeferredSaveDelayMs;
61 cache.mTryToStorePipelineCache = newCache.mTryToStorePipelineCache;
62 cache.mInStoreVkPipelineInProgress = newCache.mInStoreVkPipelineInProgress;
63 cache.mNewPipelineCacheSize = newCache.mNewPipelineCacheSize;
64 cache.mOldPipelineCacheSize = newCache.mOldPipelineCacheSize;
65 cache.mCacheDirty = newCache.mCacheDirty;
66 cache.mNumShadersCachedInRam = newCache.mNumShadersCachedInRam;
67 }
68
69 /**
70 * "setSaveDelayMs" sets the time in milliseconds to wait before saving newly inserted cache
71 * entries. If set to 0, then deferred save is disabled, and "saveToDiskLocked" must be called
72 * manually, as seen in the "terminate" testing helper function.
73 */
setSaveDelayMs(ShaderCache & cache,unsigned int saveDelayMs)74 static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) {
75 std::lock_guard lock(cache.mMutex);
76 cache.mDeferredSaveDelayMs = saveDelayMs;
77 }
78
79 /**
80 * "terminate" optionally stores the BlobCache on disk and release all in-memory cache.
81 * Next call to "initShaderDiskCache" will load again the in-memory cache from disk.
82 */
terminate(ShaderCache & cache,bool saveContent)83 static void terminate(ShaderCache& cache, bool saveContent) {
84 std::lock_guard lock(cache.mMutex);
85 if (saveContent) {
86 cache.saveToDiskLocked();
87 }
88 cache.mBlobCache = NULL;
89 }
90
91 /**
92 *
93 */
94 template <typename T>
validateCache(ShaderCache & cache,std::vector<T> hash)95 static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
96 std::lock_guard lock(cache.mMutex);
97 return cache.validateCache(hash.data(), hash.size() * sizeof(T));
98 }
99
100 /**
101 * Waits until cache::mSavePending is false, checking every 0.1 ms *while the mutex is free*.
102 *
103 * Fails if there was no save pending, or if the cache was already being written to disk, or if
104 * timeoutMs is exceeded.
105 *
106 * Note: timeoutMs only guards against mSavePending getting stuck like in b/268205519, and
107 * cannot protect against mutex-based deadlock. Reaching timeoutMs implies something is broken,
108 * so setting it to a sufficiently large value will not delay execution in the happy state.
109 */
waitForPendingSave(ShaderCache & cache,const int timeoutMs=50)110 static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) {
111 {
112 std::lock_guard lock(cache.mMutex);
113 ASSERT_TRUE(cache.mSavePending);
114 }
115 bool saving = true;
116 float elapsedMilliseconds = 0;
117 while (saving) {
118 if (elapsedMilliseconds >= timeoutMs) {
119 FAIL() << "Timed out after waiting " << timeoutMs << " ms for a pending save";
120 }
121 // This small (0.1 ms) delay is to avoid working too much while waiting for
122 // deferredSaveThread to take the mutex and start the disk write.
123 const int delayMicroseconds = 100;
124 usleep(delayMicroseconds);
125 elapsedMilliseconds += (float)delayMicroseconds / 1000;
126
127 std::lock_guard lock(cache.mMutex);
128 saving = cache.mSavePending;
129 }
130 }
131 };
132
133 } /* namespace skiapipeline */
134 } /* namespace uirenderer */
135 } /* namespace android */
136
137 namespace {
138
getExternalStorageFolder()139 std::string getExternalStorageFolder() {
140 return getenv("EXTERNAL_STORAGE");
141 }
142
folderExist(const std::string & folderName)143 bool folderExist(const std::string& folderName) {
144 DIR* dir = opendir(folderName.c_str());
145 if (dir) {
146 closedir(dir);
147 return true;
148 }
149 return false;
150 }
151
152 /**
153 * Attempts to delete the given file, and asserts that either:
154 * 1. Deletion was successful, OR
155 * 2. The file did not exist.
156 *
157 * Tip: wrap calls to this in ASSERT_NO_FATAL_FAILURE(x) if a test should exit early if this fails.
158 */
deleteFileAssertSuccess(const std::string & filePath)159 void deleteFileAssertSuccess(const std::string& filePath) {
160 int deleteResult = remove(filePath.c_str());
161 ASSERT_TRUE(0 == deleteResult || ENOENT == errno);
162 }
163
checkShader(const sk_sp<SkData> & shader1,const sk_sp<SkData> & shader2)164 inline bool checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) {
165 return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() &&
166 0 == memcmp(shader1->data(), shader2->data(), shader1->size());
167 }
168
checkShader(const sk_sp<SkData> & shader,const char * program)169 inline bool checkShader(const sk_sp<SkData>& shader, const char* program) {
170 sk_sp<SkData> shader2 = SkData::MakeWithCString(program);
171 return checkShader(shader, shader2);
172 }
173
checkShader(const sk_sp<SkData> & shader,const std::string & program)174 inline bool checkShader(const sk_sp<SkData>& shader, const std::string& program) {
175 return checkShader(shader, program.c_str());
176 }
177
178 template <typename T>
checkShader(const sk_sp<SkData> & shader,std::vector<T> & program)179 bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) {
180 sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T));
181 return checkShader(shader, shader2);
182 }
183
setShader(sk_sp<SkData> & shader,const char * program)184 void setShader(sk_sp<SkData>& shader, const char* program) {
185 shader = SkData::MakeWithCString(program);
186 }
187
setShader(sk_sp<SkData> & shader,const std::string & program)188 void setShader(sk_sp<SkData>& shader, const std::string& program) {
189 setShader(shader, program.c_str());
190 }
191
192 template <typename T>
setShader(sk_sp<SkData> & shader,std::vector<T> & buffer)193 void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) {
194 shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T));
195 }
196
197 template <typename T>
genRandomData(std::vector<T> & buffer)198 void genRandomData(std::vector<T>& buffer) {
199 for (auto& data : buffer) {
200 data = T(std::rand());
201 }
202 }
203
204 #define GrProgramDescTest(a) (*SkData::MakeWithCString(#a).get())
205
TEST(ShaderCacheTest,testWriteAndRead)206 TEST(ShaderCacheTest, testWriteAndRead) {
207 if (!folderExist(getExternalStorageFolder())) {
208 // don't run the test if external storage folder is not available
209 return;
210 }
211 std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1";
212 std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
213
214 // remove any test files from previous test run
215 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
216 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
217 std::srand(0);
218
219 // read the cache from a file that does not exist
220 ShaderCache::get().setFilename(cacheFile1.c_str());
221 ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save
222 ShaderCache::get().initShaderDiskCache();
223
224 // read a key - should not be found since the cache is empty
225 sk_sp<SkData> outVS;
226 ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>());
227
228 // write to the in-memory cache without storing on disk and verify we read the same values
229 sk_sp<SkData> inVS;
230 setShader(inVS, "sassas");
231 ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
232 setShader(inVS, "someVS");
233 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
234 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
235 ASSERT_TRUE(checkShader(outVS, "sassas"));
236 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
237 ASSERT_TRUE(checkShader(outVS, "someVS"));
238
239 // store content to disk and release in-memory cache
240 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
241
242 // change to a file that does not exist and verify load fails
243 ShaderCache::get().setFilename(cacheFile2.c_str());
244 ShaderCache::get().initShaderDiskCache();
245 ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>());
246 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
247
248 // load again content from disk from an existing file and check the data is read correctly
249 ShaderCache::get().setFilename(cacheFile1.c_str());
250 ShaderCache::get().initShaderDiskCache();
251 sk_sp<SkData> outVS2;
252 ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
253 ASSERT_TRUE(checkShader(outVS2, "someVS"));
254
255 // change data, store to disk, read back again and verify data has been changed
256 setShader(inVS, "ewData1");
257 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
258 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
259 ShaderCache::get().initShaderDiskCache();
260 ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
261 ASSERT_TRUE(checkShader(outVS2, "ewData1"));
262
263 // write and read big data chunk (50K)
264 size_t dataSize = 50 * 1024;
265 std::vector<uint8_t> dataBuffer(dataSize);
266 genRandomData(dataBuffer);
267 setShader(inVS, dataBuffer);
268 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
269 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
270 ShaderCache::get().initShaderDiskCache();
271 ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
272 ASSERT_TRUE(checkShader(outVS2, dataBuffer));
273
274 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
275 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
276 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
277 }
278
TEST(ShaderCacheTest,testCacheValidation)279 TEST(ShaderCacheTest, testCacheValidation) {
280 if (!folderExist(getExternalStorageFolder())) {
281 // don't run the test if external storage folder is not available
282 return;
283 }
284 std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1";
285 std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
286
287 // remove any test files from previous test run
288 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
289 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
290 std::srand(0);
291
292 // generate identity and read the cache from a file that does not exist
293 ShaderCache::get().setFilename(cacheFile1.c_str());
294 ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save
295 std::vector<uint8_t> identity(1024);
296 genRandomData(identity);
297 ShaderCache::get().initShaderDiskCache(
298 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
299
300 // generate random content in cache and store to disk
301 constexpr size_t numBlob(10);
302 constexpr size_t keySize(1024);
303 constexpr size_t dataSize(50 * 1024);
304
305 std::vector<std::pair<sk_sp<SkData>, sk_sp<SkData>>> blobVec(numBlob);
306 for (auto& blob : blobVec) {
307 std::vector<uint8_t> keyBuffer(keySize);
308 std::vector<uint8_t> dataBuffer(dataSize);
309 genRandomData(keyBuffer);
310 genRandomData(dataBuffer);
311
312 sk_sp<SkData> key, data;
313 setShader(key, keyBuffer);
314 setShader(data, dataBuffer);
315
316 blob = std::make_pair(key, data);
317 ShaderCache::get().store(*key.get(), *data.get(), SkString());
318 }
319 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
320
321 // change to a file that does not exist and verify validation fails
322 ShaderCache::get().setFilename(cacheFile2.c_str());
323 ShaderCache::get().initShaderDiskCache();
324 ASSERT_FALSE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity));
325 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
326
327 // restore the original file and verify validation succeeds
328 ShaderCache::get().setFilename(cacheFile1.c_str());
329 ShaderCache::get().initShaderDiskCache(
330 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
331 ASSERT_TRUE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity));
332 for (const auto& blob : blobVec) {
333 auto outVS = ShaderCache::get().load(*blob.first.get());
334 ASSERT_TRUE(checkShader(outVS, blob.second));
335 }
336
337 // generate error identity and verify load fails
338 ShaderCache::get().initShaderDiskCache(identity.data(), -1);
339 for (const auto& blob : blobVec) {
340 ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>());
341 }
342 ShaderCache::get().initShaderDiskCache(
343 nullptr, identity.size() * sizeof(decltype(identity)::value_type));
344 for (const auto& blob : blobVec) {
345 ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>());
346 }
347
348 // verify the cache validation again after load fails
349 ShaderCache::get().initShaderDiskCache(
350 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
351 ASSERT_TRUE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity));
352 for (const auto& blob : blobVec) {
353 auto outVS = ShaderCache::get().load(*blob.first.get());
354 ASSERT_TRUE(checkShader(outVS, blob.second));
355 }
356
357 // generate another identity and verify load fails
358 for (auto& data : identity) {
359 data += std::rand();
360 }
361 ShaderCache::get().initShaderDiskCache(
362 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
363 for (const auto& blob : blobVec) {
364 ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>());
365 }
366
367 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
368 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
369 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
370 }
371
372 using namespace android::uirenderer;
RENDERTHREAD_TEST(ShaderCacheTest,testOnVkFrameFlushed)373 RENDERTHREAD_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
374 if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) {
375 // RENDERTHREAD_TEST declares both SkiaVK and SkiaGL variants.
376 GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan";
377 }
378 if (!folderExist(getExternalStorageFolder())) {
379 // Don't run the test if external storage folder is not available
380 return;
381 }
382 std::string cacheFile = getExternalStorageFolder() + "/shaderCacheTest";
383 GrDirectContext* grContext = renderThread.getGrContext();
384
385 // Remove any test files from previous test run
386 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile));
387
388 // The first iteration of this loop is to save an initial VkPipelineCache data blob to disk,
389 // which sets up the second iteration for a common scenario of comparing a "new" VkPipelineCache
390 // blob passed to "store" against the same blob that's already in the persistent cache from a
391 // previous launch. "reinitializeAllFields" is critical to emulate each iteration being as close
392 // to the state of a freshly launched app as possible, as the initial values of member variables
393 // like mInStoreVkPipelineInProgress and mOldPipelineCacheSize are critical to catch issues
394 // such as b/268205519
395 for (int flushIteration = 1; flushIteration <= 2; flushIteration++) {
396 SCOPED_TRACE("Frame flush iteration " + std::to_string(flushIteration));
397 // Reset *all* in-memory data and reload the cache from disk.
398 ShaderCacheTestUtils::reinitializeAllFields(ShaderCache::get());
399 ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 10); // Delay must be > 0 to save.
400 ShaderCache::get().setFilename(cacheFile.c_str());
401 ShaderCache::get().initShaderDiskCache();
402
403 // 1st iteration: store pipeline data to be read back on a subsequent "boot" of the "app".
404 // 2nd iteration: ensure that an initial frame flush (without storing any shaders) given the
405 // same pipeline data that's already on disk doesn't break the cache.
406 ShaderCache::get().onVkFrameFlushed(grContext);
407 ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get()));
408 }
409
410 constexpr char shader1[] = "sassas";
411 constexpr char shader2[] = "someVS";
412 constexpr int numIterations = 3;
413 // Also do n iterations of separate "store some shaders then flush the frame" pairs to just
414 // double-check the cache also doesn't get stuck from that use case.
415 for (int saveIteration = 1; saveIteration <= numIterations; saveIteration++) {
416 SCOPED_TRACE("Shader save iteration " + std::to_string(saveIteration));
417 // Write twice to the in-memory cache, which should start a deferred save with both queued.
418 sk_sp<SkData> inVS;
419 setShader(inVS, shader1 + std::to_string(saveIteration));
420 ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
421 setShader(inVS, shader2 + std::to_string(saveIteration));
422 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
423
424 // Simulate flush to also save latest pipeline info.
425 ShaderCache::get().onVkFrameFlushed(grContext);
426 ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get()));
427 }
428
429 // Reload from disk to ensure saving succeeded.
430 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
431 ShaderCache::get().initShaderDiskCache();
432
433 // Read twice, ensure equal to last store.
434 sk_sp<SkData> outVS;
435 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
436 ASSERT_TRUE(checkShader(outVS, shader1 + std::to_string(numIterations)));
437 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
438 ASSERT_TRUE(checkShader(outVS, shader2 + std::to_string(numIterations)));
439
440 // Clean up.
441 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
442 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile));
443 }
444
445 } // namespace
446