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->frameEnd = meta["FrameEnd"].GetInt();
177 traceInfoOut->frameStart = meta["FrameStart"].GetInt();
178 traceInfoOut->isBinaryDataCompressed = meta["IsBinaryDataCompressed"].GetBool();
179 traceInfoOut->isCL = meta.HasMember("IsOpenCL");
180
181 if (meta.HasMember("ContextClientMajorVersion"))
182 {
183 traceInfoOut->contextClientMajorVersion = meta["ContextClientMajorVersion"].GetInt();
184 traceInfoOut->contextClientMinorVersion = meta["ContextClientMinorVersion"].GetInt();
185 traceInfoOut->drawSurfaceHeight = meta["DrawSurfaceHeight"].GetInt();
186 traceInfoOut->drawSurfaceWidth = meta["DrawSurfaceWidth"].GetInt();
187
188 angle::HexStringToUInt(meta["DrawSurfaceColorSpace"].GetString(),
189 &traceInfoOut->drawSurfaceColorSpace);
190 angle::HexStringToUInt(meta["DisplayPlatformType"].GetString(),
191 &traceInfoOut->displayPlatformType);
192 angle::HexStringToUInt(meta["DisplayDeviceType"].GetString(),
193 &traceInfoOut->displayDeviceType);
194
195 traceInfoOut->configRedBits = meta["ConfigRedBits"].GetInt();
196 traceInfoOut->configGreenBits = meta["ConfigGreenBits"].GetInt();
197 traceInfoOut->configBlueBits = meta["ConfigBlueBits"].GetInt();
198 traceInfoOut->configAlphaBits = meta["ConfigAlphaBits"].GetInt();
199 traceInfoOut->configDepthBits = meta["ConfigDepthBits"].GetInt();
200 traceInfoOut->configStencilBits = meta["ConfigStencilBits"].GetInt();
201 traceInfoOut->areClientArraysEnabled = meta["AreClientArraysEnabled"].GetBool();
202 traceInfoOut->isBindGeneratesResourcesEnabled =
203 meta["IsBindGeneratesResourcesEnabled"].GetBool();
204 traceInfoOut->isWebGLCompatibilityEnabled = meta["IsWebGLCompatibilityEnabled"].GetBool();
205 traceInfoOut->isRobustResourceInitEnabled = meta["IsRobustResourceInitEnabled"].GetBool();
206 }
207 else
208 {
209 traceInfoOut->contextClientMajorVersion = 1;
210 traceInfoOut->contextClientMinorVersion = 1;
211 traceInfoOut->drawSurfaceHeight = 1;
212 traceInfoOut->drawSurfaceWidth = 1;
213 }
214
215 if (doc.HasMember("WindowSurfaceContextID"))
216 {
217 traceInfoOut->windowSurfaceContextId = doc["WindowSurfaceContextID"].GetInt();
218 }
219
220 if (doc.HasMember("RequiredExtensions"))
221 {
222 const rapidjson::Value &requiredExtensions = doc["RequiredExtensions"];
223 if (!requiredExtensions.IsArray())
224 {
225 return false;
226 }
227 for (rapidjson::SizeType i = 0; i < requiredExtensions.Size(); i++)
228 {
229 std::string ext = std::string(requiredExtensions[i].GetString());
230 traceInfoOut->requiredExtensions.push_back(ext);
231 }
232 }
233
234 if (meta.HasMember("KeyFrames"))
235 {
236 const rapidjson::Value &keyFrames = meta["KeyFrames"];
237 if (!keyFrames.IsArray())
238 {
239 return false;
240 }
241 for (rapidjson::SizeType i = 0; i < keyFrames.Size(); i++)
242 {
243 int frame = keyFrames[i].GetInt();
244 traceInfoOut->keyFrames.push_back(frame);
245 }
246 }
247
248 const rapidjson::Document::Array &traceFiles = doc["TraceFiles"].GetArray();
249 for (const rapidjson::Value &value : traceFiles)
250 {
251 traceInfoOut->traceFiles.push_back(value.GetString());
252 }
253
254 traceInfoOut->initialized = true;
255 return true;
256 }
257
TraceLibrary(const std::string & traceName,const TraceInfo & traceInfo,const std::string & baseDir)258 TraceLibrary::TraceLibrary(const std::string &traceName,
259 const TraceInfo &traceInfo,
260 const std::string &baseDir)
261 {
262 std::stringstream libNameStr;
263 SearchType searchType = SearchType::ModuleDir;
264
265 #if defined(ANGLE_TRACE_EXTERNAL_BINARIES)
266 // This means we are using the binary build of traces on Android, which are
267 // not bundled in the APK, but located in the app's home directory.
268 searchType = SearchType::SystemDir;
269 libNameStr << baseDir;
270 #endif // defined(ANGLE_TRACE_EXTERNAL_BINARIES)
271 #if !defined(ANGLE_PLATFORM_WINDOWS)
272 libNameStr << "lib";
273 #endif // !defined(ANGLE_PLATFORM_WINDOWS)
274 libNameStr << traceName;
275 std::string libName = libNameStr.str();
276 std::string loadError;
277
278 mTraceLibrary.reset(OpenSharedLibraryAndGetError(libName.c_str(), searchType, &loadError));
279 if (mTraceLibrary->getNative() == nullptr)
280 {
281 FATAL() << "Failed to load trace library (" << libName << "): " << loadError;
282 }
283
284 callFunc<SetupEntryPoints>("SetupEntryPoints", static_cast<angle::TraceCallbacks *>(this),
285 &mTraceFunctions);
286 mTraceFunctions->SetTraceInfo(traceInfo);
287 mTraceInfo = traceInfo;
288 }
289
LoadBinaryData(const char * fileName)290 uint8_t *TraceLibrary::LoadBinaryData(const char *fileName)
291 {
292 std::ostringstream pathBuffer;
293 pathBuffer << mBinaryDataDir << "/" << fileName;
294 FILE *fp = fopen(pathBuffer.str().c_str(), "rb");
295 if (fp == 0)
296 {
297 fprintf(stderr, "Error loading binary data file: %s\n", fileName);
298 exit(1);
299 }
300 fseek(fp, 0, SEEK_END);
301 long size = ftell(fp);
302 fseek(fp, 0, SEEK_SET);
303 if (mTraceInfo.isBinaryDataCompressed)
304 {
305 if (!strstr(fileName, ".gz"))
306 {
307 fprintf(stderr, "Filename does not end in .gz");
308 exit(1);
309 }
310
311 std::vector<uint8_t> compressedData(size);
312 size_t bytesRead = fread(compressedData.data(), 1, size, fp);
313 if (bytesRead != static_cast<size_t>(size))
314 {
315 std::cerr << "Failed to read binary data: " << bytesRead << " != " << size << "\n";
316 exit(1);
317 }
318
319 if (!UncompressData(compressedData, &mBinaryData))
320 {
321 // Workaround for sporadic failures https://issuetracker.google.com/296921272
322 SaveDebugFile(mDebugOutputDir, fileName, ".gzdbg_input.gz", compressedData);
323 SaveDebugFile(mDebugOutputDir, fileName, ".gzdbg_attempt1", mBinaryData);
324 std::vector<uint8_t> uncompressedData;
325 bool secondResult = UncompressData(compressedData, &uncompressedData);
326 SaveDebugFile(mDebugOutputDir, fileName, ".gzdbg_attempt2", uncompressedData);
327 if (!secondResult)
328 {
329 std::cerr << "Uncompress retry failed\n";
330 exit(1);
331 }
332 std::cerr << "Uncompress retry succeeded, moving to mBinaryData\n";
333 mBinaryData = std::move(uncompressedData);
334 }
335 }
336 else
337 {
338 if (!strstr(fileName, ".angledata"))
339 {
340 fprintf(stderr, "Filename does not end in .angledata");
341 exit(1);
342 }
343 mBinaryData.resize(size + 1);
344 (void)fread(mBinaryData.data(), 1, size, fp);
345 }
346 fclose(fp);
347
348 return mBinaryData.data();
349 }
350
351 } // namespace angle
352