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