xref: /aosp_15_r20/external/angle/util/capture/frame_capture_test_utils.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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