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<std::mutex> lock(cache.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<std::mutex> 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<std::mutex> 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 return cache.validateCache(hash.data(), hash.size() * sizeof(T));
97 }
98
99 /**
100 * Waits until cache::mSavePending is false, checking every 0.1 ms *while the mutex is free*.
101 *
102 * Fails if there was no save pending, or if the cache was already being written to disk, or if
103 * timeoutMs is exceeded.
104 *
105 * Note: timeoutMs only guards against mSavePending getting stuck like in b/268205519, and
106 * cannot protect against mutex-based deadlock. Reaching timeoutMs implies something is broken,
107 * so setting it to a sufficiently large value will not delay execution in the happy state.
108 */
waitForPendingSave(ShaderCache & cache,const int timeoutMs=50)109 static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) {
110 {
111 std::lock_guard<std::mutex> lock(cache.mMutex);
112 ASSERT_TRUE(cache.mSavePending);
113 }
114 bool saving = true;
115 float elapsedMilliseconds = 0;
116 while (saving) {
117 if (elapsedMilliseconds >= timeoutMs) {
118 FAIL() << "Timed out after waiting " << timeoutMs << " ms for a pending save";
119 }
120 // This small (0.1 ms) delay is to avoid working too much while waiting for
121 // deferredSaveThread to take the mutex and start the disk write.
122 const int delayMicroseconds = 100;
123 usleep(delayMicroseconds);
124 elapsedMilliseconds += (float)delayMicroseconds / 1000;
125
126 std::lock_guard<std::mutex> lock(cache.mMutex);
127 saving = cache.mSavePending;
128 }
129 }
130 };
131
132 } /* namespace skiapipeline */
133 } /* namespace uirenderer */
134 } /* namespace android */
135
136 namespace {
137
getExternalStorageFolder()138 std::string getExternalStorageFolder() {
139 return getenv("EXTERNAL_STORAGE");
140 }
141
folderExist(const std::string & folderName)142 bool folderExist(const std::string& folderName) {
143 DIR* dir = opendir(folderName.c_str());
144 if (dir) {
145 closedir(dir);
146 return true;
147 }
148 return false;
149 }
150
151 /**
152 * Attempts to delete the given file, and asserts that either:
153 * 1. Deletion was successful, OR
154 * 2. The file did not exist.
155 *
156 * Tip: wrap calls to this in ASSERT_NO_FATAL_FAILURE(x) if a test should exit early if this fails.
157 */
deleteFileAssertSuccess(const std::string & filePath)158 void deleteFileAssertSuccess(const std::string& filePath) {
159 int deleteResult = remove(filePath.c_str());
160 ASSERT_TRUE(0 == deleteResult || ENOENT == errno);
161 }
162
checkShader(const sk_sp<SkData> & shader1,const sk_sp<SkData> & shader2)163 inline bool checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) {
164 return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() &&
165 0 == memcmp(shader1->data(), shader2->data(), shader1->size());
166 }
167
checkShader(const sk_sp<SkData> & shader,const char * program)168 inline bool checkShader(const sk_sp<SkData>& shader, const char* program) {
169 sk_sp<SkData> shader2 = SkData::MakeWithCString(program);
170 return checkShader(shader, shader2);
171 }
172
checkShader(const sk_sp<SkData> & shader,const std::string & program)173 inline bool checkShader(const sk_sp<SkData>& shader, const std::string& program) {
174 return checkShader(shader, program.c_str());
175 }
176
177 template <typename T>
checkShader(const sk_sp<SkData> & shader,std::vector<T> & program)178 bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) {
179 sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T));
180 return checkShader(shader, shader2);
181 }
182
setShader(sk_sp<SkData> & shader,const char * program)183 void setShader(sk_sp<SkData>& shader, const char* program) {
184 shader = SkData::MakeWithCString(program);
185 }
186
setShader(sk_sp<SkData> & shader,const std::string & program)187 void setShader(sk_sp<SkData>& shader, const std::string& program) {
188 setShader(shader, program.c_str());
189 }
190
191 template <typename T>
setShader(sk_sp<SkData> & shader,std::vector<T> & buffer)192 void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) {
193 shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T));
194 }
195
196 template <typename T>
genRandomData(std::vector<T> & buffer)197 void genRandomData(std::vector<T>& buffer) {
198 for (auto& data : buffer) {
199 data = T(std::rand());
200 }
201 }
202
203 #define GrProgramDescTest(a) (*SkData::MakeWithCString(#a).get())
204
TEST(ShaderCacheTest,testWriteAndRead)205 TEST(ShaderCacheTest, testWriteAndRead) {
206 if (!folderExist(getExternalStorageFolder())) {
207 // don't run the test if external storage folder is not available
208 return;
209 }
210 std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1";
211 std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
212
213 // remove any test files from previous test run
214 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
215 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
216 std::srand(0);
217
218 // read the cache from a file that does not exist
219 ShaderCache::get().setFilename(cacheFile1.c_str());
220 ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save
221 ShaderCache::get().initShaderDiskCache();
222
223 // read a key - should not be found since the cache is empty
224 sk_sp<SkData> outVS;
225 ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>());
226
227 // write to the in-memory cache without storing on disk and verify we read the same values
228 sk_sp<SkData> inVS;
229 setShader(inVS, "sassas");
230 ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
231 setShader(inVS, "someVS");
232 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
233 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
234 ASSERT_TRUE(checkShader(outVS, "sassas"));
235 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
236 ASSERT_TRUE(checkShader(outVS, "someVS"));
237
238 // store content to disk and release in-memory cache
239 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
240
241 // change to a file that does not exist and verify load fails
242 ShaderCache::get().setFilename(cacheFile2.c_str());
243 ShaderCache::get().initShaderDiskCache();
244 ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>());
245 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
246
247 // load again content from disk from an existing file and check the data is read correctly
248 ShaderCache::get().setFilename(cacheFile1.c_str());
249 ShaderCache::get().initShaderDiskCache();
250 sk_sp<SkData> outVS2;
251 ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
252 ASSERT_TRUE(checkShader(outVS2, "someVS"));
253
254 // change data, store to disk, read back again and verify data has been changed
255 setShader(inVS, "ewData1");
256 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
257 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
258 ShaderCache::get().initShaderDiskCache();
259 ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
260 ASSERT_TRUE(checkShader(outVS2, "ewData1"));
261
262 // write and read big data chunk (50K)
263 size_t dataSize = 50 * 1024;
264 std::vector<uint8_t> dataBuffer(dataSize);
265 genRandomData(dataBuffer);
266 setShader(inVS, dataBuffer);
267 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
268 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
269 ShaderCache::get().initShaderDiskCache();
270 ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
271 ASSERT_TRUE(checkShader(outVS2, dataBuffer));
272
273 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
274 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
275 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
276 }
277
TEST(ShaderCacheTest,testCacheValidation)278 TEST(ShaderCacheTest, testCacheValidation) {
279 if (!folderExist(getExternalStorageFolder())) {
280 // don't run the test if external storage folder is not available
281 return;
282 }
283 std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1";
284 std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
285
286 // remove any test files from previous test run
287 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
288 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
289 std::srand(0);
290
291 // generate identity and read the cache from a file that does not exist
292 ShaderCache::get().setFilename(cacheFile1.c_str());
293 ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save
294 std::vector<uint8_t> identity(1024);
295 genRandomData(identity);
296 ShaderCache::get().initShaderDiskCache(
297 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
298
299 // generate random content in cache and store to disk
300 constexpr size_t numBlob(10);
301 constexpr size_t keySize(1024);
302 constexpr size_t dataSize(50 * 1024);
303
304 std::vector<std::pair<sk_sp<SkData>, sk_sp<SkData>>> blobVec(numBlob);
305 for (auto& blob : blobVec) {
306 std::vector<uint8_t> keyBuffer(keySize);
307 std::vector<uint8_t> dataBuffer(dataSize);
308 genRandomData(keyBuffer);
309 genRandomData(dataBuffer);
310
311 sk_sp<SkData> key, data;
312 setShader(key, keyBuffer);
313 setShader(data, dataBuffer);
314
315 blob = std::make_pair(key, data);
316 ShaderCache::get().store(*key.get(), *data.get(), SkString());
317 }
318 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
319
320 // change to a file that does not exist and verify validation fails
321 ShaderCache::get().setFilename(cacheFile2.c_str());
322 ShaderCache::get().initShaderDiskCache();
323 ASSERT_FALSE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity));
324 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
325
326 // restore the original file and verify validation succeeds
327 ShaderCache::get().setFilename(cacheFile1.c_str());
328 ShaderCache::get().initShaderDiskCache(
329 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
330 ASSERT_TRUE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity));
331 for (const auto& blob : blobVec) {
332 auto outVS = ShaderCache::get().load(*blob.first.get());
333 ASSERT_TRUE(checkShader(outVS, blob.second));
334 }
335
336 // generate error identity and verify load fails
337 ShaderCache::get().initShaderDiskCache(identity.data(), -1);
338 for (const auto& blob : blobVec) {
339 ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>());
340 }
341 ShaderCache::get().initShaderDiskCache(
342 nullptr, identity.size() * sizeof(decltype(identity)::value_type));
343 for (const auto& blob : blobVec) {
344 ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>());
345 }
346
347 // verify the cache validation again after load fails
348 ShaderCache::get().initShaderDiskCache(
349 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
350 ASSERT_TRUE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity));
351 for (const auto& blob : blobVec) {
352 auto outVS = ShaderCache::get().load(*blob.first.get());
353 ASSERT_TRUE(checkShader(outVS, blob.second));
354 }
355
356 // generate another identity and verify load fails
357 for (auto& data : identity) {
358 data += std::rand();
359 }
360 ShaderCache::get().initShaderDiskCache(
361 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
362 for (const auto& blob : blobVec) {
363 ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>());
364 }
365
366 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
367 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
368 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
369 }
370
371 using namespace android::uirenderer;
RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest,testOnVkFrameFlushed)372 RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
373 if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) {
374 // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants.
375 GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan";
376 }
377 if (!folderExist(getExternalStorageFolder())) {
378 // Don't run the test if external storage folder is not available
379 return;
380 }
381 std::string cacheFile = getExternalStorageFolder() + "/shaderCacheTest";
382 GrDirectContext* grContext = renderThread.getGrContext();
383
384 // Remove any test files from previous test run
385 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile));
386
387 // The first iteration of this loop is to save an initial VkPipelineCache data blob to disk,
388 // which sets up the second iteration for a common scenario of comparing a "new" VkPipelineCache
389 // blob passed to "store" against the same blob that's already in the persistent cache from a
390 // previous launch. "reinitializeAllFields" is critical to emulate each iteration being as close
391 // to the state of a freshly launched app as possible, as the initial values of member variables
392 // like mInStoreVkPipelineInProgress and mOldPipelineCacheSize are critical to catch issues
393 // such as b/268205519
394 for (int flushIteration = 1; flushIteration <= 2; flushIteration++) {
395 SCOPED_TRACE("Frame flush iteration " + std::to_string(flushIteration));
396 // Reset *all* in-memory data and reload the cache from disk.
397 ShaderCacheTestUtils::reinitializeAllFields(ShaderCache::get());
398 ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 10); // Delay must be > 0 to save.
399 ShaderCache::get().setFilename(cacheFile.c_str());
400 ShaderCache::get().initShaderDiskCache();
401
402 // 1st iteration: store pipeline data to be read back on a subsequent "boot" of the "app".
403 // 2nd iteration: ensure that an initial frame flush (without storing any shaders) given the
404 // same pipeline data that's already on disk doesn't break the cache.
405 ShaderCache::get().onVkFrameFlushed(grContext);
406 ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get()));
407 }
408
409 constexpr char shader1[] = "sassas";
410 constexpr char shader2[] = "someVS";
411 constexpr int numIterations = 3;
412 // Also do n iterations of separate "store some shaders then flush the frame" pairs to just
413 // double-check the cache also doesn't get stuck from that use case.
414 for (int saveIteration = 1; saveIteration <= numIterations; saveIteration++) {
415 SCOPED_TRACE("Shader save iteration " + std::to_string(saveIteration));
416 // Write twice to the in-memory cache, which should start a deferred save with both queued.
417 sk_sp<SkData> inVS;
418 setShader(inVS, shader1 + std::to_string(saveIteration));
419 ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
420 setShader(inVS, shader2 + std::to_string(saveIteration));
421 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
422
423 // Simulate flush to also save latest pipeline info.
424 ShaderCache::get().onVkFrameFlushed(grContext);
425 ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get()));
426 }
427
428 // Reload from disk to ensure saving succeeded.
429 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
430 ShaderCache::get().initShaderDiskCache();
431
432 // Read twice, ensure equal to last store.
433 sk_sp<SkData> outVS;
434 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
435 ASSERT_TRUE(checkShader(outVS, shader1 + std::to_string(numIterations)));
436 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
437 ASSERT_TRUE(checkShader(outVS, shader2 + std::to_string(numIterations)));
438
439 // Clean up.
440 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
441 ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile));
442 }
443
444 } // namespace
445