1 //
2 // Copyright 2021 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 // frame_capture_test_utils:
7 // Helper functions for capture and replay of traces.
8 //
9
10 #include "frame_capture_test_utils.h"
11
12 #include "common/frame_capture_utils.h"
13 #include "common/string_utils.h"
14
15 #include <rapidjson/document.h>
16 #include <rapidjson/istreamwrapper.h>
17 #include <fstream>
18
19 namespace angle
20 {
21
22 namespace
23 {
LoadJSONFromFile(const std::string & fileName,rapidjson::Document * doc)24 bool LoadJSONFromFile(const std::string &fileName, rapidjson::Document *doc)
25 {
26 std::ifstream ifs(fileName);
27 if (!ifs.is_open())
28 {
29 return false;
30 }
31
32 rapidjson::IStreamWrapper inWrapper(ifs);
33 doc->ParseStream(inWrapper);
34 return !doc->HasParseError();
35 }
36
37 // Branched from:
38 // https://crsrc.org/c/third_party/zlib/google/compression_utils_portable.cc;drc=9fc44ce454cc889b603900ccd14b7024ea2c284c;l=167
39 // Unmodified other than inlining ZlibStreamWrapperType and z_stream arg to access .msg
GzipUncompressHelperPatched(Bytef * dest,uLongf * dest_length,const Bytef * source,uLong source_length,z_stream & stream)40 int GzipUncompressHelperPatched(Bytef *dest,
41 uLongf *dest_length,
42 const Bytef *source,
43 uLong source_length,
44 z_stream &stream)
45 {
46 stream.next_in = static_cast<z_const Bytef *>(const_cast<Bytef *>(source));
47 stream.avail_in = static_cast<uInt>(source_length);
48 if (static_cast<uLong>(stream.avail_in) != source_length)
49 return Z_BUF_ERROR;
50
51 stream.next_out = dest;
52 stream.avail_out = static_cast<uInt>(*dest_length);
53 if (static_cast<uLong>(stream.avail_out) != *dest_length)
54 return Z_BUF_ERROR;
55
56 stream.zalloc = static_cast<alloc_func>(0);
57 stream.zfree = static_cast<free_func>(0);
58
59 int err = inflateInit2(&stream, MAX_WBITS + 16);
60 if (err != Z_OK)
61 return err;
62
63 err = inflate(&stream, Z_FINISH);
64 if (err != Z_STREAM_END)
65 {
66 inflateEnd(&stream);
67 if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
68 return Z_DATA_ERROR;
69 return err;
70 }
71 *dest_length = stream.total_out;
72
73 err = inflateEnd(&stream);
74 return err;
75 }
76
UncompressData(const std::vector<uint8_t> & compressedData,std::vector<uint8_t> * uncompressedData)77 bool UncompressData(const std::vector<uint8_t> &compressedData,
78 std::vector<uint8_t> *uncompressedData)
79 {
80 uint32_t uncompressedSize =
81 zlib_internal::GetGzipUncompressedSize(compressedData.data(), compressedData.size());
82
83 uncompressedData->resize(uncompressedSize + 1); // +1 to make sure .data() is valid
84 uLong destLen = uncompressedSize;
85 z_stream stream;
86 int zResult =
87 GzipUncompressHelperPatched(uncompressedData->data(), &destLen, compressedData.data(),
88 static_cast<uLong>(compressedData.size()), stream);
89
90 if (zResult != Z_OK)
91 {
92 std::cerr << "Failure to decompressed binary data: " << zResult
93 << " msg=" << (stream.msg ? stream.msg : "nil") << "\n";
94 fprintf(stderr,
95 "next_in %p (input %p) avail_in %d total_in %lu next_out %p (output %p) avail_out "
96 "%d total_out %ld adler %lX crc %lX crc_simd %lX\n",
97 stream.next_in, compressedData.data(), stream.avail_in, stream.total_in,
98 stream.next_out, uncompressedData->data(), stream.avail_out, stream.total_out,
99 stream.adler, crc32(0, uncompressedData->data(), uncompressedSize),
100 crc32(0, uncompressedData->data(), 16 * (uncompressedSize / 16)));
101 return false;
102 }
103
104 return true;
105 }
106
SaveDebugFile(const std::string & outputDir,const char * baseFileName,const char * suffix,const std::vector<uint8_t> data)107 void SaveDebugFile(const std::string &outputDir,
108 const char *baseFileName,
109 const char *suffix,
110 const std::vector<uint8_t> data)
111 {
112 if (outputDir.empty())
113 {
114 return;
115 }
116
117 std::ostringstream path;
118 path << outputDir << "/" << baseFileName << suffix;
119 FILE *fp = fopen(path.str().c_str(), "wb");
120 fwrite(data.data(), 1, data.size(), fp);
121 fclose(fp);
122 }
123 } // namespace
124
LoadTraceNamesFromJSON(const std::string jsonFilePath,std::vector<std::string> * namesOut)125 bool LoadTraceNamesFromJSON(const std::string jsonFilePath, std::vector<std::string> *namesOut)
126 {
127 rapidjson::Document doc;
128 if (!LoadJSONFromFile(jsonFilePath, &doc))
129 {
130 return false;
131 }
132
133 if (!doc.IsArray())
134 {
135 return false;
136 }
137
138 // Read trace json into a list of trace names.
139 std::vector<std::string> traces;
140
141 rapidjson::Document::Array traceArray = doc.GetArray();
142 for (rapidjson::SizeType arrayIndex = 0; arrayIndex < traceArray.Size(); ++arrayIndex)
143 {
144 const rapidjson::Document::ValueType &arrayElement = traceArray[arrayIndex];
145
146 if (!arrayElement.IsString())
147 {
148 return false;
149 }
150
151 traces.push_back(arrayElement.GetString());
152 }
153
154 *namesOut = std::move(traces);
155 return true;
156 }
157
LoadTraceInfoFromJSON(const std::string & traceName,const std::string & traceJsonPath,TraceInfo * traceInfoOut)158 bool LoadTraceInfoFromJSON(const std::string &traceName,
159 const std::string &traceJsonPath,
160 TraceInfo *traceInfoOut)
161 {
162 rapidjson::Document doc;
163 if (!LoadJSONFromFile(traceJsonPath, &doc))
164 {
165 return false;
166 }
167
168 if (!doc.IsObject() || !doc.HasMember("TraceMetadata"))
169 {
170 return false;
171 }
172
173 const rapidjson::Document::Object &meta = doc["TraceMetadata"].GetObj();
174
175 strncpy(traceInfoOut->name, traceName.c_str(), kTraceInfoMaxNameLen);
176 traceInfoOut->contextClientMajorVersion = meta["ContextClientMajorVersion"].GetInt();
177 traceInfoOut->contextClientMinorVersion = meta["ContextClientMinorVersion"].GetInt();
178 traceInfoOut->frameEnd = meta["FrameEnd"].GetInt();
179 traceInfoOut->frameStart = meta["FrameStart"].GetInt();
180 traceInfoOut->drawSurfaceHeight = meta["DrawSurfaceHeight"].GetInt();
181 traceInfoOut->drawSurfaceWidth = meta["DrawSurfaceWidth"].GetInt();
182
183 angle::HexStringToUInt(meta["DrawSurfaceColorSpace"].GetString(),
184 &traceInfoOut->drawSurfaceColorSpace);
185 angle::HexStringToUInt(meta["DisplayPlatformType"].GetString(),
186 &traceInfoOut->displayPlatformType);
187 angle::HexStringToUInt(meta["DisplayDeviceType"].GetString(), &traceInfoOut->displayDeviceType);
188
189 traceInfoOut->configRedBits = meta["ConfigRedBits"].GetInt();
190 traceInfoOut->configGreenBits = meta["ConfigGreenBits"].GetInt();
191 traceInfoOut->configBlueBits = meta["ConfigBlueBits"].GetInt();
192 traceInfoOut->configAlphaBits = meta["ConfigAlphaBits"].GetInt();
193 traceInfoOut->configDepthBits = meta["ConfigDepthBits"].GetInt();
194 traceInfoOut->configStencilBits = meta["ConfigStencilBits"].GetInt();
195
196 traceInfoOut->isBinaryDataCompressed = meta["IsBinaryDataCompressed"].GetBool();
197 traceInfoOut->areClientArraysEnabled = meta["AreClientArraysEnabled"].GetBool();
198 traceInfoOut->isBindGeneratesResourcesEnabled =
199 meta["IsBindGeneratesResourcesEnabled"].GetBool();
200 traceInfoOut->isWebGLCompatibilityEnabled = meta["IsWebGLCompatibilityEnabled"].GetBool();
201 traceInfoOut->isRobustResourceInitEnabled = meta["IsRobustResourceInitEnabled"].GetBool();
202 traceInfoOut->windowSurfaceContextId = doc["WindowSurfaceContextID"].GetInt();
203
204 if (doc.HasMember("RequiredExtensions"))
205 {
206 const rapidjson::Value &requiredExtensions = doc["RequiredExtensions"];
207 if (!requiredExtensions.IsArray())
208 {
209 return false;
210 }
211 for (rapidjson::SizeType i = 0; i < requiredExtensions.Size(); i++)
212 {
213 std::string ext = std::string(requiredExtensions[i].GetString());
214 traceInfoOut->requiredExtensions.push_back(ext);
215 }
216 }
217
218 if (meta.HasMember("KeyFrames"))
219 {
220 const rapidjson::Value &keyFrames = meta["KeyFrames"];
221 if (!keyFrames.IsArray())
222 {
223 return false;
224 }
225 for (rapidjson::SizeType i = 0; i < keyFrames.Size(); i++)
226 {
227 int frame = keyFrames[i].GetInt();
228 traceInfoOut->keyFrames.push_back(frame);
229 }
230 }
231
232 const rapidjson::Document::Array &traceFiles = doc["TraceFiles"].GetArray();
233 for (const rapidjson::Value &value : traceFiles)
234 {
235 traceInfoOut->traceFiles.push_back(value.GetString());
236 }
237
238 traceInfoOut->initialized = true;
239 return true;
240 }
241
TraceLibrary(const std::string & traceName,const TraceInfo & traceInfo,const std::string & baseDir)242 TraceLibrary::TraceLibrary(const std::string &traceName,
243 const TraceInfo &traceInfo,
244 const std::string &baseDir)
245 {
246 std::stringstream libNameStr;
247 SearchType searchType = SearchType::ModuleDir;
248
249 #if defined(ANGLE_TRACE_EXTERNAL_BINARIES)
250 // This means we are using the binary build of traces on Android, which are
251 // not bundled in the APK, but located in the app's home directory.
252 searchType = SearchType::SystemDir;
253 libNameStr << baseDir;
254 #endif // defined(ANGLE_TRACE_EXTERNAL_BINARIES)
255 #if !defined(ANGLE_PLATFORM_WINDOWS)
256 libNameStr << "lib";
257 #endif // !defined(ANGLE_PLATFORM_WINDOWS)
258 libNameStr << traceName;
259 std::string libName = libNameStr.str();
260 std::string loadError;
261
262 mTraceLibrary.reset(OpenSharedLibraryAndGetError(libName.c_str(), searchType, &loadError));
263 if (mTraceLibrary->getNative() == nullptr)
264 {
265 FATAL() << "Failed to load trace library (" << libName << "): " << loadError;
266 }
267
268 callFunc<SetupEntryPoints>("SetupEntryPoints", static_cast<angle::TraceCallbacks *>(this),
269 &mTraceFunctions);
270 mTraceFunctions->SetTraceInfo(traceInfo);
271 mTraceInfo = traceInfo;
272 }
273
LoadBinaryData(const char * fileName)274 uint8_t *TraceLibrary::LoadBinaryData(const char *fileName)
275 {
276 std::ostringstream pathBuffer;
277 pathBuffer << mBinaryDataDir << "/" << fileName;
278 FILE *fp = fopen(pathBuffer.str().c_str(), "rb");
279 if (fp == 0)
280 {
281 fprintf(stderr, "Error loading binary data file: %s\n", fileName);
282 exit(1);
283 }
284 fseek(fp, 0, SEEK_END);
285 long size = ftell(fp);
286 fseek(fp, 0, SEEK_SET);
287 if (mTraceInfo.isBinaryDataCompressed)
288 {
289 if (!strstr(fileName, ".gz"))
290 {
291 fprintf(stderr, "Filename does not end in .gz");
292 exit(1);
293 }
294
295 std::vector<uint8_t> compressedData(size);
296 size_t bytesRead = fread(compressedData.data(), 1, size, fp);
297 if (bytesRead != static_cast<size_t>(size))
298 {
299 std::cerr << "Failed to read binary data: " << bytesRead << " != " << size << "\n";
300 exit(1);
301 }
302
303 if (!UncompressData(compressedData, &mBinaryData))
304 {
305 // Workaround for sporadic failures https://issuetracker.google.com/296921272
306 SaveDebugFile(mDebugOutputDir, fileName, ".gzdbg_input.gz", compressedData);
307 SaveDebugFile(mDebugOutputDir, fileName, ".gzdbg_attempt1", mBinaryData);
308 std::vector<uint8_t> uncompressedData;
309 bool secondResult = UncompressData(compressedData, &uncompressedData);
310 SaveDebugFile(mDebugOutputDir, fileName, ".gzdbg_attempt2", uncompressedData);
311 if (!secondResult)
312 {
313 std::cerr << "Uncompress retry failed\n";
314 exit(1);
315 }
316 std::cerr << "Uncompress retry succeeded, moving to mBinaryData\n";
317 mBinaryData = std::move(uncompressedData);
318 }
319 }
320 else
321 {
322 if (!strstr(fileName, ".angledata"))
323 {
324 fprintf(stderr, "Filename does not end in .angledata");
325 exit(1);
326 }
327 mBinaryData.resize(size + 1);
328 (void)fread(mBinaryData.data(), 1, size, fp);
329 }
330 fclose(fp);
331
332 return mBinaryData.data();
333 }
334
335 } // namespace angle
336