1 //
2 // Copyright 2020 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 // CaptureReplayTest.cpp:
7 // Application that runs replay for testing of capture replay
8 //
9
10 #include "common/debug.h"
11 #include "common/system_utils.h"
12 #include "platform/PlatformMethods.h"
13 #include "traces_export.h"
14 #include "util/EGLPlatformParameters.h"
15 #include "util/EGLWindow.h"
16 #include "util/OSWindow.h"
17 #include "util/shader_utils.h"
18 #include "util/test_utils.h"
19
20 #if defined(ANGLE_PLATFORM_ANDROID)
21 # include "util/android/AndroidWindow.h"
22 #endif
23
24 #include <stdint.h>
25 #include <string.h>
26 #include <fstream>
27 #include <functional>
28 #include <iostream>
29 #include <list>
30 #include <memory>
31 #include <ostream>
32 #include <string>
33 #include <utility>
34
35 #include "frame_capture_test_utils.h"
36
37 namespace
38 {
39 EGLWindow *gEGLWindow = nullptr;
40 constexpr char kResultTag[] = "*RESULT";
41 constexpr int kInitializationFailure = -1;
42 constexpr int kSerializationFailure = -2;
43 constexpr int kExitSuccess = 0;
44
45 // This arbitrary value rejects placeholder serialized states. In practice they are many thousands
46 // of characters long. See frame_capture_utils_mock.cpp for the current placeholder string.
47 constexpr size_t kTooShortStateLength = 40;
48
IsGLExtensionEnabled(const std::string & extName)49 [[maybe_unused]] bool IsGLExtensionEnabled(const std::string &extName)
50 {
51 return angle::CheckExtensionExists(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)),
52 extName);
53 }
54
DebugCallback(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar * message,const void * userParam)55 [[maybe_unused]] void KHRONOS_APIENTRY DebugCallback(GLenum source,
56 GLenum type,
57 GLuint id,
58 GLenum severity,
59 GLsizei length,
60 const GLchar *message,
61 const void *userParam)
62 {
63 printf("%s\n", message);
64 }
65
LogError(angle::PlatformMethods * platform,const char * errorMessage)66 [[maybe_unused]] void LogError(angle::PlatformMethods *platform, const char *errorMessage)
67 {
68 printf("ERR: %s\n", errorMessage);
69 }
70
LogWarning(angle::PlatformMethods * platform,const char * warningMessage)71 [[maybe_unused]] void LogWarning(angle::PlatformMethods *platform, const char *warningMessage)
72 {
73 printf("WARN: %s\n", warningMessage);
74 }
75
LogInfo(angle::PlatformMethods * platform,const char * infoMessage)76 [[maybe_unused]] void LogInfo(angle::PlatformMethods *platform, const char *infoMessage)
77 {
78 printf("INFO: %s\n", infoMessage);
79 }
80
CompareSerializedContexts(const char * capturedSerializedContextState,const char * replaySerializedContextState)81 bool CompareSerializedContexts(const char *capturedSerializedContextState,
82 const char *replaySerializedContextState)
83 {
84
85 return strcmp(replaySerializedContextState, capturedSerializedContextState) == 0;
86 }
87
EGLCreateImage(EGLDisplay display,EGLContext context,EGLenum target,EGLClientBuffer buffer,const EGLAttrib * attrib_list)88 EGLImage KHRONOS_APIENTRY EGLCreateImage(EGLDisplay display,
89 EGLContext context,
90 EGLenum target,
91 EGLClientBuffer buffer,
92 const EGLAttrib *attrib_list)
93 {
94
95 GLWindowContext ctx = reinterpret_cast<GLWindowContext>(context);
96 return gEGLWindow->createImage(ctx, target, buffer, attrib_list);
97 }
98
EGLCreateImageKHR(EGLDisplay display,EGLContext context,EGLenum target,EGLClientBuffer buffer,const EGLint * attrib_list)99 EGLImage KHRONOS_APIENTRY EGLCreateImageKHR(EGLDisplay display,
100 EGLContext context,
101 EGLenum target,
102 EGLClientBuffer buffer,
103 const EGLint *attrib_list)
104 {
105
106 GLWindowContext ctx = reinterpret_cast<GLWindowContext>(context);
107 return gEGLWindow->createImageKHR(ctx, target, buffer, attrib_list);
108 }
109
EGLDestroyImage(EGLDisplay display,EGLImage image)110 EGLBoolean KHRONOS_APIENTRY EGLDestroyImage(EGLDisplay display, EGLImage image)
111 {
112 return gEGLWindow->destroyImage(image);
113 }
114
EGLDestroyImageKHR(EGLDisplay display,EGLImage image)115 EGLBoolean KHRONOS_APIENTRY EGLDestroyImageKHR(EGLDisplay display, EGLImage image)
116 {
117 return gEGLWindow->destroyImageKHR(image);
118 }
119
EGLCreatePbufferSurface(EGLDisplay display,EGLConfig * config,const EGLint * attrib_list)120 EGLSurface KHRONOS_APIENTRY EGLCreatePbufferSurface(EGLDisplay display,
121 EGLConfig *config,
122 const EGLint *attrib_list)
123 {
124 return gEGLWindow->createPbufferSurface(attrib_list);
125 }
126
EGLDestroySurface(EGLDisplay display,EGLSurface surface)127 EGLBoolean KHRONOS_APIENTRY EGLDestroySurface(EGLDisplay display, EGLSurface surface)
128 {
129 return gEGLWindow->destroySurface(surface);
130 }
131
EGLBindTexImage(EGLDisplay display,EGLSurface surface,EGLint buffer)132 EGLBoolean KHRONOS_APIENTRY EGLBindTexImage(EGLDisplay display, EGLSurface surface, EGLint buffer)
133 {
134 return gEGLWindow->bindTexImage(surface, buffer);
135 }
136
EGLReleaseTexImage(EGLDisplay display,EGLSurface surface,EGLint buffer)137 EGLBoolean KHRONOS_APIENTRY EGLReleaseTexImage(EGLDisplay display,
138 EGLSurface surface,
139 EGLint buffer)
140 {
141 return gEGLWindow->releaseTexImage(surface, buffer);
142 }
143
EGLMakeCurrent(EGLDisplay display,EGLSurface draw,EGLSurface read,EGLContext context)144 EGLBoolean KHRONOS_APIENTRY EGLMakeCurrent(EGLDisplay display,
145 EGLSurface draw,
146 EGLSurface read,
147 EGLContext context)
148 {
149 return gEGLWindow->makeCurrent(draw, read, context);
150 }
151 } // namespace
152
TraceLoadProc(const char * procName)153 GenericProc KHRONOS_APIENTRY TraceLoadProc(const char *procName)
154 {
155 if (!gEGLWindow)
156 {
157 std::cout << "No Window pointer in TraceLoadProc.\n";
158 return nullptr;
159 }
160 else
161 {
162 if (strcmp(procName, "eglCreateImage") == 0)
163 {
164 return reinterpret_cast<GenericProc>(EGLCreateImage);
165 }
166 if (strcmp(procName, "eglCreateImageKHR") == 0)
167 {
168 return reinterpret_cast<GenericProc>(EGLCreateImageKHR);
169 }
170 if (strcmp(procName, "eglDestroyImage") == 0)
171 {
172 return reinterpret_cast<GenericProc>(EGLDestroyImage);
173 }
174 if (strcmp(procName, "eglDestroyImageKHR") == 0)
175 {
176 return reinterpret_cast<GenericProc>(EGLDestroyImageKHR);
177 }
178 if (strcmp(procName, "eglCreatePbufferSurface") == 0)
179 {
180 return reinterpret_cast<GenericProc>(EGLCreatePbufferSurface);
181 }
182 if (strcmp(procName, "eglDestroySurface") == 0)
183 {
184 return reinterpret_cast<GenericProc>(EGLDestroySurface);
185 }
186 if (strcmp(procName, "eglBindTexImage") == 0)
187 {
188 return reinterpret_cast<GenericProc>(EGLBindTexImage);
189 }
190 if (strcmp(procName, "eglReleaseTexImage") == 0)
191 {
192 return reinterpret_cast<GenericProc>(EGLReleaseTexImage);
193 }
194 if (strcmp(procName, "eglMakeCurrent") == 0)
195 {
196 return reinterpret_cast<GenericProc>(EGLMakeCurrent);
197 }
198 return gEGLWindow->getProcAddress(procName);
199 }
200 }
201
202 class CaptureReplayTests
203 {
204 public:
CaptureReplayTests()205 CaptureReplayTests()
206 {
207 // Load EGL library so we can initialize the display.
208 mEntryPointsLib.reset(
209 angle::OpenSharedLibrary(ANGLE_EGL_LIBRARY_NAME, angle::SearchType::ModuleDir));
210
211 mOSWindow = OSWindow::New();
212 mOSWindow->disableErrorMessageDialog();
213 }
214
~CaptureReplayTests()215 ~CaptureReplayTests()
216 {
217 EGLWindow::Delete(&mEGLWindow);
218 OSWindow::Delete(&mOSWindow);
219 }
220
initializeTest(const std::string & execDir,const angle::TraceInfo & traceInfo)221 bool initializeTest(const std::string &execDir, const angle::TraceInfo &traceInfo)
222 {
223 if (!mOSWindow->initialize(traceInfo.name, traceInfo.drawSurfaceWidth,
224 traceInfo.drawSurfaceHeight))
225 {
226 return false;
227 }
228
229 mOSWindow->disableErrorMessageDialog();
230 mOSWindow->setVisible(true);
231
232 if (mEGLWindow && !mEGLWindow->isContextVersion(traceInfo.contextClientMajorVersion,
233 traceInfo.contextClientMinorVersion))
234 {
235 EGLWindow::Delete(&mEGLWindow);
236 mEGLWindow = nullptr;
237 }
238
239 if (!mEGLWindow)
240 {
241 mEGLWindow = EGLWindow::New(traceInfo.contextClientMajorVersion,
242 traceInfo.contextClientMinorVersion);
243 }
244
245 ConfigParameters configParams;
246 configParams.redBits = traceInfo.configRedBits;
247 configParams.greenBits = traceInfo.configGreenBits;
248 configParams.blueBits = traceInfo.configBlueBits;
249 configParams.alphaBits = traceInfo.configAlphaBits;
250 configParams.depthBits = traceInfo.configDepthBits;
251 configParams.stencilBits = traceInfo.configStencilBits;
252
253 configParams.clientArraysEnabled = traceInfo.areClientArraysEnabled;
254 configParams.bindGeneratesResource = traceInfo.isBindGeneratesResourcesEnabled;
255 configParams.webGLCompatibility = traceInfo.isWebGLCompatibilityEnabled;
256 configParams.robustResourceInit = traceInfo.isRobustResourceInitEnabled;
257
258 mPlatformParams.renderer = traceInfo.displayPlatformType;
259 mPlatformParams.deviceType = traceInfo.displayDeviceType;
260 mPlatformParams.enable(angle::Feature::ForceInitShaderVariables);
261 mPlatformParams.enable(angle::Feature::EnableCaptureLimits);
262
263 #if defined(ANGLE_ENABLE_ASSERTS)
264 mPlatformMethods.logError = LogError;
265 mPlatformMethods.logWarning = LogWarning;
266 mPlatformMethods.logInfo = LogInfo;
267 mPlatformParams.platformMethods = &mPlatformMethods;
268 #endif // defined(ANGLE_ENABLE_ASSERTS)
269
270 if (!mEGLWindow->initializeGL(mOSWindow, mEntryPointsLib.get(),
271 angle::GLESDriverType::AngleEGL, mPlatformParams,
272 configParams))
273 {
274 mOSWindow->destroy();
275 return false;
276 }
277
278 gEGLWindow = mEGLWindow;
279 LoadTraceEGL(TraceLoadProc);
280 LoadTraceGLES(TraceLoadProc);
281
282 // Disable vsync
283 if (!mEGLWindow->setSwapInterval(0))
284 {
285 cleanupTest();
286 return false;
287 }
288
289 #if defined(ANGLE_ENABLE_ASSERTS)
290 if (IsGLExtensionEnabled("GL_KHR_debug"))
291 {
292 EnableDebugCallback(DebugCallback, nullptr);
293 }
294 #endif
295
296 std::string baseDir = "";
297 #if defined(ANGLE_TRACE_EXTERNAL_BINARIES)
298 baseDir += AndroidWindow::GetApplicationDirectory() + "/angle_traces/";
299 #endif
300 // Load trace
301 mTraceLibrary.reset(new angle::TraceLibrary(traceInfo.name, traceInfo, baseDir));
302 if (!mTraceLibrary->valid())
303 {
304 std::cout << "Failed to load trace library: " << traceInfo.name << "\n";
305 return false;
306 }
307
308 std::stringstream binaryPathStream;
309 binaryPathStream << execDir << angle::GetPathSeparator()
310 << ANGLE_CAPTURE_REPLAY_TEST_DATA_DIR;
311
312 mTraceLibrary->setBinaryDataDir(binaryPathStream.str().c_str());
313
314 mTraceLibrary->setupReplay();
315 return true;
316 }
317
cleanupTest()318 void cleanupTest()
319 {
320 mTraceLibrary->finishReplay();
321 mTraceLibrary.reset(nullptr);
322 mEGLWindow->destroyGL();
323 mOSWindow->destroy();
324 }
325
swap()326 void swap() { mEGLWindow->swap(); }
327
runTest(const std::string & exeDir,const angle::TraceInfo & traceInfo)328 int runTest(const std::string &exeDir, const angle::TraceInfo &traceInfo)
329 {
330 if (!initializeTest(exeDir, traceInfo))
331 {
332 return kInitializationFailure;
333 }
334
335 for (uint32_t frame = traceInfo.frameStart; frame <= traceInfo.frameEnd; frame++)
336 {
337 mTraceLibrary->replayFrame(frame);
338
339 const char *replayedSerializedState =
340 reinterpret_cast<const char *>(glGetString(GL_SERIALIZED_CONTEXT_STRING_ANGLE));
341 const char *capturedSerializedState = mTraceLibrary->getSerializedContextState(frame);
342
343 if (replayedSerializedState == nullptr ||
344 strlen(replayedSerializedState) <= kTooShortStateLength)
345 {
346 printf("Could not retrieve replay serialized state string.\n");
347 return kSerializationFailure;
348 }
349
350 if (capturedSerializedState == nullptr ||
351 strlen(capturedSerializedState) <= kTooShortStateLength)
352 {
353 printf("Could not retrieve captured serialized state string.\n");
354 return kSerializationFailure;
355 }
356
357 // Swap always to allow RenderDoc/other tools to capture frames.
358 swap();
359 if (!CompareSerializedContexts(replayedSerializedState, capturedSerializedState))
360 {
361 printf("Serialized contexts differ, saving files.\n");
362
363 std::ostringstream replayName;
364 replayName << exeDir << angle::GetPathSeparator() << traceInfo.name
365 << "_ContextReplayed" << frame << ".json";
366
367 std::ofstream debugReplay(replayName.str());
368 if (!debugReplay)
369 {
370 printf("Error opening debug replay stream.\n");
371 }
372 else
373 {
374 debugReplay << (replayedSerializedState ? replayedSerializedState : "") << "\n";
375 printf("Wrote %s.\n", replayName.str().c_str());
376 }
377
378 std::ostringstream captureName;
379 captureName << exeDir << angle::GetPathSeparator() << traceInfo.name
380 << "_ContextCaptured" << frame << ".json";
381
382 std::ofstream debugCapture(captureName.str());
383 if (!debugCapture)
384 {
385 printf("Error opening debug capture stream.\n");
386 }
387 else
388 {
389 debugCapture << (capturedSerializedState ? capturedSerializedState : "")
390 << "\n";
391 printf("Wrote %s.\n", captureName.str().c_str());
392 }
393
394 cleanupTest();
395 return kSerializationFailure;
396 }
397 }
398 cleanupTest();
399 return kExitSuccess;
400 }
401
run(const char * trace)402 int run(const char *trace)
403 {
404 std::string startingDirectory = angle::GetCWD().value();
405
406 // Set CWD to executable directory.
407 std::string exeDir = angle::GetExecutableDirectory();
408
409 std::stringstream traceJsonPathStream;
410 traceJsonPathStream << exeDir << angle::GetPathSeparator()
411 << ANGLE_CAPTURE_REPLAY_TEST_DATA_DIR << angle::GetPathSeparator()
412 << trace << ".json";
413 std::string traceJsonPath = traceJsonPathStream.str();
414
415 int result = kInitializationFailure;
416 angle::TraceInfo traceInfo = {};
417 if (!angle::LoadTraceInfoFromJSON(trace, traceJsonPath, &traceInfo))
418 {
419 std::cout << "Unable to load trace data: " << traceJsonPath << "\n";
420 }
421 else
422 {
423 result = runTest(exeDir, traceInfo);
424 }
425 std::cout << kResultTag << " " << trace << " " << result << "\n";
426
427 angle::SetCWD(startingDirectory.c_str());
428 return kExitSuccess;
429 }
430
431 private:
432 OSWindow *mOSWindow = nullptr;
433 EGLWindow *mEGLWindow = nullptr;
434 EGLPlatformParameters mPlatformParams;
435 angle::PlatformMethods mPlatformMethods;
436 // Handle to the entry point binding library.
437 std::unique_ptr<angle::Library> mEntryPointsLib;
438 std::unique_ptr<angle::TraceLibrary> mTraceLibrary;
439 };
440
main(int argc,char ** argv)441 int main(int argc, char **argv)
442 {
443 if (argc != 2)
444 {
445 printf("Usage: capture_replay_tests {trace_label}\n");
446 return -1;
447 }
448 angle::CrashCallback crashCallback = []() {};
449 angle::InitCrashHandler(&crashCallback);
450
451 CaptureReplayTests app;
452 return app.run(argv[1]);
453 }
454