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