1 //
2 // Copyright 2025 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 // FrameCaptureCommon.cpp:
7 // ANGLE Frame capture implementation for both GL and CL.
8 //
9
10 #include "libANGLE/capture/FrameCapture.h"
11
12 #define USE_SYSTEM_ZLIB
13 #include "compression_utils_portable.h"
14
15 namespace angle
16 {
17
GetBinaryDataFilePath(bool compression,const std::string & captureLabel)18 std::string GetBinaryDataFilePath(bool compression, const std::string &captureLabel)
19 {
20 std::stringstream fnameStream;
21 fnameStream << FmtCapturePrefix(kNoContextId, captureLabel) << ".angledata";
22 if (compression)
23 {
24 fnameStream << ".gz";
25 }
26 return fnameStream.str();
27 }
28
SaveBinaryData(bool compression,const std::string & outDir,gl::ContextID contextId,const std::string & captureLabel,const std::vector<uint8_t> & binaryData)29 void SaveBinaryData(bool compression,
30 const std::string &outDir,
31 gl::ContextID contextId,
32 const std::string &captureLabel,
33 const std::vector<uint8_t> &binaryData)
34 {
35 std::string binaryDataFileName = GetBinaryDataFilePath(compression, captureLabel);
36 std::string dataFilepath = outDir + binaryDataFileName;
37
38 SaveFileHelper saveData(dataFilepath);
39
40 if (compression)
41 {
42 // Save compressed data.
43 uLong uncompressedSize = static_cast<uLong>(binaryData.size());
44 uLong expectedCompressedSize = zlib_internal::GzipExpectedCompressedSize(uncompressedSize);
45
46 std::vector<uint8_t> compressedData(expectedCompressedSize, 0);
47
48 uLong compressedSize = expectedCompressedSize;
49 int zResult = zlib_internal::GzipCompressHelper(compressedData.data(), &compressedSize,
50 binaryData.data(), uncompressedSize,
51 nullptr, nullptr);
52
53 if (zResult != Z_OK)
54 {
55 FATAL() << "Error compressing binary data: " << zResult;
56 }
57
58 saveData.write(compressedData.data(), compressedSize);
59 }
60 else
61 {
62 saveData.write(binaryData.data(), binaryData.size());
63 }
64 }
65
66 template <>
WriteInlineData(const std::vector<uint8_t> & vec,std::ostream & out)67 void WriteInlineData<GLchar>(const std::vector<uint8_t> &vec, std::ostream &out)
68 {
69 const GLchar *data = reinterpret_cast<const GLchar *>(vec.data());
70 size_t count = vec.size() / sizeof(GLchar);
71
72 if (data == nullptr || data[0] == '\0')
73 {
74 return;
75 }
76
77 out << "\"";
78
79 for (size_t dataIndex = 0; dataIndex < count; ++dataIndex)
80 {
81 if (data[dataIndex] == '\0')
82 break;
83
84 out << static_cast<GLchar>(data[dataIndex]);
85 }
86
87 out << "\"";
88 }
89
WriteBinaryParamReplay(ReplayWriter & replayWriter,std::ostream & out,std::ostream & header,const CallCapture & call,const ParamCapture & param,std::vector<uint8_t> * binaryData)90 void WriteBinaryParamReplay(ReplayWriter &replayWriter,
91 std::ostream &out,
92 std::ostream &header,
93 const CallCapture &call,
94 const ParamCapture ¶m,
95 std::vector<uint8_t> *binaryData)
96 {
97 std::string varName = replayWriter.getInlineVariableName(call.entryPoint, param.name);
98
99 ASSERT(param.data.size() == 1);
100 const std::vector<uint8_t> &data = param.data[0];
101
102 // Only inline strings (shaders) to simplify the C code.
103 ParamType overrideType = param.type;
104 if (param.type == ParamType::TvoidConstPointer)
105 {
106 overrideType = ParamType::TGLubyteConstPointer;
107 }
108 if (overrideType == ParamType::TGLcharPointer || overrideType == ParamType::TcharConstPointer)
109 {
110 // Inline if data is of type string
111 std::string paramTypeString = ParamTypeToString(param.type);
112 header << paramTypeString.substr(0, paramTypeString.length() - 1) << varName << "[] = { ";
113 WriteInlineData<GLchar>(data, header);
114 header << " };\n";
115 out << varName;
116 }
117 else
118 {
119 // Store in binary file if data are not of type string
120 // Round up to 16-byte boundary for cross ABI safety
121 size_t offset = rx::roundUpPow2(binaryData->size(), kBinaryAlignment);
122 binaryData->resize(offset + data.size());
123 memcpy(binaryData->data() + offset, data.data(), data.size());
124 out << "(" << ParamTypeToString(overrideType) << ")&gBinaryData[" << offset << "]";
125 }
126 }
127
WriteStringPointerParamReplay(ReplayWriter & replayWriter,std::ostream & out,std::ostream & header,const CallCapture & call,const ParamCapture & param)128 void WriteStringPointerParamReplay(ReplayWriter &replayWriter,
129 std::ostream &out,
130 std::ostream &header,
131 const CallCapture &call,
132 const ParamCapture ¶m)
133 {
134 // Concatenate the strings to ensure we get an accurate counter
135 std::vector<std::string> strings;
136 for (const std::vector<uint8_t> &data : param.data)
137 {
138 // null terminate C style string
139 ASSERT(data.size() > 0 && data.back() == '\0');
140 strings.emplace_back(data.begin(), data.end() - 1);
141 }
142
143 bool isNewEntry = false;
144 std::string varName = replayWriter.getInlineStringSetVariableName(call.entryPoint, param.name,
145 strings, &isNewEntry);
146
147 if (isNewEntry)
148 {
149 header << "const char *" << (replayWriter.captureAPI == CaptureAPI::CL ? " " : "const ")
150 << varName << "[] = { \n";
151
152 for (const std::string &str : strings)
153 {
154 // Break up long strings for MSVC
155 size_t copyLength = 0;
156 std::string separator;
157 for (size_t i = 0; i < str.length(); i += kStringLengthLimit)
158 {
159 if ((str.length() - i) <= kStringLengthLimit)
160 {
161 copyLength = str.length() - i;
162 separator = ",";
163 }
164 else
165 {
166 copyLength = kStringLengthLimit;
167 separator = "";
168 }
169
170 header << FmtMultiLineString(str.substr(i, copyLength)) << separator << "\n";
171 }
172 }
173
174 header << "};\n";
175 }
176
177 out << varName;
178 }
179
WriteComment(std::ostream & out,const CallCapture & call)180 void WriteComment(std::ostream &out, const CallCapture &call)
181 {
182 // Read the string parameter
183 const ParamCapture &stringParam =
184 call.params.getParam("comment", ParamType::TGLcharConstPointer, 0);
185 const std::vector<uint8_t> &data = stringParam.data[0];
186 ASSERT(data.size() > 0 && data.back() == '\0');
187 std::string str(data.begin(), data.end() - 1);
188
189 // Write the string prefixed with single line comment
190 out << "// " << str;
191 }
192
EscapeString(const std::string & string)193 std::string EscapeString(const std::string &string)
194 {
195 std::stringstream strstr;
196
197 for (char c : string)
198 {
199 if (c == '\"' || c == '\\')
200 {
201 strstr << "\\";
202 }
203 strstr << c;
204 }
205
206 return strstr.str();
207 }
208
operator <<(std::ostream & os,gl::ContextID contextId)209 std::ostream &operator<<(std::ostream &os, gl::ContextID contextId)
210 {
211 os << static_cast<int>(contextId.value);
212 return os;
213 }
214
operator <<(std::ostream & os,const FmtCapturePrefix & fmt)215 std::ostream &operator<<(std::ostream &os, const FmtCapturePrefix &fmt)
216 {
217 if (fmt.captureLabel.empty())
218 {
219 os << "angle_capture";
220 }
221 else
222 {
223 os << fmt.captureLabel;
224 }
225
226 if (fmt.contextId == kSharedContextId)
227 {
228 os << "_shared";
229 }
230
231 return os;
232 }
233
operator <<(std::ostream & os,FuncUsage usage)234 std::ostream &operator<<(std::ostream &os, FuncUsage usage)
235 {
236 os << "(";
237 if (usage != FuncUsage::Call)
238 {
239 os << "void";
240 }
241 os << ")";
242 return os;
243 }
244
operator <<(std::ostream & os,const FmtReplayFunction & fmt)245 std::ostream &operator<<(std::ostream &os, const FmtReplayFunction &fmt)
246 {
247 os << "Replay";
248
249 if (fmt.contextId == kSharedContextId)
250 {
251 os << "Shared";
252 }
253
254 os << "Frame" << fmt.frameIndex;
255
256 if (fmt.partId != kNoPartId)
257 {
258 os << "Part" << fmt.partId;
259 }
260 os << fmt.usage;
261 return os;
262 }
263
operator <<(std::ostream & os,const FmtSetupFunction & fmt)264 std::ostream &operator<<(std::ostream &os, const FmtSetupFunction &fmt)
265 {
266 os << "SetupReplay";
267
268 if (fmt.contextId != kNoContextId)
269 {
270 os << "Context";
271 }
272
273 if (fmt.contextId == kSharedContextId)
274 {
275 os << "Shared";
276 }
277 else
278 {
279 os << fmt.contextId;
280 }
281
282 if (fmt.partId != kNoPartId)
283 {
284 os << "Part" << fmt.partId;
285 }
286 os << fmt.usage;
287 return os;
288 }
289
operator <<(std::ostream & os,const FmtSetupFirstFrameFunction & fmt)290 std::ostream &operator<<(std::ostream &os, const FmtSetupFirstFrameFunction &fmt)
291 {
292 os << "SetupFirstFrame()";
293 return os;
294 }
295
operator <<(std::ostream & os,const FmtSetupInactiveFunction & fmt)296 std::ostream &operator<<(std::ostream &os, const FmtSetupInactiveFunction &fmt)
297 {
298 if ((fmt.usage == FuncUsage::Call) && (fmt.partId == kNoPartId))
299 {
300 os << "if (gReplayResourceMode == angle::ReplayResourceMode::All)\n {\n ";
301 }
302 os << "SetupReplay";
303
304 if (fmt.contextId != kNoContextId)
305 {
306 os << "Context";
307 }
308
309 if (fmt.contextId == kSharedContextId)
310 {
311 os << "Shared";
312 }
313 else
314 {
315 os << fmt.contextId;
316 }
317
318 os << "Inactive";
319
320 if (fmt.partId != kNoPartId)
321 {
322 os << "Part" << fmt.partId;
323 }
324
325 os << fmt.usage;
326
327 if ((fmt.usage == FuncUsage::Call) && (fmt.partId == kNoPartId))
328 {
329 os << ";\n }";
330 }
331 return os;
332 }
333
operator <<(std::ostream & os,const FmtResetFunction & fmt)334 std::ostream &operator<<(std::ostream &os, const FmtResetFunction &fmt)
335 {
336 os << "ResetReplayContext";
337
338 if (fmt.contextId == kSharedContextId)
339 {
340 os << "Shared";
341 }
342 else
343 {
344 os << fmt.contextId;
345 }
346
347 if (fmt.partId != kNoPartId)
348 {
349 os << "Part" << fmt.partId;
350 }
351 os << fmt.usage;
352 return os;
353 }
354
operator <<(std::ostream & os,const FmtFunction & fmt)355 std::ostream &operator<<(std::ostream &os, const FmtFunction &fmt)
356 {
357 switch (fmt.funcType)
358 {
359 case ReplayFunc::Replay:
360 os << FmtReplayFunction(fmt.contextId, fmt.usage, fmt.frameIndex, fmt.partId);
361 break;
362
363 case ReplayFunc::Setup:
364 os << FmtSetupFunction(fmt.partId, fmt.contextId, fmt.usage);
365 break;
366
367 case ReplayFunc::SetupInactive:
368 os << FmtSetupInactiveFunction(fmt.partId, fmt.contextId, fmt.usage);
369 break;
370
371 case ReplayFunc::Reset:
372 os << FmtResetFunction(fmt.partId, fmt.contextId, fmt.usage);
373 break;
374
375 case ReplayFunc::SetupFirstFrame:
376 os << FmtSetupFirstFrameFunction(fmt.partId);
377 break;
378
379 default:
380 UNREACHABLE();
381 break;
382 }
383
384 return os;
385 }
386
operator <<(std::ostream & ostr,const FmtMultiLineString & fmt)387 std::ostream &operator<<(std::ostream &ostr, const FmtMultiLineString &fmt)
388 {
389 ASSERT(!fmt.strings.empty());
390 bool first = true;
391 for (const std::string &string : fmt.strings)
392 {
393 if (first)
394 {
395 first = false;
396 }
397 else
398 {
399 ostr << "\\n\"\n";
400 }
401
402 ostr << "\"" << EscapeString(string);
403 }
404
405 ostr << "\"";
406
407 return ostr;
408 }
409
GetDefaultOutDirectory()410 std::string GetDefaultOutDirectory()
411 {
412 #if defined(ANGLE_PLATFORM_ANDROID)
413 std::string path = "/sdcard/Android/data/";
414
415 // Linux interface to get application id of the running process
416 FILE *cmdline = fopen("/proc/self/cmdline", "r");
417 char applicationId[512];
418 if (cmdline)
419 {
420 fread(applicationId, 1, sizeof(applicationId), cmdline);
421 fclose(cmdline);
422
423 // Some package may have application id as <app_name>:<cmd_name>
424 char *colonSep = strchr(applicationId, ':');
425 if (colonSep)
426 {
427 *colonSep = '\0';
428 }
429 }
430 else
431 {
432 ERR() << "not able to lookup application id";
433 }
434
435 constexpr char kAndroidOutputSubdir[] = "/angle_capture/";
436 path += std::string(applicationId) + kAndroidOutputSubdir;
437
438 // Check for existence of output path
439 struct stat dir_stat;
440 if (stat(path.c_str(), &dir_stat) == -1)
441 {
442 ERR() << "Output directory '" << path
443 << "' does not exist. Create it over adb using mkdir.";
444 }
445
446 return path;
447 #else
448 return std::string("./");
449 #endif // defined(ANGLE_PLATFORM_ANDROID)
450 }
451
452 FrameCapture::FrameCapture() = default;
453 FrameCapture::~FrameCapture() = default;
454
reset()455 void FrameCapture::reset()
456 {
457 mSetupCalls.clear();
458 }
459
FrameCaptureShared()460 FrameCaptureShared::FrameCaptureShared()
461 : mEnabled(true),
462 mSerializeStateEnabled(false),
463 mCompression(true),
464 mClientVertexArrayMap{},
465 mFrameIndex(1),
466 mCaptureStartFrame(1),
467 mCaptureEndFrame(0),
468 mClientArraySizes{},
469 mReadBufferSize(0),
470 mResourceIDBufferSize(0),
471 mHasResourceType{},
472 mResourceIDToSetupCalls{},
473 mMaxAccessedResourceIDs{},
474 mCaptureTrigger(0),
475 mCaptureActive(false),
476 mWindowSurfaceContextID({0})
477 {
478 reset();
479
480 std::string enabledFromEnv =
481 GetEnvironmentVarOrUnCachedAndroidProperty(kEnabledVarName, kAndroidEnabled);
482 if (enabledFromEnv == "0")
483 {
484 mEnabled = false;
485 }
486
487 std::string startFromEnv =
488 GetEnvironmentVarOrUnCachedAndroidProperty(kFrameStartVarName, kAndroidFrameStart);
489 if (!startFromEnv.empty())
490 {
491 mCaptureStartFrame = atoi(startFromEnv.c_str());
492 }
493 if (mCaptureStartFrame < 1)
494 {
495 WARN() << "Cannot use a capture start frame less than 1.";
496 mCaptureStartFrame = 1;
497 }
498
499 std::string endFromEnv =
500 GetEnvironmentVarOrUnCachedAndroidProperty(kFrameEndVarName, kAndroidFrameEnd);
501 if (!endFromEnv.empty())
502 {
503 mCaptureEndFrame = atoi(endFromEnv.c_str());
504 }
505
506 std::string captureTriggerFromEnv =
507 GetEnvironmentVarOrUnCachedAndroidProperty(kTriggerVarName, kAndroidTrigger);
508 if (!captureTriggerFromEnv.empty())
509 {
510 mCaptureTrigger = atoi(captureTriggerFromEnv.c_str());
511
512 // Using capture trigger, initialize frame range variables for MEC
513 resetCaptureStartEndFrames();
514 }
515
516 std::string labelFromEnv =
517 GetEnvironmentVarOrUnCachedAndroidProperty(kCaptureLabelVarName, kAndroidCaptureLabel);
518 // --angle-per-test-capture-label sets the env var, not properties
519 if (labelFromEnv.empty())
520 {
521 labelFromEnv = GetEnvironmentVar(kCaptureLabelVarName);
522 }
523 if (!labelFromEnv.empty())
524 {
525 // Optional label to provide unique file names and namespaces
526 mCaptureLabel = labelFromEnv;
527 }
528
529 std::string compressionFromEnv =
530 GetEnvironmentVarOrUnCachedAndroidProperty(kCompressionVarName, kAndroidCompression);
531 if (compressionFromEnv == "0")
532 {
533 mCompression = false;
534 }
535 std::string serializeStateFromEnv = angle::GetEnvironmentVar(kSerializeStateVarName);
536 if (serializeStateFromEnv == "1")
537 {
538 mSerializeStateEnabled = true;
539 }
540
541 std::string validateSerialiedStateFromEnv =
542 GetEnvironmentVarOrUnCachedAndroidProperty(kValidationVarName, kAndroidValidation);
543 if (validateSerialiedStateFromEnv == "1")
544 {
545 mValidateSerializedState = true;
546 }
547
548 mValidationExpression =
549 GetEnvironmentVarOrUnCachedAndroidProperty(kValidationExprVarName, kAndroidValidationExpr);
550
551 if (!mValidationExpression.empty())
552 {
553 INFO() << "Validation expression is " << kValidationExprVarName;
554 }
555
556 // TODO: Remove. http://anglebug.com/42266223
557 std::string sourceExtFromEnv =
558 GetEnvironmentVarOrUnCachedAndroidProperty(kSourceExtVarName, kAndroidSourceExt);
559 if (!sourceExtFromEnv.empty())
560 {
561 if (sourceExtFromEnv == "c" || sourceExtFromEnv == "cpp")
562 {
563 mReplayWriter.setSourceFileExtension(sourceExtFromEnv.c_str());
564 }
565 else
566 {
567 WARN() << "Invalid capture source extension: " << sourceExtFromEnv;
568 }
569 }
570
571 std::string sourceSizeFromEnv =
572 GetEnvironmentVarOrUnCachedAndroidProperty(kSourceSizeVarName, kAndroidSourceSize);
573 if (!sourceSizeFromEnv.empty())
574 {
575 int sourceSize = atoi(sourceSizeFromEnv.c_str());
576 if (sourceSize < 0)
577 {
578 WARN() << "Invalid capture source size: " << sourceSize;
579 }
580 else
581 {
582 mReplayWriter.setSourceFileSizeThreshold(sourceSize);
583 }
584 }
585
586 std::string forceShadowFromEnv =
587 GetEnvironmentVarOrUnCachedAndroidProperty(kForceShadowVarName, kAndroidForceShadow);
588 if (forceShadowFromEnv == "1")
589 {
590 INFO() << "Force enabling shadow memory for coherent buffer tracking.";
591 mCoherentBufferTracker.enableShadowMemory();
592 }
593
594 if (mFrameIndex == mCaptureStartFrame)
595 {
596 // Capture is starting from the first frame, so set the capture active to ensure all GLES
597 // commands issued are handled correctly by maybeCapturePreCallUpdates() and
598 // maybeCapturePostCallUpdates().
599 setCaptureActive();
600 }
601
602 if (mCaptureEndFrame < mCaptureStartFrame)
603 {
604 // If we're still in a situation where start frame is after end frame,
605 // capture cannot happen. Consider this a disabled state.
606 // Note: We won't get here if trigger is in use, as it sets them equal but huge.
607 mEnabled = false;
608 }
609
610 // Special case the output directory
611 if (mEnabled)
612 {
613 // Only perform output directory checks if enabled
614 // - This can avoid some expensive process name and filesystem checks
615 // - We want to emit errors if the directory doesn't exist
616 getOutputDirectory();
617 }
618
619 mMaxCLParamsSize[ParamType::Tcl_device_idPointer] = 0;
620 mMaxCLParamsSize[ParamType::Tcl_context] = 0;
621 mMaxCLParamsSize[ParamType::Tcl_platform_idPointer] = 0;
622 mMaxCLParamsSize[ParamType::Tcl_command_queue] = 0;
623 mMaxCLParamsSize[ParamType::Tcl_program] = 0;
624 mMaxCLParamsSize[ParamType::Tcl_kernel] = 0;
625 mMaxCLParamsSize[ParamType::Tcl_mem] = 0;
626 mMaxCLParamsSize[ParamType::Tcl_eventPointer] = 0;
627 mMaxCLParamsSize[ParamType::Tcl_sampler] = 0;
628 mMaxCLParamsSize[ParamType::TvoidPointer] = 0;
629 }
630
~FrameCaptureShared()631 FrameCaptureShared::~FrameCaptureShared() {}
632
isCapturing() const633 bool FrameCaptureShared::isCapturing() const
634 {
635 // Currently we will always do a capture up until the last frame. In the future we could improve
636 // mid execution capture by only capturing between the start and end frames. The only necessary
637 // reason we need to capture before the start is for attached program and shader sources.
638 return mEnabled;
639 }
640
getFrameCount() const641 uint32_t FrameCaptureShared::getFrameCount() const
642 {
643 return mCaptureEndFrame - mCaptureStartFrame + 1;
644 }
645
getReplayFrameIndex() const646 uint32_t FrameCaptureShared::getReplayFrameIndex() const
647 {
648 return mFrameIndex - mCaptureStartFrame + 1;
649 }
650
isRuntimeEnabled()651 bool FrameCaptureShared::isRuntimeEnabled()
652 {
653 if (!mRuntimeEnabled && mRuntimeInitialized)
654 {
655 return false;
656 }
657 if (mRuntimeEnabled)
658 {
659 return true;
660 }
661
662 uint32_t mCaptureStartFrame = 1;
663 uint32_t mCaptureEndFrame = 0;
664 std::string enabledFromEnv =
665 GetEnvironmentVarOrUnCachedAndroidProperty(kEnabledVarName, kAndroidEnabled);
666
667 std::string startFromEnv =
668 GetEnvironmentVarOrUnCachedAndroidProperty(kFrameStartVarName, kAndroidFrameStart);
669 if (!startFromEnv.empty())
670 {
671 mCaptureStartFrame = atoi(startFromEnv.c_str());
672 }
673 if (mCaptureStartFrame < 1)
674 {
675 mCaptureStartFrame = 1;
676 }
677
678 std::string endFromEnv =
679 GetEnvironmentVarOrUnCachedAndroidProperty(kFrameEndVarName, kAndroidFrameEnd);
680 if (!endFromEnv.empty())
681 {
682 mCaptureEndFrame = atoi(endFromEnv.c_str());
683 }
684
685 uint32_t mCaptureTrigger = 0;
686 std::string captureTriggerFromEnv =
687 GetEnvironmentVarOrUnCachedAndroidProperty(kTriggerVarName, kAndroidTrigger);
688 if (!captureTriggerFromEnv.empty())
689 {
690 mCaptureTrigger = atoi(captureTriggerFromEnv.c_str());
691 }
692
693 mRuntimeEnabled =
694 enabledFromEnv != "0" &&
695 (mCaptureTrigger || (mCaptureEndFrame != 0 && mCaptureEndFrame >= mCaptureStartFrame));
696
697 mRuntimeInitialized = true;
698 return mRuntimeEnabled;
699 }
700
reset()701 void FrameCaptureShared::reset()
702 {
703 mFrameCalls.clear();
704 mClientVertexArrayMap.fill(-1);
705
706 // Do not reset replay-specific settings like the maximum read buffer size, client array sizes,
707 // or the 'has seen' type map. We could refine this into per-frame and per-capture maximums if
708 // necessary.
709 }
710
711 // This function will clear FrameCaptureShared state so that mid-execution capture can be
712 // run multiple times.
resetMidExecutionCapture(gl::Context * context)713 void FrameCaptureShared::resetMidExecutionCapture(gl::Context *context)
714 {
715 for (ResourceIDType resourceID : AllEnums<ResourceIDType>())
716 {
717 mResourceIDToSetupCalls[resourceID].clear();
718 }
719
720 egl::ShareGroup *shareGroup = context->getShareGroup();
721 for (auto shareContext : shareGroup->getContexts())
722 {
723 FrameCapture *frameCapture = shareContext.second->getFrameCapture();
724 frameCapture->reset();
725 frameCapture->getStateResetHelper().reset();
726 }
727
728 mActiveFrameIndices.clear();
729 mWroteIndexFile = false;
730 std::fill(std::begin(mClientArraySizes), std::end(mClientArraySizes), 0);
731 mReadBufferSize = 0;
732 mResourceIDBufferSize = 0;
733 mHasResourceType.Zero();
734 mBufferDataMap.clear();
735 mMaxAccessedResourceIDs.fill(0);
736 mResourceTracker.resetResourceTracking();
737 mReplayWriter.reset();
738 mShareGroupSetupCalls.clear();
739 mDeferredLinkPrograms.clear();
740 mActiveContexts.clear();
741 }
742
743 // ReplayWriter implementation.
ReplayWriter()744 ReplayWriter::ReplayWriter()
745 : mSourceFileExtension(kDefaultSourceFileExt),
746 mSourceFileSizeThreshold(kDefaultSourceFileSizeThreshold),
747 mFrameIndex(1)
748 {}
749
~ReplayWriter()750 ReplayWriter::~ReplayWriter()
751 {
752 ASSERT(mPrivateFunctionPrototypes.empty());
753 ASSERT(mPublicFunctionPrototypes.empty());
754 ASSERT(mPrivateFunctions.empty());
755 ASSERT(mPublicFunctions.empty());
756 ASSERT(mGlobalVariableDeclarations.empty());
757 ASSERT(mStaticVariableDeclarations.empty());
758 ASSERT(mReplayHeaders.empty());
759 }
760
setSourceFileExtension(const char * ext)761 void ReplayWriter::setSourceFileExtension(const char *ext)
762 {
763 mSourceFileExtension = ext;
764 }
765
setSourceFileSizeThreshold(size_t sourceFileSizeThreshold)766 void ReplayWriter::setSourceFileSizeThreshold(size_t sourceFileSizeThreshold)
767 {
768 mSourceFileSizeThreshold = sourceFileSizeThreshold;
769 }
770
setFilenamePattern(const std::string & pattern)771 void ReplayWriter::setFilenamePattern(const std::string &pattern)
772 {
773 if (mFilenamePattern != pattern)
774 {
775 mFilenamePattern = pattern;
776 }
777 }
778
setSourcePrologue(const std::string & prologue)779 void ReplayWriter::setSourcePrologue(const std::string &prologue)
780 {
781 mSourcePrologue = prologue;
782 }
783
setHeaderPrologue(const std::string & prologue)784 void ReplayWriter::setHeaderPrologue(const std::string &prologue)
785 {
786 mHeaderPrologue = prologue;
787 }
788
addPublicFunction(const std::string & functionProto,const std::stringstream & headerStream,const std::stringstream & bodyStream)789 void ReplayWriter::addPublicFunction(const std::string &functionProto,
790 const std::stringstream &headerStream,
791 const std::stringstream &bodyStream)
792 {
793 mPublicFunctionPrototypes.push_back(functionProto);
794
795 std::string header = headerStream.str();
796 std::string body = bodyStream.str();
797
798 if (!header.empty())
799 {
800 mReplayHeaders.emplace_back(header);
801 }
802
803 if (!body.empty())
804 {
805 mPublicFunctions.emplace_back(body);
806 }
807 }
808
addPrivateFunction(const std::string & functionProto,const std::stringstream & headerStream,const std::stringstream & bodyStream)809 void ReplayWriter::addPrivateFunction(const std::string &functionProto,
810 const std::stringstream &headerStream,
811 const std::stringstream &bodyStream)
812 {
813 mPrivateFunctionPrototypes.push_back(functionProto);
814
815 std::string header = headerStream.str();
816 std::string body = bodyStream.str();
817
818 if (!header.empty())
819 {
820 mReplayHeaders.emplace_back(header);
821 }
822
823 if (!body.empty())
824 {
825 mPrivateFunctions.emplace_back(body);
826 }
827 }
828
getInlineVariableName(EntryPoint entryPoint,const std::string & paramName)829 std::string ReplayWriter::getInlineVariableName(EntryPoint entryPoint, const std::string ¶mName)
830 {
831 int counter = mDataTracker.getCounters().getAndIncrement(entryPoint, paramName);
832 return GetVarName(entryPoint, paramName, counter);
833 }
834
getAndIncrement(EntryPoint entryPoint,const std::string & paramName)835 int DataCounters::getAndIncrement(EntryPoint entryPoint, const std::string ¶mName)
836 {
837 Counter counterKey = {entryPoint, paramName};
838 return mData[counterKey]++;
839 }
840
getStringCounter(const std::vector<std::string> & strings)841 int StringCounters::getStringCounter(const std::vector<std::string> &strings)
842 {
843 const auto &id = mStringCounterMap.find(strings);
844 if (id == mStringCounterMap.end())
845 {
846 return kStringsNotFound;
847 }
848 else
849 {
850 return mStringCounterMap[strings];
851 }
852 }
853
setStringCounter(const std::vector<std::string> & strings,int & counter)854 void StringCounters::setStringCounter(const std::vector<std::string> &strings, int &counter)
855 {
856 ASSERT(counter >= 0);
857 mStringCounterMap[strings] = counter;
858 }
859
860 StringCounters::StringCounters() = default;
861
862 StringCounters::~StringCounters() = default;
863
864 DataCounters::DataCounters() = default;
865
866 DataCounters::~DataCounters() = default;
867
868 DataTracker::DataTracker() = default;
869
870 DataTracker::~DataTracker() = default;
871
getInlineStringSetVariableName(EntryPoint entryPoint,const std::string & paramName,const std::vector<std::string> & strings,bool * isNewEntryOut)872 std::string ReplayWriter::getInlineStringSetVariableName(EntryPoint entryPoint,
873 const std::string ¶mName,
874 const std::vector<std::string> &strings,
875 bool *isNewEntryOut)
876 {
877 int counter = mDataTracker.getStringCounters().getStringCounter(strings);
878 *isNewEntryOut = (counter == kStringsNotFound);
879 if (*isNewEntryOut)
880 {
881 // This is a unique set of strings, so set up their declaration and update the counter
882 counter = mDataTracker.getCounters().getAndIncrement(entryPoint, paramName);
883 mDataTracker.getStringCounters().setStringCounter(strings, counter);
884
885 std::string varName = GetVarName(entryPoint, paramName, counter);
886
887 std::stringstream declStream;
888 declStream << "const char *" << (captureAPI == CaptureAPI::CL ? " " : "const ") << varName
889 << "[]";
890 std::string decl = declStream.str();
891
892 mGlobalVariableDeclarations.push_back(decl);
893
894 return varName;
895 }
896 else
897 {
898 return GetVarName(entryPoint, paramName, counter);
899 }
900 }
901
addStaticVariable(const std::string & customVarType,const std::string & customVarName)902 void ReplayWriter::addStaticVariable(const std::string &customVarType,
903 const std::string &customVarName)
904 {
905 std::string decl = customVarType + " " + customVarName;
906 mStaticVariableDeclarations.push_back(decl);
907 }
908
getStoredReplaySourceSize() const909 size_t ReplayWriter::getStoredReplaySourceSize() const
910 {
911 size_t sum = 0;
912 for (const std::string &header : mReplayHeaders)
913 {
914 sum += header.size();
915 }
916 for (const std::string &publicFunc : mPublicFunctions)
917 {
918 sum += publicFunc.size();
919 }
920 for (const std::string &privateFunc : mPrivateFunctions)
921 {
922 sum += privateFunc.size();
923 }
924 return sum;
925 }
926
927 // static
GetVarName(EntryPoint entryPoint,const std::string & paramName,int counter)928 std::string ReplayWriter::GetVarName(EntryPoint entryPoint,
929 const std::string ¶mName,
930 int counter)
931 {
932 std::stringstream strstr;
933 strstr << GetEntryPointName(entryPoint) << "_" << paramName << "_" << counter;
934 return strstr.str();
935 }
936
saveFrame()937 void ReplayWriter::saveFrame()
938 {
939 if (mReplayHeaders.empty() && mPublicFunctions.empty() && mPrivateFunctions.empty())
940 {
941 return;
942 }
943
944 ASSERT(!mSourceFileExtension.empty());
945
946 std::stringstream strstr;
947 strstr << mFilenamePattern << "_" << std::setfill('0') << std::setw(3) << mFrameIndex << "."
948 << mSourceFileExtension;
949
950 std::string frameFilePath = strstr.str();
951
952 if (captureAPI == CaptureAPI::GL)
953 {
954 ++mFrameIndex;
955 }
956
957 writeReplaySource(frameFilePath);
958 }
959
saveFrameIfFull()960 void ReplayWriter::saveFrameIfFull()
961 {
962 if (getStoredReplaySourceSize() < mSourceFileSizeThreshold)
963 {
964 INFO() << "Merging captured frame: " << getStoredReplaySourceSize()
965 << " less than threshold of " << mSourceFileSizeThreshold << " bytes";
966 return;
967 }
968
969 saveFrame();
970 }
971
saveHeader()972 void ReplayWriter::saveHeader()
973 {
974 std::stringstream headerPathStream;
975 headerPathStream << mFilenamePattern << ".h";
976 std::string headerPath = headerPathStream.str();
977
978 SaveFileHelper saveH(headerPath);
979
980 saveH << mHeaderPrologue << "\n";
981
982 saveH << "// Public functions are declared in "
983 << (captureAPI == CaptureAPI::GL ? "trace_fixture.h.\n" : "trace_fixture_cl.h.\n");
984 saveH << "\n";
985 saveH << "// Private Functions\n";
986 saveH << "\n";
987
988 for (const std::string &proto : mPrivateFunctionPrototypes)
989 {
990 saveH << proto << ";\n";
991 }
992
993 saveH << "\n";
994 saveH << "// Global variables\n";
995 saveH << "\n";
996
997 for (const std::string &globalVar : mGlobalVariableDeclarations)
998 {
999 saveH << "extern " << globalVar << ";\n";
1000 }
1001
1002 for (const std::string &staticVar : mStaticVariableDeclarations)
1003 {
1004 saveH << "static " << staticVar << ";\n";
1005 }
1006
1007 mPublicFunctionPrototypes.clear();
1008 mPrivateFunctionPrototypes.clear();
1009 mGlobalVariableDeclarations.clear();
1010 mStaticVariableDeclarations.clear();
1011
1012 addWrittenFile(headerPath);
1013 }
1014
saveIndexFilesAndHeader()1015 void ReplayWriter::saveIndexFilesAndHeader()
1016 {
1017 ASSERT(!mSourceFileExtension.empty());
1018
1019 std::stringstream sourcePathStream;
1020 sourcePathStream << mFilenamePattern << "." << mSourceFileExtension;
1021 std::string sourcePath = sourcePathStream.str();
1022
1023 writeReplaySource(sourcePath);
1024 saveHeader();
1025 }
1026
saveSetupFile()1027 void ReplayWriter::saveSetupFile()
1028 {
1029 ASSERT(!mSourceFileExtension.empty());
1030
1031 std::stringstream strstr;
1032 strstr << mFilenamePattern << "." << mSourceFileExtension;
1033
1034 std::string frameFilePath = strstr.str();
1035
1036 writeReplaySource(frameFilePath);
1037 }
1038
writeReplaySource(const std::string & filename)1039 void ReplayWriter::writeReplaySource(const std::string &filename)
1040 {
1041 SaveFileHelper saveCpp(filename);
1042
1043 saveCpp << mSourcePrologue << "\n";
1044 for (const std::string &header : mReplayHeaders)
1045 {
1046 saveCpp << header << "\n";
1047 }
1048
1049 saveCpp << "// Private Functions\n";
1050 saveCpp << "\n";
1051
1052 for (const std::string &func : mPrivateFunctions)
1053 {
1054 saveCpp << func << "\n";
1055 }
1056
1057 saveCpp << "// Public Functions\n";
1058 saveCpp << "\n";
1059
1060 if (mFilenamePattern == "cpp")
1061 {
1062 saveCpp << "extern \"C\"\n";
1063 saveCpp << "{\n";
1064 }
1065
1066 for (const std::string &func : mPublicFunctions)
1067 {
1068 saveCpp << func << "\n";
1069 }
1070
1071 if (mFilenamePattern == "cpp")
1072 {
1073 saveCpp << "} // extern \"C\"\n";
1074 }
1075
1076 mReplayHeaders.clear();
1077 mPrivateFunctions.clear();
1078 mPublicFunctions.clear();
1079
1080 addWrittenFile(filename);
1081 }
1082
GetBaseName(const std::string & nameWithPath)1083 std::string GetBaseName(const std::string &nameWithPath)
1084 {
1085 std::vector<std::string> result = angle::SplitString(
1086 nameWithPath, "/\\", WhitespaceHandling::TRIM_WHITESPACE, SplitResult::SPLIT_WANT_NONEMPTY);
1087 ASSERT(!result.empty());
1088 return result.back();
1089 }
1090
addWrittenFile(const std::string & filename)1091 void ReplayWriter::addWrittenFile(const std::string &filename)
1092 {
1093 std::string writtenFile = GetBaseName(filename);
1094 ASSERT(std::find(mWrittenFiles.begin(), mWrittenFiles.end(), writtenFile) ==
1095 mWrittenFiles.end());
1096 mWrittenFiles.push_back(writtenFile);
1097 }
1098
getAndResetWrittenFiles()1099 std::vector<std::string> ReplayWriter::getAndResetWrittenFiles()
1100 {
1101 std::vector<std::string> results = std::move(mWrittenFiles);
1102 std::sort(results.begin(), results.end());
1103 ASSERT(mWrittenFiles.empty());
1104 return results;
1105 }
1106
AddComment(std::vector<CallCapture> * outCalls,const std::string & comment)1107 void AddComment(std::vector<CallCapture> *outCalls, const std::string &comment)
1108 {
1109 ParamBuffer commentParamBuffer;
1110 ParamCapture commentParam("comment", ParamType::TGLcharConstPointer);
1111 CaptureString(comment.c_str(), &commentParam);
1112 commentParamBuffer.addParam(std::move(commentParam));
1113 outCalls->emplace_back("Comment", std::move(commentParamBuffer));
1114 }
1115
1116 bool FrameCaptureShared::mRuntimeEnabled = false;
1117 bool FrameCaptureShared::mRuntimeInitialized = false;
1118
getOutputDirectory()1119 void FrameCaptureShared::getOutputDirectory()
1120 {
1121 std::string pathFromEnv =
1122 GetEnvironmentVarOrUnCachedAndroidProperty(kOutDirectoryVarName, kAndroidOutDir);
1123 if (pathFromEnv.empty())
1124 {
1125 mOutDirectory = GetDefaultOutDirectory();
1126 }
1127 else
1128 {
1129 mOutDirectory = pathFromEnv;
1130 }
1131
1132 // Ensure the capture path ends with a slash.
1133 if (mOutDirectory.back() != '\\' && mOutDirectory.back() != '/')
1134 {
1135 mOutDirectory += '/';
1136 }
1137 }
1138
CaptureMemory(const void * source,size_t size,ParamCapture * paramCapture)1139 void CaptureMemory(const void *source, size_t size, ParamCapture *paramCapture)
1140 {
1141 std::vector<uint8_t> data(size);
1142 memcpy(data.data(), source, size);
1143 paramCapture->data.emplace_back(std::move(data));
1144 }
1145
CaptureString(const GLchar * str,ParamCapture * paramCapture)1146 void CaptureString(const GLchar *str, ParamCapture *paramCapture)
1147 {
1148 // include the '\0' suffix
1149 CaptureMemory(str, strlen(str) + 1, paramCapture);
1150 }
1151
1152 TrackedResource::TrackedResource() = default;
1153
1154 TrackedResource::~TrackedResource() = default;
1155
1156 ResourceTracker::ResourceTracker() = default;
1157
1158 ResourceTracker::~ResourceTracker() = default;
1159
1160 StateResetHelper::StateResetHelper() = default;
1161
1162 StateResetHelper::~StateResetHelper() = default;
1163
CoherentBufferTracker()1164 CoherentBufferTracker::CoherentBufferTracker()
1165 : mEnabled(false), mHasBeenReset(false), mShadowMemoryEnabled(false)
1166 {
1167 mPageSize = GetPageSize();
1168 }
1169
~CoherentBufferTracker()1170 CoherentBufferTracker::~CoherentBufferTracker()
1171 {
1172 disable();
1173 }
1174
disable()1175 void CoherentBufferTracker::disable()
1176 {
1177 if (!mEnabled)
1178 {
1179 return;
1180 }
1181
1182 if (mPageFaultHandler->disable())
1183 {
1184 mEnabled = false;
1185 }
1186 else
1187 {
1188 ERR() << "Could not disable page fault handler.";
1189 }
1190
1191 if (mShadowMemoryEnabled && mBuffers.size() > 0)
1192 {
1193 WARN() << "Disabling coherent buffer tracking while leaving shadow memory without "
1194 "synchronization. Expect rendering artifacts after capture ends.";
1195 }
1196 }
1197
1198 } // namespace angle
1199