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 <cutils/properties.h>
18 #include <dirent.h>
19 #include <errno.h>
20 #include <gtest/gtest.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <sys/types.h>
24 #include <utils/Log.h>
25 #include <cstdint>
26 #include "FileBlobCache.h"
27 #include "pipeline/skia/ShaderCache.h"
28
29 using namespace android::uirenderer::skiapipeline;
30
31 namespace android {
32 namespace uirenderer {
33 namespace skiapipeline {
34
35 class ShaderCacheTestUtils {
36 public:
37 /**
38 * "setSaveDelay" sets the time in seconds to wait before saving newly inserted cache entries.
39 * If set to 0, then deferred save is disabled.
40 */
setSaveDelay(ShaderCache & cache,unsigned int saveDelay)41 static void setSaveDelay(ShaderCache& cache, unsigned int saveDelay) {
42 cache.mDeferredSaveDelay = saveDelay;
43 }
44
45 /**
46 * "terminate" optionally stores the BlobCache on disk and release all in-memory cache.
47 * Next call to "initShaderDiskCache" will load again the in-memory cache from disk.
48 */
terminate(ShaderCache & cache,bool saveContent)49 static void terminate(ShaderCache& cache, bool saveContent) {
50 std::lock_guard<std::mutex> lock(cache.mMutex);
51 cache.mSavePending = saveContent;
52 cache.saveToDiskLocked();
53 cache.mBlobCache = NULL;
54 }
55
56 /**
57 *
58 */
59 template <typename T>
validateCache(ShaderCache & cache,std::vector<T> hash)60 static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
61 return cache.validateCache(hash.data(), hash.size() * sizeof(T));
62 }
63 };
64
65 } /* namespace skiapipeline */
66 } /* namespace uirenderer */
67 } /* namespace android */
68
69 namespace {
70
getExternalStorageFolder()71 std::string getExternalStorageFolder() {
72 return getenv("EXTERNAL_STORAGE");
73 }
74
folderExist(const std::string & folderName)75 bool folderExist(const std::string& folderName) {
76 DIR* dir = opendir(folderName.c_str());
77 if (dir) {
78 closedir(dir);
79 return true;
80 }
81 return false;
82 }
83
checkShader(const sk_sp<SkData> & shader1,const sk_sp<SkData> & shader2)84 inline bool checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) {
85 return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() &&
86 0 == memcmp(shader1->data(), shader2->data(), shader1->size());
87 }
88
checkShader(const sk_sp<SkData> & shader,const char * program)89 inline bool checkShader(const sk_sp<SkData>& shader, const char* program) {
90 sk_sp<SkData> shader2 = SkData::MakeWithCString(program);
91 return checkShader(shader, shader2);
92 }
93
94 template <typename T>
checkShader(const sk_sp<SkData> & shader,std::vector<T> & program)95 bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) {
96 sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T));
97 return checkShader(shader, shader2);
98 }
99
setShader(sk_sp<SkData> & shader,const char * program)100 void setShader(sk_sp<SkData>& shader, const char* program) {
101 shader = SkData::MakeWithCString(program);
102 }
103
104 template <typename T>
setShader(sk_sp<SkData> & shader,std::vector<T> & buffer)105 void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) {
106 shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T));
107 }
108
109 template <typename T>
genRandomData(std::vector<T> & buffer)110 void genRandomData(std::vector<T>& buffer) {
111 for (auto& data : buffer) {
112 data = T(std::rand());
113 }
114 }
115
116 #define GrProgramDescTest(a) (*SkData::MakeWithCString(#a).get())
117
TEST(ShaderCacheTest,testWriteAndRead)118 TEST(ShaderCacheTest, testWriteAndRead) {
119 if (!folderExist(getExternalStorageFolder())) {
120 // don't run the test if external storage folder is not available
121 return;
122 }
123 std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1";
124 std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
125
126 // remove any test files from previous test run
127 int deleteFile = remove(cacheFile1.c_str());
128 ASSERT_TRUE(0 == deleteFile || ENOENT == errno);
129 std::srand(0);
130
131 // read the cache from a file that does not exist
132 ShaderCache::get().setFilename(cacheFile1.c_str());
133 ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save
134 ShaderCache::get().initShaderDiskCache();
135
136 // read a key - should not be found since the cache is empty
137 sk_sp<SkData> outVS;
138 ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>());
139
140 // write to the in-memory cache without storing on disk and verify we read the same values
141 sk_sp<SkData> inVS;
142 setShader(inVS, "sassas");
143 ShaderCache::get().store(GrProgramDescTest(100), *inVS.get());
144 setShader(inVS, "someVS");
145 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
146 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
147 ASSERT_TRUE(checkShader(outVS, "sassas"));
148 ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
149 ASSERT_TRUE(checkShader(outVS, "someVS"));
150
151 // store content to disk and release in-memory cache
152 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
153
154 // change to a file that does not exist and verify load fails
155 ShaderCache::get().setFilename(cacheFile2.c_str());
156 ShaderCache::get().initShaderDiskCache();
157 ASSERT_EQ(ShaderCache::get().load(GrProgramDescTest(432)), sk_sp<SkData>());
158 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
159
160 // load again content from disk from an existing file and check the data is read correctly
161 ShaderCache::get().setFilename(cacheFile1.c_str());
162 ShaderCache::get().initShaderDiskCache();
163 sk_sp<SkData> outVS2;
164 ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
165 ASSERT_TRUE(checkShader(outVS2, "someVS"));
166
167 // change data, store to disk, read back again and verify data has been changed
168 setShader(inVS, "ewData1");
169 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
170 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
171 ShaderCache::get().initShaderDiskCache();
172 ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
173 ASSERT_TRUE(checkShader(outVS2, "ewData1"));
174
175 // write and read big data chunk (50K)
176 size_t dataSize = 50 * 1024;
177 std::vector<uint8_t> dataBuffer(dataSize);
178 genRandomData(dataBuffer);
179 setShader(inVS, dataBuffer);
180 ShaderCache::get().store(GrProgramDescTest(432), *inVS.get());
181 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
182 ShaderCache::get().initShaderDiskCache();
183 ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
184 ASSERT_TRUE(checkShader(outVS2, dataBuffer));
185
186 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
187 remove(cacheFile1.c_str());
188 }
189
TEST(ShaderCacheTest,testCacheValidation)190 TEST(ShaderCacheTest, testCacheValidation) {
191 if (!folderExist(getExternalStorageFolder())) {
192 // don't run the test if external storage folder is not available
193 return;
194 }
195 std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1";
196 std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
197
198 // remove any test files from previous test run
199 int deleteFile = remove(cacheFile1.c_str());
200 ASSERT_TRUE(0 == deleteFile || ENOENT == errno);
201 std::srand(0);
202
203 // generate identity and read the cache from a file that does not exist
204 ShaderCache::get().setFilename(cacheFile1.c_str());
205 ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save
206 std::vector<uint8_t> identity(1024);
207 genRandomData(identity);
208 ShaderCache::get().initShaderDiskCache(
209 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
210
211 // generate random content in cache and store to disk
212 constexpr size_t numBlob(10);
213 constexpr size_t keySize(1024);
214 constexpr size_t dataSize(50 * 1024);
215
216 std::vector<std::pair<sk_sp<SkData>, sk_sp<SkData>>> blobVec(numBlob);
217 for (auto& blob : blobVec) {
218 std::vector<uint8_t> keyBuffer(keySize);
219 std::vector<uint8_t> dataBuffer(dataSize);
220 genRandomData(keyBuffer);
221 genRandomData(dataBuffer);
222
223 sk_sp<SkData> key, data;
224 setShader(key, keyBuffer);
225 setShader(data, dataBuffer);
226
227 blob = std::make_pair(key, data);
228 ShaderCache::get().store(*key.get(), *data.get());
229 }
230 ShaderCacheTestUtils::terminate(ShaderCache::get(), true);
231
232 // change to a file that does not exist and verify validation fails
233 ShaderCache::get().setFilename(cacheFile2.c_str());
234 ShaderCache::get().initShaderDiskCache();
235 ASSERT_FALSE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity));
236 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
237
238 // restore the original file and verify validation succeeds
239 ShaderCache::get().setFilename(cacheFile1.c_str());
240 ShaderCache::get().initShaderDiskCache(
241 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
242 ASSERT_TRUE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity));
243 for (const auto& blob : blobVec) {
244 auto outVS = ShaderCache::get().load(*blob.first.get());
245 ASSERT_TRUE(checkShader(outVS, blob.second));
246 }
247
248 // generate error identity and verify load fails
249 ShaderCache::get().initShaderDiskCache(identity.data(), -1);
250 for (const auto& blob : blobVec) {
251 ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>());
252 }
253 ShaderCache::get().initShaderDiskCache(
254 nullptr, identity.size() * sizeof(decltype(identity)::value_type));
255 for (const auto& blob : blobVec) {
256 ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>());
257 }
258
259 // verify the cache validation again after load fails
260 ShaderCache::get().initShaderDiskCache(
261 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
262 ASSERT_TRUE(ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity));
263 for (const auto& blob : blobVec) {
264 auto outVS = ShaderCache::get().load(*blob.first.get());
265 ASSERT_TRUE(checkShader(outVS, blob.second));
266 }
267
268 // generate another identity and verify load fails
269 for (auto& data : identity) {
270 data += std::rand();
271 }
272 ShaderCache::get().initShaderDiskCache(
273 identity.data(), identity.size() * sizeof(decltype(identity)::value_type));
274 for (const auto& blob : blobVec) {
275 ASSERT_EQ(ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>());
276 }
277
278 ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
279 remove(cacheFile1.c_str());
280 }
281
282 } // namespace
283