Files
godot-angle-static/src/libANGLE/capture/FrameCapture.cpp
Charlie Lao 1e1c9d9de8 Pack and reduce Program::mProgramInput size
Right now mProgramInput is std::vector<sh::ShaderVariable>. It really
only need a subset of ShaderVariable struct. This CL adds a ProgramInput
struct so that we can add data members that actually required. This CL
also makes bools into bitfield and some variables to uint16_t to further
compact the size. This CL also groups the data memebers other than
string to basicDataTypeStruct which only contains basic data types and
the entire struct is memcpied during program binary load and save. This
not just reduces number of memcpy calls, but also improves reliability
so that when someone adds a new member into the struct, it will
automatically load/save correctly.

Bug: b/275102061
Change-Id: Ic055c986453ed46e56057a0122c9926245fef4d1
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4776267
Reviewed-by: Roman Lavrov <romanl@google.com>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Charlie Lao <cclao@google.com>
2023-08-17 18:27:03 +00:00

9797 lines
375 KiB
C++

//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// FrameCapture.cpp:
// ANGLE Frame capture implementation.
//
#include "libANGLE/capture/FrameCapture.h"
#include <cerrno>
#include <cstring>
#include <fstream>
#include <string>
#include "sys/stat.h"
#include "common/aligned_memory.h"
#include "common/angle_version_info.h"
#include "common/frame_capture_utils.h"
#include "common/gl_enum_utils.h"
#include "common/mathutil.h"
#include "common/serializer/JsonSerializer.h"
#include "common/string_utils.h"
#include "common/system_utils.h"
#include "gpu_info_util/SystemInfo.h"
#include "image_util/storeimage.h"
#include "libANGLE/Config.h"
#include "libANGLE/Context.h"
#include "libANGLE/Context.inl.h"
#include "libANGLE/Display.h"
#include "libANGLE/EGLSync.h"
#include "libANGLE/Fence.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/GLES1Renderer.h"
#include "libANGLE/Query.h"
#include "libANGLE/ResourceMap.h"
#include "libANGLE/Shader.h"
#include "libANGLE/Surface.h"
#include "libANGLE/VertexArray.h"
#include "libANGLE/capture/capture_egl_autogen.h"
#include "libANGLE/capture/capture_gles_1_0_autogen.h"
#include "libANGLE/capture/capture_gles_2_0_autogen.h"
#include "libANGLE/capture/capture_gles_3_0_autogen.h"
#include "libANGLE/capture/capture_gles_3_1_autogen.h"
#include "libANGLE/capture/capture_gles_3_2_autogen.h"
#include "libANGLE/capture/capture_gles_ext_autogen.h"
#include "libANGLE/capture/serialize.h"
#include "libANGLE/entry_points_utils.h"
#include "libANGLE/queryconversions.h"
#include "libANGLE/queryutils.h"
#include "libANGLE/validationEGL.h"
#include "third_party/ceval/ceval.h"
#define USE_SYSTEM_ZLIB
#include "compression_utils_portable.h"
#if !ANGLE_CAPTURE_ENABLED
# error Frame capture must be enabled to include this file.
#endif // !ANGLE_CAPTURE_ENABLED
namespace angle
{
namespace
{
// TODO: Consolidate to C output and remove option. http://anglebug.com/7753
constexpr char kEnabledVarName[] = "ANGLE_CAPTURE_ENABLED";
constexpr char kOutDirectoryVarName[] = "ANGLE_CAPTURE_OUT_DIR";
constexpr char kFrameStartVarName[] = "ANGLE_CAPTURE_FRAME_START";
constexpr char kFrameEndVarName[] = "ANGLE_CAPTURE_FRAME_END";
constexpr char kTriggerVarName[] = "ANGLE_CAPTURE_TRIGGER";
constexpr char kCaptureLabelVarName[] = "ANGLE_CAPTURE_LABEL";
constexpr char kCompressionVarName[] = "ANGLE_CAPTURE_COMPRESSION";
constexpr char kSerializeStateVarName[] = "ANGLE_CAPTURE_SERIALIZE_STATE";
constexpr char kValidationVarName[] = "ANGLE_CAPTURE_VALIDATION";
constexpr char kValidationExprVarName[] = "ANGLE_CAPTURE_VALIDATION_EXPR";
constexpr char kTrimEnabledVarName[] = "ANGLE_CAPTURE_TRIM_ENABLED";
constexpr char kSourceExtVarName[] = "ANGLE_CAPTURE_SOURCE_EXT";
constexpr char kSourceSizeVarName[] = "ANGLE_CAPTURE_SOURCE_SIZE";
constexpr char kForceShadowVarName[] = "ANGLE_CAPTURE_FORCE_SHADOW";
constexpr size_t kBinaryAlignment = 16;
constexpr size_t kFunctionSizeLimit = 5000;
// Limit based on MSVC Compiler Error C2026
constexpr size_t kStringLengthLimit = 16380;
// Default limit to number of bytes in a capture source files.
constexpr char kDefaultSourceFileExt[] = "cpp";
constexpr size_t kDefaultSourceFileSizeThreshold = 400000;
// Android debug properties that correspond to the above environment variables
constexpr char kAndroidEnabled[] = "debug.angle.capture.enabled";
constexpr char kAndroidOutDir[] = "debug.angle.capture.out_dir";
constexpr char kAndroidFrameStart[] = "debug.angle.capture.frame_start";
constexpr char kAndroidFrameEnd[] = "debug.angle.capture.frame_end";
constexpr char kAndroidTrigger[] = "debug.angle.capture.trigger";
constexpr char kAndroidCaptureLabel[] = "debug.angle.capture.label";
constexpr char kAndroidCompression[] = "debug.angle.capture.compression";
constexpr char kAndroidValidation[] = "debug.angle.capture.validation";
constexpr char kAndroidValidationExpr[] = "debug.angle.capture.validation_expr";
constexpr char kAndroidTrimEnabled[] = "debug.angle.capture.trim_enabled";
constexpr char kAndroidSourceExt[] = "debug.angle.capture.source_ext";
constexpr char kAndroidSourceSize[] = "debug.angle.capture.source_size";
constexpr char kAndroidForceShadow[] = "debug.angle.capture.force_shadow";
struct FramebufferCaptureFuncs
{
FramebufferCaptureFuncs(bool isGLES1)
{
if (isGLES1)
{
// From GL_OES_framebuffer_object
framebufferTexture2D = &gl::CaptureFramebufferTexture2DOES;
framebufferRenderbuffer = &gl::CaptureFramebufferRenderbufferOES;
bindFramebuffer = &gl::CaptureBindFramebufferOES;
genFramebuffers = &gl::CaptureGenFramebuffersOES;
bindRenderbuffer = &gl::CaptureBindRenderbufferOES;
genRenderbuffers = &gl::CaptureGenRenderbuffersOES;
renderbufferStorage = &gl::CaptureRenderbufferStorageOES;
}
else
{
framebufferTexture2D = &gl::CaptureFramebufferTexture2D;
framebufferRenderbuffer = &gl::CaptureFramebufferRenderbuffer;
bindFramebuffer = &gl::CaptureBindFramebuffer;
genFramebuffers = &gl::CaptureGenFramebuffers;
bindRenderbuffer = &gl::CaptureBindRenderbuffer;
genRenderbuffers = &gl::CaptureGenRenderbuffers;
renderbufferStorage = &gl::CaptureRenderbufferStorage;
}
}
decltype(&gl::CaptureFramebufferTexture2D) framebufferTexture2D;
decltype(&gl::CaptureFramebufferRenderbuffer) framebufferRenderbuffer;
decltype(&gl::CaptureBindFramebuffer) bindFramebuffer;
decltype(&gl::CaptureGenFramebuffers) genFramebuffers;
decltype(&gl::CaptureBindRenderbuffer) bindRenderbuffer;
decltype(&gl::CaptureGenRenderbuffers) genRenderbuffers;
decltype(&gl::CaptureRenderbufferStorage) renderbufferStorage;
};
struct VertexArrayCaptureFuncs
{
VertexArrayCaptureFuncs(bool isGLES1)
{
if (isGLES1)
{
// From GL_OES_vertex_array_object
bindVertexArray = &gl::CaptureBindVertexArrayOES;
deleteVertexArrays = &gl::CaptureDeleteVertexArraysOES;
genVertexArrays = &gl::CaptureGenVertexArraysOES;
isVertexArray = &gl::CaptureIsVertexArrayOES;
}
else
{
bindVertexArray = &gl::CaptureBindVertexArray;
deleteVertexArrays = &gl::CaptureDeleteVertexArrays;
genVertexArrays = &gl::CaptureGenVertexArrays;
isVertexArray = &gl::CaptureIsVertexArray;
}
}
decltype(&gl::CaptureBindVertexArray) bindVertexArray;
decltype(&gl::CaptureDeleteVertexArrays) deleteVertexArrays;
decltype(&gl::CaptureGenVertexArrays) genVertexArrays;
decltype(&gl::CaptureIsVertexArray) isVertexArray;
};
std::string GetDefaultOutDirectory()
{
#if defined(ANGLE_PLATFORM_ANDROID)
std::string path = "/sdcard/Android/data/";
// Linux interface to get application id of the running process
FILE *cmdline = fopen("/proc/self/cmdline", "r");
char applicationId[512];
if (cmdline)
{
fread(applicationId, 1, sizeof(applicationId), cmdline);
fclose(cmdline);
// Some package may have application id as <app_name>:<cmd_name>
char *colonSep = strchr(applicationId, ':');
if (colonSep)
{
*colonSep = '\0';
}
}
else
{
ERR() << "not able to lookup application id";
}
constexpr char kAndroidOutputSubdir[] = "/angle_capture/";
path += std::string(applicationId) + kAndroidOutputSubdir;
// Check for existence of output path
struct stat dir_stat;
if (stat(path.c_str(), &dir_stat) == -1)
{
ERR() << "Output directory '" << path
<< "' does not exist. Create it over adb using mkdir.";
}
return path;
#else
return std::string("./");
#endif // defined(ANGLE_PLATFORM_ANDROID)
}
std::string GetCaptureTrigger()
{
// Use the GetAndSet variant to improve future lookup times
return GetAndSetEnvironmentVarOrUnCachedAndroidProperty(kTriggerVarName, kAndroidTrigger);
}
std::ostream &operator<<(std::ostream &os, gl::ContextID contextId)
{
os << static_cast<int>(contextId.value);
return os;
}
// Used to indicate that "shared" should be used to identify the files.
constexpr gl::ContextID kSharedContextId = {0};
// Used to indicate no context ID should be output.
constexpr gl::ContextID kNoContextId = {std::numeric_limits<uint32_t>::max()};
struct FmtCapturePrefix
{
FmtCapturePrefix(gl::ContextID contextIdIn, const std::string &captureLabelIn)
: contextId(contextIdIn), captureLabel(captureLabelIn)
{}
gl::ContextID contextId;
const std::string &captureLabel;
};
std::ostream &operator<<(std::ostream &os, const FmtCapturePrefix &fmt)
{
if (fmt.captureLabel.empty())
{
os << "angle_capture";
}
else
{
os << fmt.captureLabel;
}
if (fmt.contextId == kSharedContextId)
{
os << "_shared";
}
return os;
}
enum class ReplayFunc
{
Replay,
Setup,
Reset,
};
constexpr uint32_t kNoPartId = std::numeric_limits<uint32_t>::max();
// In C, when you declare or define a function that takes no parameters, you must explicitly say the
// function takes "void" parameters. When you're calling the function you omit this void. It's
// therefore necessary to know how we're using a function to know if we should emi the "void".
enum FuncUsage
{
Prototype,
Definition,
Call,
};
std::ostream &operator<<(std::ostream &os, FuncUsage usage)
{
os << "(";
if (usage != FuncUsage::Call)
{
os << "void";
}
os << ")";
return os;
}
struct FmtReplayFunction
{
FmtReplayFunction(gl::ContextID contextIdIn,
FuncUsage usageIn,
uint32_t frameIndexIn,
uint32_t partIdIn = kNoPartId)
: contextId(contextIdIn), usage(usageIn), frameIndex(frameIndexIn), partId(partIdIn)
{}
gl::ContextID contextId;
FuncUsage usage;
uint32_t frameIndex;
uint32_t partId;
};
std::ostream &operator<<(std::ostream &os, const FmtReplayFunction &fmt)
{
os << "Replay";
if (fmt.contextId == kSharedContextId)
{
os << "Shared";
}
os << "Frame" << fmt.frameIndex;
if (fmt.partId != kNoPartId)
{
os << "Part" << fmt.partId;
}
os << fmt.usage;
return os;
}
struct FmtSetupFunction
{
FmtSetupFunction(uint32_t partIdIn, gl::ContextID contextIdIn, FuncUsage usageIn)
: partId(partIdIn), contextId(contextIdIn), usage(usageIn)
{}
uint32_t partId;
gl::ContextID contextId;
FuncUsage usage;
};
std::ostream &operator<<(std::ostream &os, const FmtSetupFunction &fmt)
{
os << "SetupReplayContext";
if (fmt.contextId == kSharedContextId)
{
os << "Shared";
}
else
{
os << fmt.contextId;
}
if (fmt.partId != kNoPartId)
{
os << "Part" << fmt.partId;
}
os << fmt.usage;
return os;
}
struct FmtResetFunction
{
FmtResetFunction(uint32_t partIdIn, gl::ContextID contextIdIn, FuncUsage usageIn)
: partId(partIdIn), contextId(contextIdIn), usage(usageIn)
{}
uint32_t partId;
gl::ContextID contextId;
FuncUsage usage;
};
std::ostream &operator<<(std::ostream &os, const FmtResetFunction &fmt)
{
os << "ResetReplayContext";
if (fmt.contextId == kSharedContextId)
{
os << "Shared";
}
else
{
os << fmt.contextId;
}
if (fmt.partId != kNoPartId)
{
os << "Part" << fmt.partId;
}
os << fmt.usage;
return os;
}
struct FmtFunction
{
FmtFunction(ReplayFunc funcTypeIn,
gl::ContextID contextIdIn,
FuncUsage usageIn,
uint32_t frameIndexIn,
uint32_t partIdIn)
: funcType(funcTypeIn),
contextId(contextIdIn),
usage(usageIn),
frameIndex(frameIndexIn),
partId(partIdIn)
{}
ReplayFunc funcType;
gl::ContextID contextId;
FuncUsage usage;
uint32_t frameIndex;
uint32_t partId;
};
std::ostream &operator<<(std::ostream &os, const FmtFunction &fmt)
{
switch (fmt.funcType)
{
case ReplayFunc::Replay:
os << FmtReplayFunction(fmt.contextId, fmt.usage, fmt.frameIndex, fmt.partId);
break;
case ReplayFunc::Setup:
os << FmtSetupFunction(fmt.partId, fmt.contextId, fmt.usage);
break;
case ReplayFunc::Reset:
os << FmtResetFunction(fmt.partId, fmt.contextId, fmt.usage);
break;
default:
UNREACHABLE();
break;
}
return os;
}
struct FmtGetSerializedContextStateFunction
{
FmtGetSerializedContextStateFunction(gl::ContextID contextIdIn,
FuncUsage usageIn,
uint32_t frameIndexIn)
: contextId(contextIdIn), usage(usageIn), frameIndex(frameIndexIn)
{}
gl::ContextID contextId;
FuncUsage usage;
uint32_t frameIndex;
};
std::ostream &operator<<(std::ostream &os, const FmtGetSerializedContextStateFunction &fmt)
{
os << "GetSerializedContext" << fmt.contextId << "StateFrame" << fmt.frameIndex << "Data"
<< fmt.usage;
return os;
}
void WriteGLFloatValue(std::ostream &out, GLfloat value)
{
// Check for non-representable values
ASSERT(std::numeric_limits<float>::has_infinity);
ASSERT(std::numeric_limits<float>::has_quiet_NaN);
if (std::isinf(value))
{
float negativeInf = -std::numeric_limits<float>::infinity();
if (value == negativeInf)
{
out << "-";
}
out << "INFINITY";
}
else if (std::isnan(value))
{
out << "NAN";
}
else
{
out << std::setprecision(16);
out << value;
}
}
template <typename T, typename CastT = T>
void WriteInlineData(const std::vector<uint8_t> &vec, std::ostream &out)
{
const T *data = reinterpret_cast<const T *>(vec.data());
size_t count = vec.size() / sizeof(T);
if (data == nullptr)
{
return;
}
out << static_cast<CastT>(data[0]);
for (size_t dataIndex = 1; dataIndex < count; ++dataIndex)
{
out << ", " << static_cast<CastT>(data[dataIndex]);
}
}
template <>
void WriteInlineData<GLchar>(const std::vector<uint8_t> &vec, std::ostream &out)
{
const GLchar *data = reinterpret_cast<const GLchar *>(vec.data());
size_t count = vec.size() / sizeof(GLchar);
if (data == nullptr || data[0] == '\0')
{
return;
}
out << "\"";
for (size_t dataIndex = 0; dataIndex < count; ++dataIndex)
{
if (data[dataIndex] == '\0')
break;
out << static_cast<GLchar>(data[dataIndex]);
}
out << "\"";
}
// For compatibility with C, which does not have multi-line string literals, we break strings up
// into multiple lines like:
//
// const char *str[] = {
// "multiple\n"
// "line\n"
// "strings may have \"quotes\"\n"
// "and \\slashes\\\n",
// };
//
// Note we need to emit extra escapes to ensure quotes and other special characters are preserved.
struct FmtMultiLineString
{
FmtMultiLineString(const std::string &str) : strings()
{
std::string str2;
// Strip any carriage returns before splitting, for consistency
if (str.find("\r") != std::string::npos)
{
// str is const, so have to make a copy of it first
str2 = str;
ReplaceAllSubstrings(&str2, "\r", "");
}
strings =
angle::SplitString(str2.empty() ? str : str2, "\n", WhitespaceHandling::KEEP_WHITESPACE,
SplitResult::SPLIT_WANT_ALL);
}
std::vector<std::string> strings;
};
std::string EscapeString(const std::string &string)
{
std::stringstream strstr;
for (char c : string)
{
if (c == '\"' || c == '\\')
{
strstr << "\\";
}
strstr << c;
}
return strstr.str();
}
std::ostream &operator<<(std::ostream &ostr, const FmtMultiLineString &fmt)
{
ASSERT(!fmt.strings.empty());
bool first = true;
for (const std::string &string : fmt.strings)
{
if (first)
{
first = false;
}
else
{
ostr << "\\n\"\n";
}
ostr << "\"" << EscapeString(string);
}
ostr << "\"";
return ostr;
}
void WriteStringParamReplay(ReplayWriter &replayWriter,
std::ostream &out,
std::ostream &header,
const CallCapture &call,
const ParamCapture &param,
std::vector<uint8_t> *binaryData)
{
const std::vector<uint8_t> &data = param.data[0];
// null terminate C style string
ASSERT(data.size() > 0 && data.back() == '\0');
std::string str(data.begin(), data.end() - 1);
constexpr size_t kMaxInlineStringLength = 20000;
if (str.size() > kMaxInlineStringLength)
{
// Store in binary file if the string is too long.
// Round up to 16-byte boundary for cross ABI safety.
size_t offset = rx::roundUpPow2(binaryData->size(), kBinaryAlignment);
binaryData->resize(offset + str.size() + 1);
memcpy(binaryData->data() + offset, str.data(), str.size() + 1);
out << "(const char *)&gBinaryData[" << offset << "]";
}
else if (str.find('\n') != std::string::npos)
{
std::string varName = replayWriter.getInlineVariableName(call.entryPoint, param.name);
header << "const char " << varName << "[] = \n" << FmtMultiLineString(str) << ";";
out << varName;
}
else
{
out << "\"" << str << "\"";
}
}
void WriteStringPointerParamReplay(ReplayWriter &replayWriter,
std::ostream &out,
std::ostream &header,
const CallCapture &call,
const ParamCapture &param)
{
// Concatenate the strings to ensure we get an accurate counter
std::vector<std::string> strings;
for (const std::vector<uint8_t> &data : param.data)
{
// null terminate C style string
ASSERT(data.size() > 0 && data.back() == '\0');
strings.emplace_back(data.begin(), data.end() - 1);
}
bool isNewEntry = false;
std::string varName = replayWriter.getInlineStringSetVariableName(call.entryPoint, param.name,
strings, &isNewEntry);
if (isNewEntry)
{
header << "const char *const " << varName << "[] = { \n";
for (const std::string &str : strings)
{
// Break up long strings for MSVC
size_t copyLength = 0;
std::string separator;
for (size_t i = 0; i < str.length(); i += kStringLengthLimit)
{
if ((str.length() - i) <= kStringLengthLimit)
{
copyLength = str.length() - i;
separator = ",";
}
else
{
copyLength = kStringLengthLimit;
separator = "";
}
header << FmtMultiLineString(str.substr(i, copyLength)) << separator << "\n";
}
}
header << "};\n";
}
out << varName;
}
enum class Indent
{
Indent,
NoIdent,
};
void UpdateResourceIDBuffer(std::ostream &out,
Indent indent,
size_t bufferIndex,
const char *mapName,
GLuint resourceID)
{
if (indent == Indent::Indent)
{
out << " ";
}
out << "UpdateResourceIDBuffer(" << bufferIndex << ", g" << mapName << "Map[" << resourceID
<< "]);\n";
}
template <typename ParamT>
void WriteResourceIDPointerParamReplay(ReplayWriter &replayWriter,
std::ostream &out,
std::ostream &header,
const CallCapture &call,
const ParamCapture &param,
size_t *maxResourceIDBufferSize)
{
const ResourceIDType resourceIDType = GetResourceIDTypeFromParamType(param.type);
ASSERT(resourceIDType != ResourceIDType::InvalidEnum);
const char *name = GetResourceIDTypeName(resourceIDType);
if (param.dataNElements > 0)
{
ASSERT(param.data.size() == 1);
const ParamT *returnedIDs = reinterpret_cast<const ParamT *>(param.data[0].data());
for (GLsizei resIndex = 0; resIndex < param.dataNElements; ++resIndex)
{
ParamT id = returnedIDs[resIndex];
UpdateResourceIDBuffer(header, Indent::NoIdent, resIndex, name, id.value);
}
*maxResourceIDBufferSize = std::max<size_t>(*maxResourceIDBufferSize, param.dataNElements);
}
out << "gResourceIDBuffer";
}
void WriteBinaryParamReplay(ReplayWriter &replayWriter,
std::ostream &out,
std::ostream &header,
const CallCapture &call,
const ParamCapture &param,
std::vector<uint8_t> *binaryData)
{
std::string varName = replayWriter.getInlineVariableName(call.entryPoint, param.name);
ASSERT(param.data.size() == 1);
const std::vector<uint8_t> &data = param.data[0];
// Only inline strings (shaders) to simplify the C code.
ParamType overrideType = param.type;
if (param.type == ParamType::TvoidConstPointer)
{
overrideType = ParamType::TGLubyteConstPointer;
}
if (overrideType == ParamType::TGLcharPointer)
{
// Inline if data is of type string
std::string paramTypeString = ParamTypeToString(param.type);
header << paramTypeString.substr(0, paramTypeString.length() - 1) << varName << "[] = { ";
WriteInlineData<GLchar>(data, header);
header << " };\n";
out << varName;
}
else
{
// Store in binary file if data are not of type string
// Round up to 16-byte boundary for cross ABI safety
size_t offset = rx::roundUpPow2(binaryData->size(), kBinaryAlignment);
binaryData->resize(offset + data.size());
memcpy(binaryData->data() + offset, data.data(), data.size());
out << "(" << ParamTypeToString(overrideType) << ")&gBinaryData[" << offset << "]";
}
}
void WriteCppReplayForCall(const CallCapture &call,
ReplayWriter &replayWriter,
std::ostream &out,
std::ostream &header,
std::vector<uint8_t> *binaryData,
size_t *maxResourceIDBufferSize)
{
std::ostringstream callOut;
callOut << call.name() << "(";
bool first = true;
for (const ParamCapture &param : call.params.getParamCaptures())
{
if (!first)
{
callOut << ", ";
}
if (param.arrayClientPointerIndex != -1 && param.value.voidConstPointerVal != nullptr)
{
callOut << "gClientArrays[" << param.arrayClientPointerIndex << "]";
}
else if (param.readBufferSizeBytes > 0)
{
callOut << "(" << ParamTypeToString(param.type) << ")gReadBuffer";
}
else if (param.data.empty())
{
if (param.type == ParamType::TGLenum)
{
OutputGLenumString(callOut, param.enumGroup, param.value.GLenumVal);
}
else if (param.type == ParamType::TGLbitfield)
{
OutputGLbitfieldString(callOut, param.enumGroup, param.value.GLbitfieldVal);
}
else if (param.type == ParamType::TGLfloat)
{
WriteGLFloatValue(callOut, param.value.GLfloatVal);
}
else if (param.type == ParamType::TGLsync)
{
callOut << "gSyncMap[" << FmtPointerIndex(param.value.GLsyncVal) << "]";
}
else if (param.type == ParamType::TGLuint64 && param.name == "timeout")
{
if (param.value.GLuint64Val == GL_TIMEOUT_IGNORED)
{
callOut << "GL_TIMEOUT_IGNORED";
}
else
{
WriteParamCaptureReplay(callOut, call, param);
}
}
else
{
WriteParamCaptureReplay(callOut, call, param);
}
}
else
{
switch (param.type)
{
case ParamType::TGLcharConstPointer:
WriteStringParamReplay(replayWriter, callOut, header, call, param, binaryData);
break;
case ParamType::TGLcharConstPointerPointer:
WriteStringPointerParamReplay(replayWriter, callOut, header, call, param);
break;
case ParamType::TBufferIDConstPointer:
WriteResourceIDPointerParamReplay<gl::BufferID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TFenceNVIDConstPointer:
WriteResourceIDPointerParamReplay<gl::FenceNVID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TFramebufferIDConstPointer:
WriteResourceIDPointerParamReplay<gl::FramebufferID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TMemoryObjectIDConstPointer:
WriteResourceIDPointerParamReplay<gl::MemoryObjectID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TProgramPipelineIDConstPointer:
WriteResourceIDPointerParamReplay<gl::ProgramPipelineID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TQueryIDConstPointer:
WriteResourceIDPointerParamReplay<gl::QueryID>(replayWriter, callOut, out, call,
param, maxResourceIDBufferSize);
break;
case ParamType::TRenderbufferIDConstPointer:
WriteResourceIDPointerParamReplay<gl::RenderbufferID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TSamplerIDConstPointer:
WriteResourceIDPointerParamReplay<gl::SamplerID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TSemaphoreIDConstPointer:
WriteResourceIDPointerParamReplay<gl::SemaphoreID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TTextureIDConstPointer:
WriteResourceIDPointerParamReplay<gl::TextureID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TTransformFeedbackIDConstPointer:
WriteResourceIDPointerParamReplay<gl::TransformFeedbackID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
case ParamType::TVertexArrayIDConstPointer:
WriteResourceIDPointerParamReplay<gl::VertexArrayID>(
replayWriter, callOut, out, call, param, maxResourceIDBufferSize);
break;
default:
WriteBinaryParamReplay(replayWriter, callOut, header, call, param, binaryData);
break;
}
}
first = false;
}
callOut << ")";
out << callOut.str();
}
size_t MaxClientArraySize(const gl::AttribArray<size_t> &clientArraySizes)
{
size_t found = 0;
for (size_t size : clientArraySizes)
{
if (size > found)
{
found = size;
}
}
return found;
}
std::string GetBinaryDataFilePath(bool compression, const std::string &captureLabel)
{
std::stringstream fnameStream;
fnameStream << FmtCapturePrefix(kNoContextId, captureLabel) << ".angledata";
if (compression)
{
fnameStream << ".gz";
}
return fnameStream.str();
}
void SaveBinaryData(bool compression,
const std::string &outDir,
gl::ContextID contextId,
const std::string &captureLabel,
const std::vector<uint8_t> &binaryData)
{
std::string binaryDataFileName = GetBinaryDataFilePath(compression, captureLabel);
std::string dataFilepath = outDir + binaryDataFileName;
SaveFileHelper saveData(dataFilepath);
if (compression)
{
// Save compressed data.
uLong uncompressedSize = static_cast<uLong>(binaryData.size());
uLong expectedCompressedSize = zlib_internal::GzipExpectedCompressedSize(uncompressedSize);
std::vector<uint8_t> compressedData(expectedCompressedSize, 0);
uLong compressedSize = expectedCompressedSize;
int zResult = zlib_internal::GzipCompressHelper(compressedData.data(), &compressedSize,
binaryData.data(), uncompressedSize,
nullptr, nullptr);
if (zResult != Z_OK)
{
FATAL() << "Error compressing binary data: " << zResult;
}
saveData.write(compressedData.data(), compressedSize);
}
else
{
saveData.write(binaryData.data(), binaryData.size());
}
}
void WriteInitReplayCall(bool compression,
std::ostream &out,
gl::ContextID contextID,
const std::string &captureLabel,
size_t maxClientArraySize,
size_t readBufferSize,
size_t resourceIDBufferSize,
const PackedEnumMap<ResourceIDType, uint32_t> &maxIDs)
{
std::string binaryDataFileName = GetBinaryDataFilePath(compression, captureLabel);
out << " // binaryDataFileName = " << binaryDataFileName << "\n";
out << " // maxClientArraySize = " << maxClientArraySize << "\n";
out << " // maxClientArraySize = " << maxClientArraySize << "\n";
out << " // readBufferSize = " << readBufferSize << "\n";
out << " // resourceIDBufferSize = " << resourceIDBufferSize << "\n";
out << " // contextID = " << contextID << "\n";
for (ResourceIDType resourceID : AllEnums<ResourceIDType>())
{
const char *name = GetResourceIDTypeName(resourceID);
out << " // max" << name << " = " << maxIDs[resourceID] << "\n";
}
out << " InitializeReplay4(\"" << binaryDataFileName << "\", " << maxClientArraySize << ", "
<< readBufferSize << ", " << resourceIDBufferSize << ", " << contextID;
for (ResourceIDType resourceID : AllEnums<ResourceIDType>())
{
out << ", " << maxIDs[resourceID];
}
out << ");\n";
}
void DeleteResourcesInReset(std::stringstream &out,
const ResourceSet &newResources,
const ResourceSet &resourcesToDelete,
const char *resourceName,
size_t *maxResourceIDBufferSize)
{
if (!newResources.empty() || !resourcesToDelete.empty())
{
size_t count = 0;
for (GLuint oldResource : resourcesToDelete)
{
UpdateResourceIDBuffer(out, Indent::Indent, count++, resourceName, oldResource);
}
for (GLuint newResource : newResources)
{
UpdateResourceIDBuffer(out, Indent::Indent, count++, resourceName, newResource);
}
// Delete all the new and old buffers at once
out << " glDelete" << resourceName << "s(" << count << ", gResourceIDBuffer);\n";
*maxResourceIDBufferSize = std::max(*maxResourceIDBufferSize, count);
}
}
// TODO (http://anglebug.com/4599): Reset more state on frame loop
void MaybeResetResources(gl::ContextID contextID,
ResourceIDType resourceIDType,
ReplayWriter &replayWriter,
std::stringstream &out,
std::stringstream &header,
ResourceTracker *resourceTracker,
std::vector<uint8_t> *binaryData,
bool &anyResourceReset,
size_t *maxResourceIDBufferSize)
{
// Track the initial output position so we can detect if it has moved
std::streampos initialOutPos = out.tellp();
switch (resourceIDType)
{
case ResourceIDType::Buffer:
{
TrackedResource &trackedBuffers =
resourceTracker->getTrackedResource(contextID, ResourceIDType::Buffer);
ResourceSet &newBuffers = trackedBuffers.getNewResources();
ResourceSet &buffersToDelete = trackedBuffers.getResourcesToDelete();
ResourceSet &buffersToRegen = trackedBuffers.getResourcesToRegen();
ResourceCalls &bufferRegenCalls = trackedBuffers.getResourceRegenCalls();
ResourceCalls &bufferRestoreCalls = trackedBuffers.getResourceRestoreCalls();
BufferCalls &bufferMapCalls = resourceTracker->getBufferMapCalls();
BufferCalls &bufferUnmapCalls = resourceTracker->getBufferUnmapCalls();
DeleteResourcesInReset(out, newBuffers, buffersToDelete, "Buffer",
maxResourceIDBufferSize);
// If any of our starting buffers were deleted during the run, recreate them
for (GLuint id : buffersToRegen)
{
// Emit their regen calls
for (CallCapture &call : bufferRegenCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
// If any of our starting buffers were modified during the run, restore their contents
ResourceSet &buffersToRestore = trackedBuffers.getResourcesToRestore();
for (GLuint id : buffersToRestore)
{
if (resourceTracker->getStartingBuffersMappedCurrent(id))
{
// Some drivers require the buffer to be unmapped before you can update data,
// which violates the spec. See gl::Buffer::bufferDataImpl().
for (CallCapture &call : bufferUnmapCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
// Emit their restore calls
for (CallCapture &call : bufferRestoreCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
// Also note that this buffer has been implicitly unmapped by this call
resourceTracker->setBufferUnmapped(contextID, id);
}
}
// Update the map/unmap of buffers to match the starting state
ResourceSet startingBuffers = trackedBuffers.getStartingResources();
for (GLuint id : startingBuffers)
{
// If the buffer was mapped at the start, but is not mapped now, we need to map
if (resourceTracker->getStartingBuffersMappedInitial(id) &&
!resourceTracker->getStartingBuffersMappedCurrent(id))
{
// Emit their map calls
for (CallCapture &call : bufferMapCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
// If the buffer was unmapped at the start, but is mapped now, we need to unmap
if (!resourceTracker->getStartingBuffersMappedInitial(id) &&
resourceTracker->getStartingBuffersMappedCurrent(id))
{
// Emit their unmap calls
for (CallCapture &call : bufferUnmapCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
}
// Restore buffer bindings as seen during MEC
std::vector<CallCapture> &bufferBindingCalls = resourceTracker->getBufferBindingCalls();
for (CallCapture &call : bufferBindingCalls)
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
break;
}
case ResourceIDType::Framebuffer:
{
TrackedResource &trackedFramebuffers =
resourceTracker->getTrackedResource(contextID, ResourceIDType::Framebuffer);
ResourceSet &newFramebuffers = trackedFramebuffers.getNewResources();
ResourceSet &framebuffersToDelete = trackedFramebuffers.getResourcesToDelete();
ResourceSet &framebuffersToRegen = trackedFramebuffers.getResourcesToRegen();
ResourceCalls &framebufferRegenCalls = trackedFramebuffers.getResourceRegenCalls();
ResourceCalls &framebufferRestoreCalls = trackedFramebuffers.getResourceRestoreCalls();
DeleteResourcesInReset(out, newFramebuffers, framebuffersToDelete, "Framebuffer",
maxResourceIDBufferSize);
for (GLuint id : framebuffersToRegen)
{
// Emit their regen calls
for (CallCapture &call : framebufferRegenCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
// If any of our starting framebuffers were modified during the run, restore their
// contents
ResourceSet &framebuffersToRestore = trackedFramebuffers.getResourcesToRestore();
for (GLuint id : framebuffersToRestore)
{
// Emit their restore calls
for (CallCapture &call : framebufferRestoreCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
break;
}
case ResourceIDType::Renderbuffer:
{
TrackedResource &trackedRenderbuffers =
resourceTracker->getTrackedResource(contextID, ResourceIDType::Renderbuffer);
ResourceSet &newRenderbuffers = trackedRenderbuffers.getNewResources();
ResourceSet &renderbuffersToDelete = trackedRenderbuffers.getResourcesToDelete();
ResourceSet &renderbuffersToRegen = trackedRenderbuffers.getResourcesToRegen();
ResourceCalls &renderbufferRegenCalls = trackedRenderbuffers.getResourceRegenCalls();
ResourceCalls &renderbufferRestoreCalls =
trackedRenderbuffers.getResourceRestoreCalls();
DeleteResourcesInReset(out, newRenderbuffers, renderbuffersToDelete, "Renderbuffer",
maxResourceIDBufferSize);
for (GLuint id : renderbuffersToRegen)
{
// Emit their regen calls
for (CallCapture &call : renderbufferRegenCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
// If any of our starting renderbuffers were modified during the run, restore their
// contents
ResourceSet &renderbuffersToRestore = trackedRenderbuffers.getResourcesToRestore();
for (GLuint id : renderbuffersToRestore)
{
// Emit their restore calls
for (CallCapture &call : renderbufferRestoreCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
break;
}
case ResourceIDType::ShaderProgram:
{
TrackedResource &trackedShaderPrograms =
resourceTracker->getTrackedResource(contextID, ResourceIDType::ShaderProgram);
ResourceSet &newShaderPrograms = trackedShaderPrograms.getNewResources();
ResourceSet &shaderProgramsToDelete = trackedShaderPrograms.getResourcesToDelete();
ResourceSet &shaderProgramsToRegen = trackedShaderPrograms.getResourcesToRegen();
ResourceSet &shaderProgramsToRestore = trackedShaderPrograms.getResourcesToRestore();
ResourceCalls &shaderProgramRegenCalls = trackedShaderPrograms.getResourceRegenCalls();
ResourceCalls &shaderProgramRestoreCalls =
trackedShaderPrograms.getResourceRestoreCalls();
// If we have any new shaders or programs created and not deleted during the run, delete
// them now
for (const GLuint &newShaderProgram : newShaderPrograms)
{
if (resourceTracker->getShaderProgramType({newShaderProgram}) ==
ShaderProgramType::ShaderType)
{
out << " glDeleteShader(gShaderProgramMap[" << newShaderProgram << "]);\n";
}
else
{
ASSERT(resourceTracker->getShaderProgramType({newShaderProgram}) ==
ShaderProgramType::ProgramType);
out << " glDeleteProgram(gShaderProgramMap[" << newShaderProgram << "]);\n";
}
}
// Do the same for shaders/programs to be deleted
for (const GLuint &shaderProgramToDelete : shaderProgramsToDelete)
{
if (resourceTracker->getShaderProgramType({shaderProgramToDelete}) ==
ShaderProgramType::ShaderType)
{
out << " glDeleteShader(gShaderProgramMap[" << shaderProgramToDelete
<< "]);\n";
}
else
{
ASSERT(resourceTracker->getShaderProgramType({shaderProgramToDelete}) ==
ShaderProgramType::ProgramType);
out << " glDeleteProgram(gShaderProgramMap[" << shaderProgramToDelete
<< "]);\n";
}
}
for (const GLuint id : shaderProgramsToRegen)
{
// Emit their regen calls
for (CallCapture &call : shaderProgramRegenCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
for (const GLuint id : shaderProgramsToRestore)
{
// Emit their restore calls
for (CallCapture &call : shaderProgramRestoreCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
break;
}
case ResourceIDType::Texture:
{
TrackedResource &trackedTextures =
resourceTracker->getTrackedResource(contextID, ResourceIDType::Texture);
ResourceSet &newTextures = trackedTextures.getNewResources();
ResourceSet &texturesToDelete = trackedTextures.getResourcesToDelete();
ResourceSet &texturesToRegen = trackedTextures.getResourcesToRegen();
ResourceCalls &textureRegenCalls = trackedTextures.getResourceRegenCalls();
ResourceCalls &textureRestoreCalls = trackedTextures.getResourceRestoreCalls();
DeleteResourcesInReset(out, newTextures, texturesToDelete, "Texture",
maxResourceIDBufferSize);
// If any of our starting textures were deleted, regen them
for (GLuint id : texturesToRegen)
{
// Emit their regen calls
for (CallCapture &call : textureRegenCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
// If any of our starting textures were modified during the run, restore their contents
ResourceSet &texturesToRestore = trackedTextures.getResourcesToRestore();
for (GLuint id : texturesToRestore)
{
// Emit their restore calls
for (CallCapture &call : textureRestoreCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
break;
}
case ResourceIDType::VertexArray:
{
TrackedResource &trackedVertexArrays =
resourceTracker->getTrackedResource(contextID, ResourceIDType::VertexArray);
ResourceSet &newVertexArrays = trackedVertexArrays.getNewResources();
ResourceSet &vertexArraysToDelete = trackedVertexArrays.getResourcesToDelete();
ResourceSet &vertexArraysToRegen = trackedVertexArrays.getResourcesToRegen();
ResourceSet &vertexArraysToRestore = trackedVertexArrays.getResourcesToRestore();
ResourceCalls &vertexArrayRegenCalls = trackedVertexArrays.getResourceRegenCalls();
ResourceCalls &vertexArrayRestoreCalls = trackedVertexArrays.getResourceRestoreCalls();
DeleteResourcesInReset(out, newVertexArrays, vertexArraysToDelete, "VertexArray",
maxResourceIDBufferSize);
// If any of our starting vertex arrays were deleted during the run, recreate them
for (GLuint id : vertexArraysToRegen)
{
// Emit their regen calls
for (CallCapture &call : vertexArrayRegenCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
// If any of our starting vertex arrays were modified during the run, restore their
// contents
for (GLuint id : vertexArraysToRestore)
{
// Emit their restore calls
for (CallCapture &call : vertexArrayRestoreCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
break;
}
case ResourceIDType::egl_Sync:
{
TrackedResource &trackedEGLSyncs =
resourceTracker->getTrackedResource(contextID, ResourceIDType::egl_Sync);
ResourceSet &newEGLSyncs = trackedEGLSyncs.getNewResources();
ResourceSet &eglSyncsToDelete = trackedEGLSyncs.getResourcesToDelete();
ResourceSet &eglSyncsToRegen = trackedEGLSyncs.getResourcesToRegen();
ResourceCalls &eglSyncRegenCalls = trackedEGLSyncs.getResourceRegenCalls();
if (!newEGLSyncs.empty() || !eglSyncsToDelete.empty())
{
for (GLuint oldResource : eglSyncsToDelete)
{
out << " eglDestroySyncKHR(gEGLDisplay, gEGLSyncMap[" << oldResource
<< "]);\n";
}
for (GLuint newResource : newEGLSyncs)
{
out << " eglDestroySyncKHR(gEGLDisplay, gEGLSyncMap[" << newResource
<< "]);\n";
}
}
// If any of our starting EGLsyncs were deleted during the run, recreate them
for (GLuint id : eglSyncsToRegen)
{
// Emit their regen calls
for (CallCapture &call : eglSyncRegenCalls[id])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
break;
}
default:
// TODO (http://anglebug.com/4599): Reset more resource types
break;
}
// If the output position has moved, we Reset something
anyResourceReset = (initialOutPos != out.tellp());
}
void MaybeResetFenceSyncObjects(std::stringstream &out,
ReplayWriter &replayWriter,
std::stringstream &header,
ResourceTracker *resourceTracker,
std::vector<uint8_t> *binaryData,
size_t *maxResourceIDBufferSize)
{
FenceSyncCalls &fenceSyncRegenCalls = resourceTracker->getFenceSyncRegenCalls();
// If any of our starting fence sync objects were deleted during the run, recreate them
FenceSyncSet &fenceSyncsToRegen = resourceTracker->getFenceSyncsToRegen();
for (const gl::SyncID syncID : fenceSyncsToRegen)
{
// Emit their regen calls
for (CallCapture &call : fenceSyncRegenCalls[syncID])
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
}
void Capture(std::vector<CallCapture> *setupCalls, CallCapture &&call)
{
setupCalls->emplace_back(std::move(call));
}
void CaptureUpdateCurrentProgram(const CallCapture &call,
int programParamPos,
std::vector<CallCapture> *callsOut)
{
const ParamCapture &param =
call.params.getParam("programPacked", ParamType::TShaderProgramID, programParamPos);
gl::ShaderProgramID programID = param.value.ShaderProgramIDVal;
ParamBuffer paramBuffer;
paramBuffer.addValueParam("program", ParamType::TGLuint, programID.value);
callsOut->emplace_back("UpdateCurrentProgram", std::move(paramBuffer));
}
bool ProgramNeedsReset(const gl::ContextID contextID,
ResourceTracker *resourceTracker,
gl::ShaderProgramID programID)
{
// Check whether the program is listed in programs to regen or restore
TrackedResource &trackedShaderPrograms =
resourceTracker->getTrackedResource(contextID, ResourceIDType::ShaderProgram);
ResourceSet &shaderProgramsToRegen = trackedShaderPrograms.getResourcesToRegen();
if (shaderProgramsToRegen.count(programID.value) != 0)
{
return true;
}
ResourceSet &shaderProgramsToRestore = trackedShaderPrograms.getResourcesToRestore();
if (shaderProgramsToRestore.count(programID.value) != 0)
{
return true;
}
return false;
}
void MaybeResetDefaultUniforms(std::stringstream &out,
ReplayWriter &replayWriter,
std::stringstream &header,
const gl::Context *context,
ResourceTracker *resourceTracker,
std::vector<uint8_t> *binaryData,
size_t *maxResourceIDBufferSize)
{
DefaultUniformLocationsPerProgramMap &defaultUniformsToReset =
resourceTracker->getDefaultUniformsToReset();
for (const auto &uniformIter : defaultUniformsToReset)
{
gl::ShaderProgramID programID = uniformIter.first;
const DefaultUniformLocationsSet &locations = uniformIter.second;
if (ProgramNeedsReset(context->id(), resourceTracker, programID))
{
// Skip programs marked for reset as they will update their own uniforms
return;
}
// Bind the program to update its uniforms
std::vector<CallCapture> bindCalls;
Capture(&bindCalls, CaptureUseProgram(context->getState(), true, programID));
CaptureUpdateCurrentProgram((&bindCalls)->back(), 0, &bindCalls);
for (CallCapture &call : bindCalls)
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
DefaultUniformCallsPerLocationMap &defaultUniformResetCalls =
resourceTracker->getDefaultUniformResetCalls(programID);
// Uniform arrays might have been modified in the middle (i.e. location 5 out of 10)
// We only have Reset calls for the entire array, so emit them once for the entire array
std::set<gl::UniformLocation> alreadyReset;
// Emit the reset calls per modified location
for (const gl::UniformLocation &location : locations)
{
gl::UniformLocation baseLocation =
resourceTracker->getDefaultUniformBaseLocation(programID, location);
if (alreadyReset.find(baseLocation) != alreadyReset.end())
{
// We've already Reset this array
continue;
}
alreadyReset.insert(baseLocation);
ASSERT(defaultUniformResetCalls.find(baseLocation) != defaultUniformResetCalls.end());
std::vector<CallCapture> &callsPerLocation = defaultUniformResetCalls[baseLocation];
for (CallCapture &call : callsPerLocation)
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
}
}
void MaybeResetOpaqueTypeObjects(ReplayWriter &replayWriter,
std::stringstream &out,
std::stringstream &header,
const gl::Context *context,
ResourceTracker *resourceTracker,
std::vector<uint8_t> *binaryData,
size_t *maxResourceIDBufferSize)
{
MaybeResetFenceSyncObjects(out, replayWriter, header, resourceTracker, binaryData,
maxResourceIDBufferSize);
MaybeResetDefaultUniforms(out, replayWriter, header, context, resourceTracker, binaryData,
maxResourceIDBufferSize);
}
void MaybeResetContextState(ReplayWriter &replayWriter,
std::stringstream &out,
std::stringstream &header,
ResourceTracker *resourceTracker,
const gl::Context *context,
std::vector<uint8_t> *binaryData,
StateResetHelper &stateResetHelper,
size_t *maxResourceIDBufferSize)
{
// Check dirty states per entrypoint
for (const EntryPoint &entryPoint : stateResetHelper.getDirtyEntryPoints())
{
const CallResetMap *resetCalls = &stateResetHelper.getResetCalls();
// Create the default reset call for this entrypoint
if (resetCalls->find(entryPoint) == resetCalls->end())
{
// If we don't have any reset calls for these entrypoints, that means we started capture
// from the beginning, amd mid-execution capture was not invoked.
stateResetHelper.setDefaultResetCalls(context, entryPoint);
}
// Emit the calls
for (const auto &call : resetCalls->at(entryPoint))
{
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData,
maxResourceIDBufferSize);
out << ";\n";
}
}
}
void MarkResourceIDActive(ResourceIDType resourceType,
GLuint id,
std::vector<CallCapture> *setupCalls,
const ResourceIDToSetupCallsMap *resourceIDToSetupCallsMap)
{
const std::map<GLuint, gl::Range<size_t>> &resourceSetupCalls =
(*resourceIDToSetupCallsMap)[resourceType];
const auto iter = resourceSetupCalls.find(id);
if (iter == resourceSetupCalls.end())
{
return;
}
// Mark all of the calls that were used to initialize this resource as ACTIVE
const gl::Range<size_t> &calls = iter->second;
for (size_t index : calls)
{
(*setupCalls)[index].isActive = true;
}
}
// Some replay functions can get quite large. If over a certain size, this method breaks up the
// function into parts to avoid overflowing the stack and causing slow compilation.
void WriteCppReplayFunctionWithParts(const gl::ContextID contextID,
ReplayFunc replayFunc,
ReplayWriter &replayWriter,
uint32_t frameIndex,
std::vector<uint8_t> *binaryData,
const std::vector<CallCapture> &calls,
std::stringstream &header,
std::stringstream &out,
size_t *maxResourceIDBufferSize)
{
int callCount = 0;
int partCount = 0;
if (calls.size() > kFunctionSizeLimit)
{
out << "void "
<< FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, ++partCount)
<< "\n";
}
else
{
out << "void "
<< FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, kNoPartId)
<< "\n";
}
out << "{\n";
for (const CallCapture &call : calls)
{
if (!call.isActive)
{
// Don't write setup calls for inactive resources
continue;
}
out << " ";
WriteCppReplayForCall(call, replayWriter, out, header, binaryData, maxResourceIDBufferSize);
out << ";\n";
if (partCount > 0 && ++callCount % kFunctionSizeLimit == 0)
{
out << "}\n";
out << "\n";
out << "void "
<< FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex,
++partCount)
<< "\n";
out << "{\n";
}
}
out << "}\n";
if (partCount > 0)
{
out << "\n";
out << "void "
<< FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, kNoPartId)
<< "\n";
out << "{\n";
// Write out the main call which calls all the parts.
for (int i = 1; i <= partCount; i++)
{
out << " " << FmtFunction(replayFunc, contextID, FuncUsage::Call, frameIndex, i)
<< ";\n";
}
out << "}\n";
}
}
// Auxiliary contexts are other contexts in the share group that aren't the context calling
// eglSwapBuffers().
void WriteAuxiliaryContextCppSetupReplay(ReplayWriter &replayWriter,
bool compression,
const std::string &outDir,
const gl::Context *context,
const std::string &captureLabel,
uint32_t frameIndex,
const std::vector<CallCapture> &setupCalls,
std::vector<uint8_t> *binaryData,
bool serializeStateEnabled,
const FrameCaptureShared &frameCaptureShared,
size_t *maxResourceIDBufferSize)
{
ASSERT(frameCaptureShared.getWindowSurfaceContextID() != context->id());
{
std::stringstream filenameStream;
filenameStream << outDir << FmtCapturePrefix(context->id(), captureLabel);
std::string filenamePattern = filenameStream.str();
replayWriter.setFilenamePattern(filenamePattern);
}
{
std::stringstream include;
include << "#include \""
<< FmtCapturePrefix(frameCaptureShared.getWindowSurfaceContextID(), captureLabel)
<< ".h\"\n";
include << "#include \"angle_trace_gl.h\"\n";
std::string frameIncludes = include.str();
replayWriter.setSourcePrologue(frameIncludes);
replayWriter.setHeaderPrologue(frameIncludes);
}
{
std::stringstream protoStream;
std::stringstream headerStream;
std::stringstream bodyStream;
protoStream << "void " << FmtSetupFunction(kNoPartId, context->id(), FuncUsage::Prototype);
std::string proto = protoStream.str();
WriteCppReplayFunctionWithParts(context->id(), ReplayFunc::Setup, replayWriter, frameIndex,
binaryData, setupCalls, headerStream, bodyStream,
maxResourceIDBufferSize);
replayWriter.addPrivateFunction(proto, headerStream, bodyStream);
}
replayWriter.saveFrame();
}
void WriteShareGroupCppSetupReplay(ReplayWriter &replayWriter,
bool compression,
const std::string &outDir,
const std::string &captureLabel,
uint32_t frameIndex,
uint32_t frameCount,
const std::vector<CallCapture> &setupCalls,
ResourceTracker *resourceTracker,
std::vector<uint8_t> *binaryData,
bool serializeStateEnabled,
gl::ContextID windowSurfaceContextID,
size_t *maxResourceIDBufferSize)
{
{
std::stringstream include;
include << "#include \"angle_trace_gl.h\"\n";
include << "#include \"" << FmtCapturePrefix(windowSurfaceContextID, captureLabel)
<< ".h\"\n";
std::string includeString = include.str();
replayWriter.setSourcePrologue(includeString);
}
{
std::stringstream protoStream;
std::stringstream headerStream;
std::stringstream bodyStream;
protoStream << "void "
<< FmtSetupFunction(kNoPartId, kSharedContextId, FuncUsage::Prototype);
std::string proto = protoStream.str();
WriteCppReplayFunctionWithParts(kSharedContextId, ReplayFunc::Setup, replayWriter,
frameIndex, binaryData, setupCalls, headerStream,
bodyStream, maxResourceIDBufferSize);
replayWriter.addPrivateFunction(proto, headerStream, bodyStream);
}
{
std::stringstream filenameStream;
filenameStream << outDir << FmtCapturePrefix(kSharedContextId, captureLabel);
std::string filenamePattern = filenameStream.str();
replayWriter.setFilenamePattern(filenamePattern);
}
replayWriter.saveSetupFile();
}
ProgramSources GetAttachedProgramSources(const gl::Program *program)
{
ProgramSources sources;
for (gl::ShaderType shaderType : gl::AllShaderTypes())
{
const gl::Shader *shader = program->getAttachedShader(shaderType);
if (shader)
{
sources[shaderType] = shader->getSourceString();
}
}
return sources;
}
template <typename IDType>
void CaptureUpdateResourceIDs(const gl::Context *context,
const CallCapture &call,
const ParamCapture &param,
ResourceTracker *resourceTracker,
std::vector<CallCapture> *callsOut)
{
GLsizei n = call.params.getParamFlexName("n", "count", ParamType::TGLsizei, 0).value.GLsizeiVal;
ASSERT(param.data.size() == 1);
ResourceIDType resourceIDType = GetResourceIDTypeFromParamType(param.type);
ASSERT(resourceIDType != ResourceIDType::InvalidEnum &&
resourceIDType != ResourceIDType::ShaderProgram);
const char *resourceName = GetResourceIDTypeName(resourceIDType);
std::stringstream updateFuncNameStr;
updateFuncNameStr << "Update" << resourceName << "ID";
std::string updateFuncName = updateFuncNameStr.str();
const IDType *returnedIDs = reinterpret_cast<const IDType *>(param.data[0].data());
ResourceSet &startingSet =
resourceTracker->getTrackedResource(context->id(), resourceIDType).getStartingResources();
for (GLsizei idIndex = 0; idIndex < n; ++idIndex)
{
IDType id = returnedIDs[idIndex];
GLsizei readBufferOffset = idIndex * sizeof(gl::RenderbufferID);
ParamBuffer params;
params.addValueParam("id", ParamType::TGLuint, id.value);
params.addValueParam("readBufferOffset", ParamType::TGLsizei, readBufferOffset);
callsOut->emplace_back(updateFuncName, std::move(params));
// Add only if not in starting resources.
if (startingSet.find(id.value) == startingSet.end())
{
resourceTracker->getTrackedResource(context->id(), resourceIDType)
.getNewResources()
.insert(id.value);
}
}
}
void CaptureUpdateUniformLocations(const gl::Program *program, std::vector<CallCapture> *callsOut)
{
const std::vector<gl::LinkedUniform> &uniforms = program->getState().getUniforms();
const std::vector<gl::VariableLocation> &locations = program->getUniformLocations();
for (GLint location = 0; location < static_cast<GLint>(locations.size()); ++location)
{
const gl::VariableLocation &locationVar = locations[location];
// This handles the case where the application calls glBindUniformLocationCHROMIUM
// on an unused uniform. We must still store a -1 into gUniformLocations in case the
// application attempts to call a glUniform* call. To do this we'll pass in a blank name to
// force glGetUniformLocation to return -1.
std::string name;
int count = 1;
ParamBuffer params;
params.addValueParam("program", ParamType::TGLuint, program->id().value);
if (locationVar.index >= uniforms.size())
{
name = "";
}
else
{
const gl::LinkedUniform &uniform = uniforms[locationVar.index];
name = program->getUniformNameByIndex(locationVar.index);
if (uniform.isArray())
{
if (locationVar.arrayIndex > 0)
{
// Non-sequential array uniform locations are not currently handled.
// In practice array locations shouldn't ever be non-sequential.
ASSERT(uniform.getLocation() == -1 ||
location ==
uniform.getLocation() + static_cast<int>(locationVar.arrayIndex));
continue;
}
name = gl::StripLastArrayIndex(name);
count = uniform.getBasicTypeElementCount();
}
}
ParamCapture nameParam("name", ParamType::TGLcharConstPointer);
CaptureString(name.c_str(), &nameParam);
params.addParam(std::move(nameParam));
params.addValueParam("location", ParamType::TGLint, location);
params.addValueParam("count", ParamType::TGLint, static_cast<GLint>(count));
callsOut->emplace_back("UpdateUniformLocation", std::move(params));
}
}
void CaptureValidateSerializedState(const gl::Context *context, std::vector<CallCapture> *callsOut)
{
INFO() << "Capturing validation checkpoint at position " << callsOut->size();
context->finishImmutable();
std::string serializedState;
angle::Result result = angle::SerializeContextToString(context, &serializedState);
if (result != angle::Result::Continue)
{
ERR() << "Internal error serializing context state.";
return;
}
ParamCapture serializedStateParam("serializedState", ParamType::TGLcharConstPointer);
CaptureString(serializedState.c_str(), &serializedStateParam);
ParamBuffer params;
params.addParam(std::move(serializedStateParam));
callsOut->emplace_back("VALIDATE_CHECKPOINT", std::move(params));
}
void CaptureUpdateUniformBlockIndexes(const gl::Program *program,
std::vector<CallCapture> *callsOut)
{
const std::vector<gl::InterfaceBlock> &uniformBlocks = program->getState().getUniformBlocks();
for (GLuint index = 0; index < uniformBlocks.size(); ++index)
{
ParamBuffer params;
std::string name;
params.addValueParam("program", ParamType::TShaderProgramID, program->id());
const std::string fullName = uniformBlocks[index].nameWithArrayIndex();
ParamCapture nameParam("name", ParamType::TGLcharConstPointer);
CaptureString(fullName.c_str(), &nameParam);
params.addParam(std::move(nameParam));
params.addValueParam("index", ParamType::TGLuint, index);
callsOut->emplace_back("UpdateUniformBlockIndex", std::move(params));
}
}
void CaptureDeleteUniformLocations(gl::ShaderProgramID program, std::vector<CallCapture> *callsOut)
{
ParamBuffer params;
params.addValueParam("program", ParamType::TShaderProgramID, program);
callsOut->emplace_back("DeleteUniformLocations", std::move(params));
}
void MaybeCaptureUpdateResourceIDs(const gl::Context *context,
ResourceTracker *resourceTracker,
std::vector<CallCapture> *callsOut)
{
const CallCapture &call = callsOut->back();
switch (call.entryPoint)
{
case EntryPoint::GLGenBuffers:
{
const ParamCapture &buffers =
call.params.getParam("buffersPacked", ParamType::TBufferIDPointer, 1);
CaptureUpdateResourceIDs<gl::BufferID>(context, call, buffers, resourceTracker,
callsOut);
break;
}
case EntryPoint::GLGenFencesNV:
{
const ParamCapture &fences =
call.params.getParam("fencesPacked", ParamType::TFenceNVIDPointer, 1);
CaptureUpdateResourceIDs<gl::FenceNVID>(context, call, fences, resourceTracker,
callsOut);
break;
}
case EntryPoint::GLGenFramebuffers:
case EntryPoint::GLGenFramebuffersOES:
{
const ParamCapture &framebuffers =
call.params.getParam("framebuffersPacked", ParamType::TFramebufferIDPointer, 1);
CaptureUpdateResourceIDs<gl::FramebufferID>(context, call, framebuffers,
resourceTracker, callsOut);
break;
}
case EntryPoint::GLGenProgramPipelines:
{
const ParamCapture &pipelines =
call.params.getParam("pipelinesPacked", ParamType::TProgramPipelineIDPointer, 1);
CaptureUpdateResourceIDs<gl::ProgramPipelineID>(context, call, pipelines,
resourceTracker, callsOut);
break;
}
case EntryPoint::GLGenQueries:
case EntryPoint::GLGenQueriesEXT:
{
const ParamCapture &queries =
call.params.getParam("idsPacked", ParamType::TQueryIDPointer, 1);
CaptureUpdateResourceIDs<gl::QueryID>(context, call, queries, resourceTracker,
callsOut);
break;
}
case EntryPoint::GLGenRenderbuffers:
case EntryPoint::GLGenRenderbuffersOES:
{
const ParamCapture &renderbuffers =
call.params.getParam("renderbuffersPacked", ParamType::TRenderbufferIDPointer, 1);
CaptureUpdateResourceIDs<gl::RenderbufferID>(context, call, renderbuffers,
resourceTracker, callsOut);
break;
}
case EntryPoint::GLGenSamplers:
{
const ParamCapture &samplers =
call.params.getParam("samplersPacked", ParamType::TSamplerIDPointer, 1);
CaptureUpdateResourceIDs<gl::SamplerID>(context, call, samplers, resourceTracker,
callsOut);
break;
}
case EntryPoint::GLGenSemaphoresEXT:
{
const ParamCapture &semaphores =
call.params.getParam("semaphoresPacked", ParamType::TSemaphoreIDPointer, 1);
CaptureUpdateResourceIDs<gl::SemaphoreID>(context, call, semaphores, resourceTracker,
callsOut);
break;
}
case EntryPoint::GLGenTextures:
{
const ParamCapture &textures =
call.params.getParam("texturesPacked", ParamType::TTextureIDPointer, 1);
CaptureUpdateResourceIDs<gl::TextureID>(context, call, textures, resourceTracker,
callsOut);
break;
}
case EntryPoint::GLGenTransformFeedbacks:
{
const ParamCapture &xfbs =
call.params.getParam("idsPacked", ParamType::TTransformFeedbackIDPointer, 1);
CaptureUpdateResourceIDs<gl::TransformFeedbackID>(context, call, xfbs, resourceTracker,
callsOut);
break;
}
case EntryPoint::GLGenVertexArrays:
case EntryPoint::GLGenVertexArraysOES:
{
const ParamCapture &vertexArrays =
call.params.getParam("arraysPacked", ParamType::TVertexArrayIDPointer, 1);
CaptureUpdateResourceIDs<gl::VertexArrayID>(context, call, vertexArrays,
resourceTracker, callsOut);
break;
}
case EntryPoint::GLCreateMemoryObjectsEXT:
{
const ParamCapture &memoryObjects =
call.params.getParam("memoryObjectsPacked", ParamType::TMemoryObjectIDPointer, 1);
CaptureUpdateResourceIDs<gl::MemoryObjectID>(context, call, memoryObjects,
resourceTracker, callsOut);
break;
}
default:
break;
}
}
bool IsDefaultCurrentValue(const gl::VertexAttribCurrentValueData &currentValue)
{
if (currentValue.Type != gl::VertexAttribType::Float)
return false;
return currentValue.Values.FloatValues[0] == 0.0f &&
currentValue.Values.FloatValues[1] == 0.0f &&
currentValue.Values.FloatValues[2] == 0.0f && currentValue.Values.FloatValues[3] == 1.0f;
}
bool IsQueryActive(const gl::State &glState, gl::QueryID &queryID)
{
const gl::ActiveQueryMap &activeQueries = glState.getActiveQueriesForCapture();
for (const auto &activeQueryIter : activeQueries)
{
const gl::Query *activeQuery = activeQueryIter.get();
if (activeQuery && activeQuery->id() == queryID)
{
return true;
}
}
return false;
}
bool IsTextureUpdate(CallCapture &call)
{
switch (call.entryPoint)
{
case EntryPoint::GLCompressedCopyTextureCHROMIUM:
case EntryPoint::GLCompressedTexImage1D:
case EntryPoint::GLCompressedTexImage2D:
case EntryPoint::GLCompressedTexImage2DRobustANGLE:
case EntryPoint::GLCompressedTexImage3D:
case EntryPoint::GLCompressedTexImage3DOES:
case EntryPoint::GLCompressedTexImage3DRobustANGLE:
case EntryPoint::GLCompressedTexSubImage1D:
case EntryPoint::GLCompressedTexSubImage2D:
case EntryPoint::GLCompressedTexSubImage2DRobustANGLE:
case EntryPoint::GLCompressedTexSubImage3D:
case EntryPoint::GLCompressedTexSubImage3DOES:
case EntryPoint::GLCompressedTexSubImage3DRobustANGLE:
case EntryPoint::GLCompressedTextureSubImage1D:
case EntryPoint::GLCompressedTextureSubImage2D:
case EntryPoint::GLCompressedTextureSubImage3D:
case EntryPoint::GLCopyTexImage1D:
case EntryPoint::GLCopyTexImage2D:
case EntryPoint::GLCopyTexSubImage1D:
case EntryPoint::GLCopyTexSubImage2D:
case EntryPoint::GLCopyTexSubImage3D:
case EntryPoint::GLCopyTexSubImage3DOES:
case EntryPoint::GLCopyTexture3DANGLE:
case EntryPoint::GLCopyTextureCHROMIUM:
case EntryPoint::GLCopyTextureSubImage1D:
case EntryPoint::GLCopyTextureSubImage2D:
case EntryPoint::GLCopyTextureSubImage3D:
case EntryPoint::GLTexImage1D:
case EntryPoint::GLTexImage2D:
case EntryPoint::GLTexImage2DExternalANGLE:
case EntryPoint::GLTexImage2DMultisample:
case EntryPoint::GLTexImage2DRobustANGLE:
case EntryPoint::GLTexImage3D:
case EntryPoint::GLTexImage3DMultisample:
case EntryPoint::GLTexImage3DOES:
case EntryPoint::GLTexImage3DRobustANGLE:
case EntryPoint::GLTexSubImage1D:
case EntryPoint::GLTexSubImage2D:
case EntryPoint::GLTexSubImage2DRobustANGLE:
case EntryPoint::GLTexSubImage3D:
case EntryPoint::GLTexSubImage3DOES:
case EntryPoint::GLTexSubImage3DRobustANGLE:
case EntryPoint::GLTextureSubImage1D:
case EntryPoint::GLTextureSubImage2D:
case EntryPoint::GLTextureSubImage3D:
case EntryPoint::GLCopyImageSubData:
case EntryPoint::GLCopyImageSubDataEXT:
case EntryPoint::GLCopyImageSubDataOES:
return true;
default:
return false;
}
}
bool IsVertexArrayUpdate(CallCapture &call)
{
switch (call.entryPoint)
{
case EntryPoint::GLVertexAttribFormat:
case EntryPoint::GLVertexAttribIFormat:
case EntryPoint::GLBindVertexBuffer:
case EntryPoint::GLVertexAttribBinding:
case EntryPoint::GLVertexAttribPointer:
case EntryPoint::GLVertexAttribIPointer:
case EntryPoint::GLEnableVertexAttribArray:
case EntryPoint::GLDisableVertexAttribArray:
case EntryPoint::GLVertexBindingDivisor:
case EntryPoint::GLVertexAttribDivisor:
return true;
default:
return false;
}
}
bool IsSharedObjectResource(ResourceIDType type)
{
// This helper function informs us which objects are shared vs. per context
//
// OpenGL ES Version 3.2 (October 22, 2019)
// Chapter 5 Shared Objects and Multiple Contexts:
//
// - Objects that can be shared between contexts include buffer objects, program
// and shader objects, renderbuffer objects, sampler objects, sync objects, and texture
// objects (except for the texture objects named zero).
// - Objects which contain references to other objects include framebuffer, program
// pipeline, transform feedback, and vertex array objects. Such objects are called
// container objects and are not shared.
//
// Notably absent from this list are Sync objects, which are not ResourceIDType, are handled
// elsewhere, and are shared:
// - 2.6.13 Sync Objects: Sync objects may be shared.
switch (type)
{
case ResourceIDType::Buffer:
// 2.6.2 Buffer Objects: Buffer objects may be shared.
return true;
case ResourceIDType::Framebuffer:
// 2.6.9 Framebuffer Objects: Framebuffer objects are container objects including
// references to renderbuffer and / or texture objects, and are not shared.
return false;
case ResourceIDType::ProgramPipeline:
// 2.6.5 Program Pipeline Objects: Program pipeline objects are container objects
// including references to program objects, and are not shared.
return false;
case ResourceIDType::TransformFeedback:
// 2.6.11 Transform Feedback Objects: Transform feedback objects are container objects
// including references to buffer objects, and are not shared
return false;
case ResourceIDType::VertexArray:
// 2.6.10 Vertex Array Objects: Vertex array objects are container objects including
// references to buffer objects, and are not shared
return false;
case ResourceIDType::FenceNV:
// From https://registry.khronos.org/OpenGL/extensions/NV/NV_fence.txt
// Are the fences sharable between multiple contexts?
// RESOLUTION: No.
return false;
case ResourceIDType::Renderbuffer:
// 2.6.8 Renderbuffer Objects: Renderbuffer objects may be shared.
return true;
case ResourceIDType::ShaderProgram:
// 2.6.3 Shader Objects: Shader objects may be shared.
// 2.6.4 Program Objects: Program objects may be shared.
return true;
case ResourceIDType::Sampler:
// 2.6.7 Sampler Objects: Sampler objects may be shared
return true;
case ResourceIDType::Sync:
// 2.6.13 Sync Objects: Sync objects may be shared.
return true;
case ResourceIDType::Texture:
// 2.6.6 Texture Objects: Texture objects may be shared
return true;
case ResourceIDType::Query:
// 2.6.12 Query Objects: Query objects are not shared
return false;
case ResourceIDType::Semaphore:
// From https://registry.khronos.org/OpenGL/extensions/EXT/EXT_external_objects.txt
// 2.6.14 Semaphore Objects: Semaphore objects may be shared.
return true;
case ResourceIDType::MemoryObject:
// From https://registry.khronos.org/OpenGL/extensions/EXT/EXT_external_objects.txt
// 2.6.15 Memory Objects: Memory objects may be shared.
return true;
case ResourceIDType::Context:
case ResourceIDType::Image:
case ResourceIDType::Surface:
case ResourceIDType::egl_Sync:
// EGL types are associated with a display and not bound to a context
// For the way this function is used, we can treat them as shared.
return true;
case ResourceIDType::EnumCount:
default:
ERR() << "Unhandled ResourceIDType= " << static_cast<int>(type);
UNREACHABLE();
return false;
}
}
enum class DefaultUniformType
{
None,
CurrentProgram,
SpecifiedProgram,
};
DefaultUniformType GetDefaultUniformType(const CallCapture &call)
{
switch (call.entryPoint)
{
case EntryPoint::GLProgramUniform1d:
case EntryPoint::GLProgramUniform1dv:
case EntryPoint::GLProgramUniform1f:
case EntryPoint::GLProgramUniform1fEXT:
case EntryPoint::GLProgramUniform1fv:
case EntryPoint::GLProgramUniform1fvEXT:
case EntryPoint::GLProgramUniform1i:
case EntryPoint::GLProgramUniform1iEXT:
case EntryPoint::GLProgramUniform1iv:
case EntryPoint::GLProgramUniform1ivEXT:
case EntryPoint::GLProgramUniform1ui:
case EntryPoint::GLProgramUniform1uiEXT:
case EntryPoint::GLProgramUniform1uiv:
case EntryPoint::GLProgramUniform1uivEXT:
case EntryPoint::GLProgramUniform2d:
case EntryPoint::GLProgramUniform2dv:
case EntryPoint::GLProgramUniform2f:
case EntryPoint::GLProgramUniform2fEXT:
case EntryPoint::GLProgramUniform2fv:
case EntryPoint::GLProgramUniform2fvEXT:
case EntryPoint::GLProgramUniform2i:
case EntryPoint::GLProgramUniform2iEXT:
case EntryPoint::GLProgramUniform2iv:
case EntryPoint::GLProgramUniform2ivEXT:
case EntryPoint::GLProgramUniform2ui:
case EntryPoint::GLProgramUniform2uiEXT:
case EntryPoint::GLProgramUniform2uiv:
case EntryPoint::GLProgramUniform2uivEXT:
case EntryPoint::GLProgramUniform3d:
case EntryPoint::GLProgramUniform3dv:
case EntryPoint::GLProgramUniform3f:
case EntryPoint::GLProgramUniform3fEXT:
case EntryPoint::GLProgramUniform3fv:
case EntryPoint::GLProgramUniform3fvEXT:
case EntryPoint::GLProgramUniform3i:
case EntryPoint::GLProgramUniform3iEXT:
case EntryPoint::GLProgramUniform3iv:
case EntryPoint::GLProgramUniform3ivEXT:
case EntryPoint::GLProgramUniform3ui:
case EntryPoint::GLProgramUniform3uiEXT:
case EntryPoint::GLProgramUniform3uiv:
case EntryPoint::GLProgramUniform3uivEXT:
case EntryPoint::GLProgramUniform4d:
case EntryPoint::GLProgramUniform4dv:
case EntryPoint::GLProgramUniform4f:
case EntryPoint::GLProgramUniform4fEXT:
case EntryPoint::GLProgramUniform4fv:
case EntryPoint::GLProgramUniform4fvEXT:
case EntryPoint::GLProgramUniform4i:
case EntryPoint::GLProgramUniform4iEXT:
case EntryPoint::GLProgramUniform4iv:
case EntryPoint::GLProgramUniform4ivEXT:
case EntryPoint::GLProgramUniform4ui:
case EntryPoint::GLProgramUniform4uiEXT:
case EntryPoint::GLProgramUniform4uiv:
case EntryPoint::GLProgramUniform4uivEXT:
case EntryPoint::GLProgramUniformMatrix2dv:
case EntryPoint::GLProgramUniformMatrix2fv:
case EntryPoint::GLProgramUniformMatrix2fvEXT:
case EntryPoint::GLProgramUniformMatrix2x3dv:
case EntryPoint::GLProgramUniformMatrix2x3fv:
case EntryPoint::GLProgramUniformMatrix2x3fvEXT:
case EntryPoint::GLProgramUniformMatrix2x4dv:
case EntryPoint::GLProgramUniformMatrix2x4fv:
case EntryPoint::GLProgramUniformMatrix2x4fvEXT:
case EntryPoint::GLProgramUniformMatrix3dv:
case EntryPoint::GLProgramUniformMatrix3fv:
case EntryPoint::GLProgramUniformMatrix3fvEXT:
case EntryPoint::GLProgramUniformMatrix3x2dv:
case EntryPoint::GLProgramUniformMatrix3x2fv:
case EntryPoint::GLProgramUniformMatrix3x2fvEXT:
case EntryPoint::GLProgramUniformMatrix3x4dv:
case EntryPoint::GLProgramUniformMatrix3x4fv:
case EntryPoint::GLProgramUniformMatrix3x4fvEXT:
case EntryPoint::GLProgramUniformMatrix4dv:
case EntryPoint::GLProgramUniformMatrix4fv:
case EntryPoint::GLProgramUniformMatrix4fvEXT:
case EntryPoint::GLProgramUniformMatrix4x2dv:
case EntryPoint::GLProgramUniformMatrix4x2fv:
case EntryPoint::GLProgramUniformMatrix4x2fvEXT:
case EntryPoint::GLProgramUniformMatrix4x3dv:
case EntryPoint::GLProgramUniformMatrix4x3fv:
case EntryPoint::GLProgramUniformMatrix4x3fvEXT:
return DefaultUniformType::SpecifiedProgram;
case EntryPoint::GLUniform1d:
case EntryPoint::GLUniform1dv:
case EntryPoint::GLUniform1f:
case EntryPoint::GLUniform1fv:
case EntryPoint::GLUniform1i:
case EntryPoint::GLUniform1iv:
case EntryPoint::GLUniform1ui:
case EntryPoint::GLUniform1uiv:
case EntryPoint::GLUniform2d:
case EntryPoint::GLUniform2dv:
case EntryPoint::GLUniform2f:
case EntryPoint::GLUniform2fv:
case EntryPoint::GLUniform2i:
case EntryPoint::GLUniform2iv:
case EntryPoint::GLUniform2ui:
case EntryPoint::GLUniform2uiv:
case EntryPoint::GLUniform3d:
case EntryPoint::GLUniform3dv:
case EntryPoint::GLUniform3f:
case EntryPoint::GLUniform3fv:
case EntryPoint::GLUniform3i:
case EntryPoint::GLUniform3iv:
case EntryPoint::GLUniform3ui:
case EntryPoint::GLUniform3uiv:
case EntryPoint::GLUniform4d:
case EntryPoint::GLUniform4dv:
case EntryPoint::GLUniform4f:
case EntryPoint::GLUniform4fv:
case EntryPoint::GLUniform4i:
case EntryPoint::GLUniform4iv:
case EntryPoint::GLUniform4ui:
case EntryPoint::GLUniform4uiv:
case EntryPoint::GLUniformMatrix2dv:
case EntryPoint::GLUniformMatrix2fv:
case EntryPoint::GLUniformMatrix2x3dv:
case EntryPoint::GLUniformMatrix2x3fv:
case EntryPoint::GLUniformMatrix2x4dv:
case EntryPoint::GLUniformMatrix2x4fv:
case EntryPoint::GLUniformMatrix3dv:
case EntryPoint::GLUniformMatrix3fv:
case EntryPoint::GLUniformMatrix3x2dv:
case EntryPoint::GLUniformMatrix3x2fv:
case EntryPoint::GLUniformMatrix3x4dv:
case EntryPoint::GLUniformMatrix3x4fv:
case EntryPoint::GLUniformMatrix4dv:
case EntryPoint::GLUniformMatrix4fv:
case EntryPoint::GLUniformMatrix4x2dv:
case EntryPoint::GLUniformMatrix4x2fv:
case EntryPoint::GLUniformMatrix4x3dv:
case EntryPoint::GLUniformMatrix4x3fv:
case EntryPoint::GLUniformSubroutinesuiv:
return DefaultUniformType::CurrentProgram;
default:
return DefaultUniformType::None;
}
}
void CaptureFramebufferAttachment(std::vector<CallCapture> *setupCalls,
const gl::State &replayState,
const FramebufferCaptureFuncs &framebufferFuncs,
const gl::FramebufferAttachment &attachment)
{
GLuint resourceID = attachment.getResource()->getId();
if (attachment.type() == GL_TEXTURE)
{
gl::ImageIndex index = attachment.getTextureImageIndex();
if (index.usesTex3D())
{
Capture(setupCalls, CaptureFramebufferTextureLayer(
replayState, true, GL_FRAMEBUFFER, attachment.getBinding(),
{resourceID}, index.getLevelIndex(), index.getLayerIndex()));
}
else
{
Capture(setupCalls,
framebufferFuncs.framebufferTexture2D(
replayState, true, GL_FRAMEBUFFER, attachment.getBinding(),
index.getTargetOrFirstCubeFace(), {resourceID}, index.getLevelIndex()));
}
}
else
{
ASSERT(attachment.type() == GL_RENDERBUFFER);
Capture(setupCalls, framebufferFuncs.framebufferRenderbuffer(
replayState, true, GL_FRAMEBUFFER, attachment.getBinding(),
GL_RENDERBUFFER, {resourceID}));
}
}
void CaptureUpdateUniformValues(const gl::State &replayState,
const gl::Context *context,
gl::Program *program,
ResourceTracker *resourceTracker,
std::vector<CallCapture> *callsOut)
{
if (!program->isLinked())
{
// We can't populate uniforms if the program hasn't been linked
return;
}
// We need to bind the program and update its uniforms
if (!replayState.getProgram() || replayState.getProgram()->id() != program->id())
{
Capture(callsOut, CaptureUseProgram(replayState, true, program->id()));
CaptureUpdateCurrentProgram(callsOut->back(), 0, callsOut);
}
for (GLuint uniformIndex = 0; uniformIndex < static_cast<GLuint>(program->getUniforms().size());
uniformIndex++)
{
std::string uniformName = program->getUniformNameByIndex(uniformIndex);
const gl::LinkedUniform &uniform = program->getUniformByIndex(uniformIndex);
int uniformCount = 1;
if (uniform.isArray())
{
uniformCount = uniform.getBasicTypeElementCount();
uniformName = gl::StripLastArrayIndex(uniformName);
}
gl::UniformLocation uniformLoc = program->getUniformLocation(uniformName);
const gl::UniformTypeInfo &typeInfo = gl::GetUniformTypeInfo(uniform.getType());
int componentCount = typeInfo.componentCount;
int uniformSize = uniformCount * componentCount;
// For arrayed uniforms, we'll need to increment a read location
gl::UniformLocation readLoc = uniformLoc;
// If the uniform is unused, just continue
if (readLoc.value == -1)
{
continue;
}
// Image uniforms are special and cannot be set this way
if (typeInfo.isImageType)
{
continue;
}
DefaultUniformCallsPerLocationMap &resetCalls =
resourceTracker->getDefaultUniformResetCalls(program->id());
// Create two lists of calls for uniforms, one for Setup, one for Reset
CallVector defaultUniformCalls({callsOut, &resetCalls[uniformLoc]});
// Samplers should be populated with GL_INT, regardless of return type
if (typeInfo.isSampler)
{
std::vector<GLint> uniformBuffer(uniformSize);
for (int index = 0; index < uniformCount; index++, readLoc.value++)
{
program->getUniformiv(context, readLoc,
uniformBuffer.data() + index * componentCount);
resourceTracker->setDefaultUniformBaseLocation(program->id(), readLoc, uniformLoc);
}
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform1iv(replayState, true, uniformLoc, uniformCount,
uniformBuffer.data()));
}
continue;
}
switch (typeInfo.componentType)
{
case GL_FLOAT:
{
std::vector<GLfloat> uniformBuffer(uniformSize);
for (int index = 0; index < uniformCount; index++, readLoc.value++)
{
program->getUniformfv(context, readLoc,
uniformBuffer.data() + index * componentCount);
resourceTracker->setDefaultUniformBaseLocation(program->id(), readLoc,
uniformLoc);
}
switch (typeInfo.type)
{
// Note: All matrix uniforms are populated without transpose
case GL_FLOAT_MAT4x3:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniformMatrix4x3fv(replayState, true, uniformLoc,
uniformCount, false,
uniformBuffer.data()));
}
break;
case GL_FLOAT_MAT4x2:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniformMatrix4x2fv(replayState, true, uniformLoc,
uniformCount, false,
uniformBuffer.data()));
}
break;
case GL_FLOAT_MAT4:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniformMatrix4fv(replayState, true, uniformLoc,
uniformCount, false,
uniformBuffer.data()));
}
break;
case GL_FLOAT_MAT3x4:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniformMatrix3x4fv(replayState, true, uniformLoc,
uniformCount, false,
uniformBuffer.data()));
}
break;
case GL_FLOAT_MAT3x2:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniformMatrix3x2fv(replayState, true, uniformLoc,
uniformCount, false,
uniformBuffer.data()));
}
break;
case GL_FLOAT_MAT3:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniformMatrix3fv(replayState, true, uniformLoc,
uniformCount, false,
uniformBuffer.data()));
}
break;
case GL_FLOAT_MAT2x4:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniformMatrix2x4fv(replayState, true, uniformLoc,
uniformCount, false,
uniformBuffer.data()));
}
break;
case GL_FLOAT_MAT2x3:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniformMatrix2x3fv(replayState, true, uniformLoc,
uniformCount, false,
uniformBuffer.data()));
}
break;
case GL_FLOAT_MAT2:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniformMatrix2fv(replayState, true, uniformLoc,
uniformCount, false,
uniformBuffer.data()));
}
break;
case GL_FLOAT_VEC4:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform4fv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
case GL_FLOAT_VEC3:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform3fv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
case GL_FLOAT_VEC2:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform2fv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
case GL_FLOAT:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform1fv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
default:
UNIMPLEMENTED();
break;
}
break;
}
case GL_INT:
{
std::vector<GLint> uniformBuffer(uniformSize);
for (int index = 0; index < uniformCount; index++, readLoc.value++)
{
program->getUniformiv(context, readLoc,
uniformBuffer.data() + index * componentCount);
resourceTracker->setDefaultUniformBaseLocation(program->id(), readLoc,
uniformLoc);
}
switch (componentCount)
{
case 4:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform4iv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
case 3:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform3iv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
case 2:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform2iv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
case 1:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform1iv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
default:
UNIMPLEMENTED();
break;
}
break;
}
case GL_BOOL:
case GL_UNSIGNED_INT:
{
std::vector<GLuint> uniformBuffer(uniformSize);
for (int index = 0; index < uniformCount; index++, readLoc.value++)
{
program->getUniformuiv(context, readLoc,
uniformBuffer.data() + index * componentCount);
resourceTracker->setDefaultUniformBaseLocation(program->id(), readLoc,
uniformLoc);
}
switch (componentCount)
{
case 4:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform4uiv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
case 3:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform3uiv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
case 2:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform2uiv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
case 1:
for (std::vector<CallCapture> *calls : defaultUniformCalls)
{
Capture(calls, CaptureUniform1uiv(replayState, true, uniformLoc,
uniformCount, uniformBuffer.data()));
}
break;
default:
UNIMPLEMENTED();
break;
}
break;
}
default:
UNIMPLEMENTED();
break;
}
}
}
void CaptureVertexPointerES1(std::vector<CallCapture> *setupCalls,
gl::State *replayState,
GLuint attribIndex,
const gl::VertexAttribute &attrib,
const gl::VertexBinding &binding)
{
switch (gl::GLES1Renderer::VertexArrayType(attribIndex))
{
case gl::ClientVertexArrayType::Vertex:
Capture(setupCalls,
CaptureVertexPointer(*replayState, true, attrib.format->channelCount,
attrib.format->vertexAttribType, binding.getStride(),
attrib.pointer));
break;
case gl::ClientVertexArrayType::Normal:
Capture(setupCalls,
CaptureNormalPointer(*replayState, true, attrib.format->vertexAttribType,
binding.getStride(), attrib.pointer));
break;
case gl::ClientVertexArrayType::Color:
Capture(setupCalls, CaptureColorPointer(*replayState, true, attrib.format->channelCount,
attrib.format->vertexAttribType,
binding.getStride(), attrib.pointer));
break;
case gl::ClientVertexArrayType::PointSize:
Capture(setupCalls,
CapturePointSizePointerOES(*replayState, true, attrib.format->vertexAttribType,
binding.getStride(), attrib.pointer));
break;
case gl::ClientVertexArrayType::TextureCoord:
Capture(setupCalls,
CaptureTexCoordPointer(*replayState, true, attrib.format->channelCount,
attrib.format->vertexAttribType, binding.getStride(),
attrib.pointer));
break;
default:
UNREACHABLE();
}
}
void CaptureTextureEnvironmentState(std::vector<CallCapture> *setupCalls,
gl::State *replayState,
const gl::State *apiState,
unsigned int unit)
{
const gl::TextureEnvironmentParameters &currentEnv = apiState->gles1().textureEnvironment(unit);
const gl::TextureEnvironmentParameters &defaultEnv =
replayState->gles1().textureEnvironment(unit);
if (currentEnv == defaultEnv)
{
return;
}
auto capIfNe = [setupCalls](auto currentState, auto defaultState, CallCapture &&call) {
if (currentState != defaultState)
{
setupCalls->emplace_back(std::move(call));
}
};
// When the texture env state differs on a non-default sampler unit, emit an ActiveTexture call.
// The default sampler unit is GL_TEXTURE0.
GLenum currentUnit = GL_TEXTURE0 + static_cast<GLenum>(unit);
GLenum defaultUnit = GL_TEXTURE0 + static_cast<GLenum>(replayState->getActiveSampler());
capIfNe(currentUnit, defaultUnit, CaptureActiveTexture(*replayState, true, currentUnit));
auto capEnum = [capIfNe, replayState](gl::TextureEnvParameter pname, auto currentState,
auto defaultState) {
capIfNe(currentState, defaultState,
CaptureTexEnvi(*replayState, true, gl::TextureEnvTarget::Env, pname,
ToGLenum(currentState)));
};
capEnum(gl::TextureEnvParameter::Mode, currentEnv.mode, defaultEnv.mode);
capEnum(gl::TextureEnvParameter::CombineRgb, currentEnv.combineRgb, defaultEnv.combineRgb);
capEnum(gl::TextureEnvParameter::CombineAlpha, currentEnv.combineAlpha,
defaultEnv.combineAlpha);
capEnum(gl::TextureEnvParameter::Src0Rgb, currentEnv.src0Rgb, defaultEnv.src0Rgb);
capEnum(gl::TextureEnvParameter::Src1Rgb, currentEnv.src1Rgb, defaultEnv.src1Rgb);
capEnum(gl::TextureEnvParameter::Src2Rgb, currentEnv.src2Rgb, defaultEnv.src2Rgb);
capEnum(gl::TextureEnvParameter::Src0Alpha, currentEnv.src0Alpha, defaultEnv.src0Alpha);
capEnum(gl::TextureEnvParameter::Src1Alpha, currentEnv.src1Alpha, defaultEnv.src1Alpha);
capEnum(gl::TextureEnvParameter::Src2Alpha, currentEnv.src2Alpha, defaultEnv.src2Alpha);
capEnum(gl::TextureEnvParameter::Op0Rgb, currentEnv.op0Rgb, defaultEnv.op0Rgb);
capEnum(gl::TextureEnvParameter::Op1Rgb, currentEnv.op1Rgb, defaultEnv.op1Rgb);
capEnum(gl::TextureEnvParameter::Op2Rgb, currentEnv.op2Rgb, defaultEnv.op2Rgb);
capEnum(gl::TextureEnvParameter::Op0Alpha, currentEnv.op0Alpha, defaultEnv.op0Alpha);
capEnum(gl::TextureEnvParameter::Op1Alpha, currentEnv.op1Alpha, defaultEnv.op1Alpha);
capEnum(gl::TextureEnvParameter::Op2Alpha, currentEnv.op2Alpha, defaultEnv.op2Alpha);
auto capFloat = [capIfNe, replayState](gl::TextureEnvParameter pname, auto currentState,
auto defaultState) {
capIfNe(currentState, defaultState,
CaptureTexEnvf(*replayState, true, gl::TextureEnvTarget::Env, pname, currentState));
};
capFloat(gl::TextureEnvParameter::RgbScale, currentEnv.rgbScale, defaultEnv.rgbScale);
capFloat(gl::TextureEnvParameter::AlphaScale, currentEnv.alphaScale, defaultEnv.alphaScale);
capIfNe(currentEnv.color, defaultEnv.color,
CaptureTexEnvfv(*replayState, true, gl::TextureEnvTarget::Env,
gl::TextureEnvParameter::Color, currentEnv.color.data()));
// PointCoordReplace is the only parameter that uses the PointSprite TextureEnvTarget.
capIfNe(currentEnv.pointSpriteCoordReplace, defaultEnv.pointSpriteCoordReplace,
CaptureTexEnvi(*replayState, true, gl::TextureEnvTarget::PointSprite,
gl::TextureEnvParameter::PointCoordReplace,
currentEnv.pointSpriteCoordReplace));
// In case of non-default sampler units, the default unit must be set back here.
capIfNe(currentUnit, defaultUnit, CaptureActiveTexture(*replayState, true, defaultUnit));
}
bool VertexBindingMatchesAttribStride(const gl::VertexAttribute &attrib,
const gl::VertexBinding &binding)
{
if (attrib.vertexAttribArrayStride == 0 &&
binding.getStride() == ComputeVertexAttributeTypeSize(attrib))
{
return true;
}
return attrib.vertexAttribArrayStride == binding.getStride();
}
void CaptureVertexArrayState(std::vector<CallCapture> *setupCalls,
const gl::Context *context,
const gl::VertexArray *vertexArray,
gl::State *replayState)
{
const std::vector<gl::VertexAttribute> &vertexAttribs = vertexArray->getVertexAttributes();
const std::vector<gl::VertexBinding> &vertexBindings = vertexArray->getVertexBindings();
gl::AttributesMask vertexPointerBindings;
ASSERT(vertexAttribs.size() <= vertexBindings.size());
for (GLuint attribIndex = 0; attribIndex < vertexAttribs.size(); ++attribIndex)
{
const gl::VertexAttribute defaultAttrib(attribIndex);
const gl::VertexBinding defaultBinding;
const gl::VertexAttribute &attrib = vertexAttribs[attribIndex];
const gl::VertexBinding &binding = vertexBindings[attrib.bindingIndex];
if (attrib.enabled != defaultAttrib.enabled)
{
if (context->isGLES1())
{
Capture(setupCalls,
CaptureEnableClientState(*replayState, false,
gl::GLES1Renderer::VertexArrayType(attribIndex)));
}
else
{
Capture(setupCalls,
CaptureEnableVertexAttribArray(*replayState, false, attribIndex));
}
}
// Don't capture CaptureVertexAttribPointer calls when a non-default VAO is bound, the array
// buffer is null and a non-null attrib pointer is used.
bool skipInvalidAttrib = vertexArray->id().value != 0 &&
binding.getBuffer().get() == nullptr && attrib.pointer != nullptr;
if (!skipInvalidAttrib &&
(attrib.format != defaultAttrib.format || attrib.pointer != defaultAttrib.pointer ||
binding.getStride() != defaultBinding.getStride() ||
attrib.bindingIndex != defaultAttrib.bindingIndex ||
binding.getBuffer().get() != nullptr))
{
// Each attribute can pull from a separate buffer, so check the binding
gl::Buffer *buffer = binding.getBuffer().get();
if (buffer != replayState->getArrayBuffer())
{
replayState->setBufferBinding(context, gl::BufferBinding::Array, buffer);
gl::BufferID bufferID = {0};
if (buffer)
{
bufferID = buffer->id();
}
Capture(setupCalls,
CaptureBindBuffer(*replayState, true, gl::BufferBinding::Array, bufferID));
}
// Establish the relationship between currently bound buffer and the VAO
if (context->isGLES1())
{
// Track indexes that used ES1 calls
vertexPointerBindings.set(attribIndex);
CaptureVertexPointerES1(setupCalls, replayState, attribIndex, attrib, binding);
}
else if (attrib.bindingIndex == attribIndex &&
VertexBindingMatchesAttribStride(attrib, binding) &&
(!buffer || binding.getOffset() == reinterpret_cast<GLintptr>(attrib.pointer)))
{
// Check if we can use strictly ES2 semantics, and track indexes that do.
vertexPointerBindings.set(attribIndex);
if (attrib.format->isPureInt())
{
Capture(setupCalls, CaptureVertexAttribIPointer(*replayState, true, attribIndex,
attrib.format->channelCount,
attrib.format->vertexAttribType,
attrib.vertexAttribArrayStride,
attrib.pointer));
}
else
{
Capture(setupCalls,
CaptureVertexAttribPointer(
*replayState, true, attribIndex, attrib.format->channelCount,
attrib.format->vertexAttribType, attrib.format->isNorm(),
attrib.vertexAttribArrayStride, attrib.pointer));
}
if (binding.getDivisor() != 0)
{
Capture(setupCalls, CaptureVertexAttribDivisor(*replayState, true, attribIndex,
binding.getDivisor()));
}
}
else
{
ASSERT(context->getClientVersion() >= gl::ES_3_1);
if (attrib.format->isPureInt())
{
Capture(setupCalls, CaptureVertexAttribIFormat(*replayState, true, attribIndex,
attrib.format->channelCount,
attrib.format->vertexAttribType,
attrib.relativeOffset));
}
else
{
Capture(setupCalls, CaptureVertexAttribFormat(*replayState, true, attribIndex,
attrib.format->channelCount,
attrib.format->vertexAttribType,
attrib.format->isNorm(),
attrib.relativeOffset));
}
Capture(setupCalls, CaptureVertexAttribBinding(*replayState, true, attribIndex,
attrib.bindingIndex));
}
}
}
// The loop below expects attribs and bindings to have equal counts
static_assert(gl::MAX_VERTEX_ATTRIBS == gl::MAX_VERTEX_ATTRIB_BINDINGS,
"Max vertex attribs and bindings count mismatch");
// Loop through binding indices that weren't used by VertexAttribPointer
for (size_t bindingIndex : vertexPointerBindings.flip())
{
const gl::VertexBinding &binding = vertexBindings[bindingIndex];
if (binding.getBuffer().id().value != 0)
{
Capture(setupCalls,
CaptureBindVertexBuffer(*replayState, true, static_cast<GLuint>(bindingIndex),
binding.getBuffer().id(), binding.getOffset(),
binding.getStride()));
}
if (binding.getDivisor() != 0)
{
Capture(setupCalls, CaptureVertexBindingDivisor(*replayState, true,
static_cast<GLuint>(bindingIndex),
binding.getDivisor()));
}
}
// The element array buffer is not per attribute, but per VAO
gl::Buffer *elementArrayBuffer = vertexArray->getElementArrayBuffer();
if (elementArrayBuffer)
{
Capture(setupCalls, CaptureBindBuffer(*replayState, true, gl::BufferBinding::ElementArray,
elementArrayBuffer->id()));
}
}
void CaptureTextureStorage(std::vector<CallCapture> *setupCalls,
gl::State *replayState,
const gl::Texture *texture)
{
// Use mip-level 0 for the base dimensions
gl::ImageIndex imageIndex = gl::ImageIndex::MakeFromType(texture->getType(), 0);
const gl::ImageDesc &desc = texture->getTextureState().getImageDesc(imageIndex);
switch (texture->getType())
{
case gl::TextureType::_2D:
case gl::TextureType::CubeMap:
{
Capture(setupCalls, CaptureTexStorage2D(*replayState, true, texture->getType(),
texture->getImmutableLevels(),
desc.format.info->internalFormat,
desc.size.width, desc.size.height));
break;
}
case gl::TextureType::_3D:
case gl::TextureType::_2DArray:
case gl::TextureType::CubeMapArray:
{
Capture(setupCalls, CaptureTexStorage3D(
*replayState, true, texture->getType(),
texture->getImmutableLevels(), desc.format.info->internalFormat,
desc.size.width, desc.size.height, desc.size.depth));
break;
}
case gl::TextureType::Buffer:
{
// Do nothing. This will already be captured as a buffer.
break;
}
default:
UNIMPLEMENTED();
break;
}
}
void CaptureTextureContents(std::vector<CallCapture> *setupCalls,
gl::State *replayState,
const gl::Texture *texture,
const gl::ImageIndex &index,
const gl::ImageDesc &desc,
GLuint size,
const void *data)
{
const gl::InternalFormat &format = *desc.format.info;
if (index.getType() == gl::TextureType::Buffer)
{
// Zero binding size indicates full buffer bound
if (texture->getBuffer().getSize() == 0)
{
Capture(setupCalls,
CaptureTexBufferEXT(*replayState, true, index.getType(), format.internalFormat,
texture->getBuffer().get()->id()));
}
else
{
Capture(setupCalls, CaptureTexBufferRangeEXT(*replayState, true, index.getType(),
format.internalFormat,
texture->getBuffer().get()->id(),
texture->getBuffer().getOffset(),
texture->getBuffer().getSize()));
}
// For buffers, we're done
return;
}
if (index.getType() == gl::TextureType::External)
{
// The generated glTexImage2D call is for creating the staging texture
Capture(setupCalls,
CaptureTexImage2D(*replayState, true, gl::TextureTarget::_2D, index.getLevelIndex(),
format.internalFormat, desc.size.width, desc.size.height, 0,
format.format, format.type, data));
// For external textures, we're done
return;
}
bool is3D =
(index.getType() == gl::TextureType::_3D || index.getType() == gl::TextureType::_2DArray ||
index.getType() == gl::TextureType::CubeMapArray);
if (format.compressed || format.paletted)
{
if (is3D)
{
if (texture->getImmutableFormat())
{
Capture(setupCalls,
CaptureCompressedTexSubImage3D(
*replayState, true, index.getTarget(), index.getLevelIndex(), 0, 0, 0,
desc.size.width, desc.size.height, desc.size.depth,
format.internalFormat, size, data));
}
else
{
Capture(setupCalls,
CaptureCompressedTexImage3D(*replayState, true, index.getTarget(),
index.getLevelIndex(), format.internalFormat,
desc.size.width, desc.size.height,
desc.size.depth, 0, size, data));
}
}
else
{
if (texture->getImmutableFormat())
{
Capture(setupCalls,
CaptureCompressedTexSubImage2D(
*replayState, true, index.getTarget(), index.getLevelIndex(), 0, 0,
desc.size.width, desc.size.height, format.internalFormat, size, data));
}
else
{
Capture(setupCalls, CaptureCompressedTexImage2D(
*replayState, true, index.getTarget(),
index.getLevelIndex(), format.internalFormat,
desc.size.width, desc.size.height, 0, size, data));
}
}
}
else
{
if (is3D)
{
if (texture->getImmutableFormat())
{
Capture(setupCalls,
CaptureTexSubImage3D(*replayState, true, index.getTarget(),
index.getLevelIndex(), 0, 0, 0, desc.size.width,
desc.size.height, desc.size.depth, format.format,
format.type, data));
}
else
{
Capture(
setupCalls,
CaptureTexImage3D(*replayState, true, index.getTarget(), index.getLevelIndex(),
format.internalFormat, desc.size.width, desc.size.height,
desc.size.depth, 0, format.format, format.type, data));
}
}
else
{
if (texture->getImmutableFormat())
{
Capture(setupCalls,
CaptureTexSubImage2D(*replayState, true, index.getTarget(),
index.getLevelIndex(), 0, 0, desc.size.width,
desc.size.height, format.format, format.type, data));
}
else
{
Capture(setupCalls, CaptureTexImage2D(*replayState, true, index.getTarget(),
index.getLevelIndex(), format.internalFormat,
desc.size.width, desc.size.height, 0,
format.format, format.type, data));
}
}
}
}
void CaptureCustomUniformBlockBinding(const CallCapture &callIn, std::vector<CallCapture> &callsOut)
{
const ParamBuffer &paramsIn = callIn.params;
const ParamCapture &programID =
paramsIn.getParam("programPacked", ParamType::TShaderProgramID, 0);
const ParamCapture &blockIndex =
paramsIn.getParam("uniformBlockIndexPacked", ParamType::TUniformBlockIndex, 1);
const ParamCapture &blockBinding =
paramsIn.getParam("uniformBlockBinding", ParamType::TGLuint, 2);
ParamBuffer params;
params.addValueParam("program", ParamType::TGLuint, programID.value.ShaderProgramIDVal.value);
params.addValueParam("uniformBlockIndex", ParamType::TGLuint,
blockIndex.value.UniformBlockIndexVal.value);
params.addValueParam("uniformBlockBinding", ParamType::TGLuint, blockBinding.value.GLuintVal);
callsOut.emplace_back("UniformBlockBinding", std::move(params));
}
void CaptureCustomMapBuffer(const char *entryPointName,
CallCapture &call,
std::vector<CallCapture> &callsOut,
gl::BufferID mappedBufferID)
{
call.params.addValueParam("buffer", ParamType::TGLuint, mappedBufferID.value);
callsOut.emplace_back(entryPointName, std::move(call.params));
}
void CaptureCustomShaderProgram(const char *name,
CallCapture &call,
std::vector<CallCapture> &callsOut)
{
call.params.addValueParam("shaderProgram", ParamType::TGLuint,
call.params.getReturnValue().value.GLuintVal);
call.customFunctionName = name;
callsOut.emplace_back(std::move(call));
}
void CaptureCustomFenceSync(CallCapture &call, std::vector<CallCapture> &callsOut)
{
ParamBuffer &&params = std::move(call.params);
params.addValueParam("fenceSync", ParamType::TGLuint64,
params.getReturnValue().value.GLuint64Val);
call.customFunctionName = "FenceSync2";
callsOut.emplace_back(std::move(call));
}
void CaptureCustomCreateEGLImage(const char *name,
CallCapture &call,
std::vector<CallCapture> &callsOut)
{
ParamBuffer &&params = std::move(call.params);
EGLImage returnVal = params.getReturnValue().value.EGLImageVal;
egl::ImageID imageID = egl::PackParam<egl::ImageID>(returnVal);
params.addValueParam("image", ParamType::TGLuint, imageID.value);
call.customFunctionName = name;
callsOut.emplace_back(std::move(call));
}
void CaptureCustomCreateEGLSync(const char *name,
CallCapture &call,
std::vector<CallCapture> &callsOut)
{
ParamBuffer &&params = std::move(call.params);
EGLSync returnVal = params.getReturnValue().value.EGLSyncVal;
egl::SyncID syncID = egl::PackParam<egl::SyncID>(returnVal);
params.addValueParam("sync", ParamType::TGLuint, syncID.value);
call.customFunctionName = name;
callsOut.emplace_back(std::move(call));
}
void CaptureCustomCreatePbufferSurface(CallCapture &call, std::vector<CallCapture> &callsOut)
{
ParamBuffer &&params = std::move(call.params);
EGLSurface returnVal = params.getReturnValue().value.EGLSurfaceVal;
egl::SurfaceID surfaceID = egl::PackParam<egl::SurfaceID>(returnVal);
params.addValueParam("surface", ParamType::TGLuint, surfaceID.value);
call.customFunctionName = "CreatePbufferSurface";
callsOut.emplace_back(std::move(call));
}
void CaptureCustomCreateNativeClientbuffer(CallCapture &call, std::vector<CallCapture> &callsOut)
{
ParamBuffer &&params = std::move(call.params);
params.addValueParam("clientBuffer", ParamType::TEGLClientBuffer,
params.getReturnValue().value.EGLClientBufferVal);
call.customFunctionName = "CreateNativeClientBufferANDROID";
callsOut.emplace_back(std::move(call));
}
void GenerateLinkedProgram(const gl::Context *context,
const gl::State &replayState,
ResourceTracker *resourceTracker,
std::vector<CallCapture> *setupCalls,
gl::Program *program,
gl::ShaderProgramID id,
gl::ShaderProgramID tempIDStart,
const ProgramSources &linkedSources)
{
// A map to store the gShaderProgram map lookup index of the temp shaders we attached below. We
// need this map to retrieve the lookup index to pass to CaptureDetachShader calls at the end of
// GenerateLinkedProgram.
PackedEnumMap<gl::ShaderType, gl::ShaderProgramID> tempShaderIDTracker;
// Compile with last linked sources.
for (gl::ShaderType shaderType : program->getExecutable().getLinkedShaderStages())
{
// Bump the max shader program id for each new tempIDStart we use to create, compile, and
// attach the temp shader object.
resourceTracker->onShaderProgramAccess(tempIDStart);
// Store the tempIDStart in the tempShaderIDTracker to retrieve for CaptureDetachShader
// calls later.
tempShaderIDTracker[shaderType] = tempIDStart;
const std::string &sourceString = linkedSources[shaderType];
const char *sourcePointer = sourceString.c_str();
if (sourceString.empty())
{
// If we don't have source for this shader, that means it was populated by the app
// using glProgramBinary. We need to look it up from our cached copy.
const ProgramSources &cachedLinkedSources =
context->getShareGroup()->getFrameCaptureShared()->getProgramSources(id);
const std::string &cachedSourceString = cachedLinkedSources[shaderType];
sourcePointer = cachedSourceString.c_str();
ASSERT(!cachedSourceString.empty());
}
// Compile and attach the temporary shader. Then free it immediately.
CallCapture createShader =
CaptureCreateShader(replayState, true, shaderType, tempIDStart.value);
CaptureCustomShaderProgram("CreateShader", createShader, *setupCalls);
Capture(setupCalls,
CaptureShaderSource(replayState, true, tempIDStart, 1, &sourcePointer, nullptr));
Capture(setupCalls, CaptureCompileShader(replayState, true, tempIDStart));
Capture(setupCalls, CaptureAttachShader(replayState, true, id, tempIDStart));
// Increment tempIDStart to get a new gShaderProgram map index for the next linked stage
// shader object. We can't reuse the same tempIDStart as we need to retrieve the index of
// each attached shader object later to pass to CaptureDetachShader calls.
tempIDStart.value += 1;
}
// Gather XFB varyings
std::vector<std::string> xfbVaryings;
for (const gl::TransformFeedbackVarying &xfbVarying :
program->getState().getLinkedTransformFeedbackVaryings())
{
xfbVaryings.push_back(xfbVarying.nameWithArrayIndex());
}
if (!xfbVaryings.empty())
{
std::vector<const char *> varyingsStrings;
for (const std::string &varyingString : xfbVaryings)
{
varyingsStrings.push_back(varyingString.data());
}
GLenum xfbMode = program->getState().getTransformFeedbackBufferMode();
Capture(setupCalls, CaptureTransformFeedbackVaryings(replayState, true, id,
static_cast<GLint>(xfbVaryings.size()),
varyingsStrings.data(), xfbMode));
}
// Force the attributes to be bound the same way as in the existing program.
// This can affect attributes that are optimized out in some implementations.
for (const gl::ProgramInput &attrib : program->getState().getProgramInputs())
{
if (gl::IsBuiltInName(attrib.name))
{
// Don't try to bind built-in attributes
continue;
}
// Separable programs may not have a VS, meaning it may not have attributes.
if (program->getExecutable().hasLinkedShaderStage(gl::ShaderType::Vertex))
{
ASSERT(attrib.getLocation() != -1);
Capture(setupCalls, CaptureBindAttribLocation(replayState, true, id,
static_cast<GLuint>(attrib.getLocation()),
attrib.name.c_str()));
}
}
if (program->isSeparable())
{
// MEC manually recreates separable programs, rather than attempting to recreate a call
// to glCreateShaderProgramv(), so insert a call to mark it separable.
Capture(setupCalls,
CaptureProgramParameteri(replayState, true, id, GL_PROGRAM_SEPARABLE, GL_TRUE));
}
Capture(setupCalls, CaptureLinkProgram(replayState, true, id));
CaptureUpdateUniformLocations(program, setupCalls);
CaptureUpdateUniformValues(replayState, context, program, resourceTracker, setupCalls);
CaptureUpdateUniformBlockIndexes(program, setupCalls);
// Capture uniform block bindings for each program
for (unsigned int uniformBlockIndex = 0;
uniformBlockIndex < program->getActiveUniformBlockCount(); uniformBlockIndex++)
{
GLuint blockBinding = program->getUniformBlockBinding(uniformBlockIndex);
CallCapture updateCallCapture =
CaptureUniformBlockBinding(replayState, true, id, {uniformBlockIndex}, blockBinding);
CaptureCustomUniformBlockBinding(updateCallCapture, *setupCalls);
}
// Add DetachShader call if that's what the app does, so that the
// ResourceManagerBase::mHandleAllocator can release the ShaderProgramID handle assigned to the
// shader object when glDeleteShader is called. This ensures the ShaderProgramID handles used in
// SetupReplayContextShared() are consistent with the ShaderProgramID handles used by the app.
for (gl::ShaderType shaderType : program->getExecutable().getLinkedShaderStages())
{
gl::Shader *attachedShader = program->getAttachedShader(shaderType);
if (attachedShader == nullptr)
{
Capture(setupCalls,
CaptureDetachShader(replayState, true, id, tempShaderIDTracker[shaderType]));
}
Capture(setupCalls,
CaptureDeleteShader(replayState, true, tempShaderIDTracker[shaderType]));
}
}
// TODO(http://anglebug.com/4599): Improve reset/restore call generation
// There are multiple ways to track reset calls for individual resources. For now, we are tracking
// separate lists of instructions that mirror the calls created during mid-execution setup. Other
// methods could involve passing the original CallCaptures to this function, or tracking the
// indices of original setup calls.
void CaptureBufferResetCalls(const gl::Context *context,
const gl::State &replayState,
ResourceTracker *resourceTracker,
gl::BufferID *id,
const gl::Buffer *buffer)
{
GLuint bufferID = (*id).value;
// Track this as a starting resource that may need to be restored.
TrackedResource &trackedBuffers =
resourceTracker->getTrackedResource(context->id(), ResourceIDType::Buffer);
// Track calls to regenerate a given buffer
ResourceCalls &bufferRegenCalls = trackedBuffers.getResourceRegenCalls();
Capture(&bufferRegenCalls[bufferID], CaptureGenBuffers(replayState, true, 1, id));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, &bufferRegenCalls[bufferID]);
// Call glBufferStorageEXT when regenerating immutable buffers,
// as we can't call glBufferData on restore.
if (buffer->isImmutable())
{
Capture(&bufferRegenCalls[bufferID],
CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, *id));
Capture(
&bufferRegenCalls[bufferID],
CaptureBufferStorageEXT(replayState, true, gl::BufferBinding::Array,
static_cast<GLsizeiptr>(buffer->getSize()),
buffer->getMapPointer(), buffer->getStorageExtUsageFlags()));
}
// Track calls to restore a given buffer's contents
ResourceCalls &bufferRestoreCalls = trackedBuffers.getResourceRestoreCalls();
Capture(&bufferRestoreCalls[bufferID],
CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, *id));
// Mutable buffers will be restored here using glBufferData.
// Immutable buffers need to be restored below, after maping.
if (!buffer->isImmutable())
{
Capture(&bufferRestoreCalls[bufferID],
CaptureBufferData(replayState, true, gl::BufferBinding::Array,
static_cast<GLsizeiptr>(buffer->getSize()),
buffer->getMapPointer(), buffer->getUsage()));
}
if (buffer->isMapped())
{
// Track calls to remap a buffer that started as mapped
BufferCalls &bufferMapCalls = resourceTracker->getBufferMapCalls();
Capture(&bufferMapCalls[bufferID],
CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, *id));
void *dontCare = nullptr;
CallCapture mapBufferRange = CaptureMapBufferRange(
replayState, true, gl::BufferBinding::Array,
static_cast<GLsizeiptr>(buffer->getMapOffset()),
static_cast<GLsizeiptr>(buffer->getMapLength()), buffer->getAccessFlags(), dontCare);
CaptureCustomMapBuffer("MapBufferRange", mapBufferRange, bufferMapCalls[bufferID],
buffer->id());
// Restore immutable mapped buffers. Needs to happen after mapping.
if (buffer->isImmutable())
{
ParamBuffer dataParamBuffer;
dataParamBuffer.addValueParam("dest", ParamType::TGLuint, buffer->id().value);
ParamCapture captureData("source", ParamType::TvoidConstPointer);
CaptureMemory(buffer->getMapPointer(), static_cast<GLsizeiptr>(buffer->getSize()),
&captureData);
dataParamBuffer.addParam(std::move(captureData));
dataParamBuffer.addValueParam<GLsizeiptr>("size", ParamType::TGLsizeiptr,
static_cast<GLsizeiptr>(buffer->getSize()));
bufferMapCalls[bufferID].emplace_back("UpdateClientBufferData",
std::move(dataParamBuffer));
}
}
// Track calls unmap a buffer that started as unmapped
BufferCalls &bufferUnmapCalls = resourceTracker->getBufferUnmapCalls();
Capture(&bufferUnmapCalls[bufferID],
CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, *id));
Capture(&bufferUnmapCalls[bufferID],
CaptureUnmapBuffer(replayState, true, gl::BufferBinding::Array, GL_TRUE));
}
void CaptureFenceSyncResetCalls(const gl::Context *context,
const gl::State &replayState,
ResourceTracker *resourceTracker,
gl::SyncID syncID,
GLsync syncObject,
const gl::Sync *sync)
{
// Track calls to regenerate a given fence sync
FenceSyncCalls &fenceSyncRegenCalls = resourceTracker->getFenceSyncRegenCalls();
CallCapture fenceSync =
CaptureFenceSync(replayState, true, sync->getCondition(), sync->getFlags(), syncObject);
CaptureCustomFenceSync(fenceSync, fenceSyncRegenCalls[syncID]);
MaybeCaptureUpdateResourceIDs(context, resourceTracker, &fenceSyncRegenCalls[syncID]);
}
void CaptureEGLSyncResetCalls(const gl::Context *context,
const gl::State &replayState,
ResourceTracker *resourceTracker,
egl::SyncID eglSyncID,
EGLSync eglSyncObject,
const egl::Sync *eglSync)
{
// Track this as a starting resource that may need to be restored.
TrackedResource &trackedEGLSyncs =
resourceTracker->getTrackedResource(context->id(), ResourceIDType::egl_Sync);
// Track calls to regenerate a given buffer
ResourceCalls &eglSyncRegenCalls = trackedEGLSyncs.getResourceRegenCalls();
CallCapture createEGLSync =
CaptureCreateSyncKHR(nullptr, true, context->getDisplay(), eglSync->getType(),
eglSync->getAttributeMap(), eglSyncObject);
CaptureCustomCreateEGLSync("CreateEGLSyncKHR", createEGLSync,
eglSyncRegenCalls[eglSyncID.value]);
MaybeCaptureUpdateResourceIDs(context, resourceTracker, &eglSyncRegenCalls[eglSyncID.value]);
}
void CaptureBufferBindingResetCalls(const gl::State &replayState,
ResourceTracker *resourceTracker,
gl::BufferBinding binding,
gl::BufferID id)
{
std::vector<CallCapture> &bufferBindingCalls = resourceTracker->getBufferBindingCalls();
Capture(&bufferBindingCalls, CaptureBindBuffer(replayState, true, binding, id));
}
void CaptureIndexedBuffers(const gl::State &glState,
const gl::BufferVector &indexedBuffers,
gl::BufferBinding binding,
std::vector<CallCapture> *setupCalls)
{
for (unsigned int index = 0; index < indexedBuffers.size(); ++index)
{
const gl::OffsetBindingPointer<gl::Buffer> &buffer = indexedBuffers[index];
if (buffer.get() == nullptr)
{
continue;
}
GLintptr offset = buffer.getOffset();
GLsizeiptr size = buffer.getSize();
gl::BufferID bufferID = buffer.get()->id();
// Context::bindBufferBase() calls Context::bindBufferRange() with size and offset = 0.
if ((offset == 0) && (size == 0))
{
Capture(setupCalls, CaptureBindBufferBase(glState, true, binding, index, bufferID));
}
else
{
Capture(setupCalls,
CaptureBindBufferRange(glState, true, binding, index, bufferID, offset, size));
}
}
}
void CaptureDefaultVertexAttribs(const gl::State &replayState,
const gl::State &apiState,
std::vector<CallCapture> *setupCalls)
{
const std::vector<gl::VertexAttribCurrentValueData> &currentValues =
apiState.getVertexAttribCurrentValues();
for (GLuint attribIndex = 0; attribIndex < currentValues.size(); ++attribIndex)
{
const gl::VertexAttribCurrentValueData &defaultValue = currentValues[attribIndex];
if (!IsDefaultCurrentValue(defaultValue))
{
Capture(setupCalls, CaptureVertexAttrib4fv(replayState, true, attribIndex,
defaultValue.Values.FloatValues));
}
}
}
void CompressPalettedTexture(angle::MemoryBuffer &data,
angle::MemoryBuffer &tmp,
const gl::InternalFormat &compressedFormat,
const gl::Extents &extents)
{
constexpr int uncompressedChannelCount = 4;
uint32_t indexBits = 0, redBlueBits = 0, greenBits = 0, alphaBits = 0;
switch (compressedFormat.internalFormat)
{
case GL_PALETTE4_RGB8_OES:
indexBits = 4;
redBlueBits = 8;
greenBits = 8;
alphaBits = 0;
break;
case GL_PALETTE4_RGBA8_OES:
indexBits = 4;
redBlueBits = 8;
greenBits = 8;
alphaBits = 8;
break;
case GL_PALETTE4_R5_G6_B5_OES:
indexBits = 4;
redBlueBits = 5;
greenBits = 6;
alphaBits = 0;
break;
case GL_PALETTE4_RGBA4_OES:
indexBits = 4;
redBlueBits = 4;
greenBits = 4;
alphaBits = 4;
break;
case GL_PALETTE4_RGB5_A1_OES:
indexBits = 4;
redBlueBits = 5;
greenBits = 5;
alphaBits = 1;
break;
case GL_PALETTE8_RGB8_OES:
indexBits = 8;
redBlueBits = 8;
greenBits = 8;
alphaBits = 0;
break;
case GL_PALETTE8_RGBA8_OES:
indexBits = 8;
redBlueBits = 8;
greenBits = 8;
alphaBits = 8;
break;
case GL_PALETTE8_R5_G6_B5_OES:
indexBits = 8;
redBlueBits = 5;
greenBits = 6;
alphaBits = 0;
break;
case GL_PALETTE8_RGBA4_OES:
indexBits = 8;
redBlueBits = 4;
greenBits = 4;
alphaBits = 4;
break;
case GL_PALETTE8_RGB5_A1_OES:
indexBits = 8;
redBlueBits = 5;
greenBits = 5;
alphaBits = 1;
break;
default:
UNREACHABLE();
break;
}
bool result = data.resize(
// Palette size
(1 << indexBits) * (2 * redBlueBits + greenBits + alphaBits) / 8 +
// Texels size
indexBits * extents.width * extents.height * extents.depth / 8);
ASSERT(result);
angle::StoreRGBA8ToPalettedImpl(
extents.width, extents.height, extents.depth, indexBits, redBlueBits, greenBits, alphaBits,
tmp.data(),
uncompressedChannelCount * extents.width, // inputRowPitch
uncompressedChannelCount * extents.width * extents.height, // inputDepthPitch
data.data(), // output
indexBits * extents.width / 8, // outputRowPitch
indexBits * extents.width * extents.height / 8 // outputDepthPitch
);
}
// Capture the setup of the state that's shared by all of the contexts in the share group
// See IsSharedObjectResource for the list of objects covered here.
void CaptureShareGroupMidExecutionSetup(
gl::Context *context,
std::vector<CallCapture> *setupCalls,
ResourceTracker *resourceTracker,
gl::State &replayState,
const PackedEnumMap<ResourceIDType, uint32_t> &maxAccessedResourceIDs)
{
FrameCaptureShared *frameCaptureShared = context->getShareGroup()->getFrameCaptureShared();
const gl::State &apiState = context->getState();
// Small helper function to make the code more readable.
auto cap = [setupCalls](CallCapture &&call) { setupCalls->emplace_back(std::move(call)); };
// Capture Buffer data.
const gl::BufferManager &buffers = apiState.getBufferManagerForCapture();
for (const auto &bufferIter : buffers)
{
gl::BufferID id = {bufferIter.first};
gl::Buffer *buffer = bufferIter.second;
if (id.value == 0)
{
continue;
}
// Generate binding.
cap(CaptureGenBuffers(replayState, true, 1, &id));
resourceTracker->getTrackedResource(context->id(), ResourceIDType::Buffer)
.getStartingResources()
.insert(id.value);
MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls);
// glBufferData. Would possibly be better implemented using a getData impl method.
// Saving buffers that are mapped during a swap is not yet handled.
if (buffer->getSize() == 0)
{
resourceTracker->setStartingBufferMapped(buffer->id().value, false);
continue;
}
// Remember if the buffer was already mapped
GLboolean bufferMapped = buffer->isMapped();
// If needed, map the buffer so we can capture its contents
if (!bufferMapped)
{
(void)buffer->mapRange(context, 0, static_cast<GLsizeiptr>(buffer->getSize()),
GL_MAP_READ_BIT);
}
// Always use the array buffer binding point to upload data to keep things simple.
if (buffer != replayState.getArrayBuffer())
{
replayState.setBufferBinding(context, gl::BufferBinding::Array, buffer);
cap(CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, id));
}
if (buffer->isImmutable())
{
cap(CaptureBufferStorageEXT(replayState, true, gl::BufferBinding::Array,
static_cast<GLsizeiptr>(buffer->getSize()),
buffer->getMapPointer(),
buffer->getStorageExtUsageFlags()));
}
else
{
cap(CaptureBufferData(replayState, true, gl::BufferBinding::Array,
static_cast<GLsizeiptr>(buffer->getSize()),
buffer->getMapPointer(), buffer->getUsage()));
}
if (bufferMapped)
{
void *dontCare = nullptr;
CallCapture mapBufferRange =
CaptureMapBufferRange(replayState, true, gl::BufferBinding::Array,
static_cast<GLsizeiptr>(buffer->getMapOffset()),
static_cast<GLsizeiptr>(buffer->getMapLength()),
buffer->getAccessFlags(), dontCare);
CaptureCustomMapBuffer("MapBufferRange", mapBufferRange, *setupCalls, buffer->id());
resourceTracker->setStartingBufferMapped(buffer->id().value, true);
frameCaptureShared->trackBufferMapping(
context, &setupCalls->back(), buffer->id(), buffer,
static_cast<GLsizeiptr>(buffer->getMapOffset()),
static_cast<GLsizeiptr>(buffer->getMapLength()),
(buffer->getAccessFlags() & GL_MAP_WRITE_BIT) != 0,
(buffer->getStorageExtUsageFlags() & GL_MAP_COHERENT_BIT_EXT) != 0);
}
else
{
resourceTracker->setStartingBufferMapped(buffer->id().value, false);
}
// Generate the calls needed to restore this buffer to original state for frame looping
CaptureBufferResetCalls(context, replayState, resourceTracker, &id, buffer);
// Unmap the buffer if it wasn't already mapped
if (!bufferMapped)
{
GLboolean dontCare;
(void)buffer->unmap(context, &dontCare);
}
}
// Clear the array buffer binding.
if (replayState.getTargetBuffer(gl::BufferBinding::Array))
{
cap(CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, {0}));
replayState.setBufferBinding(context, gl::BufferBinding::Array, nullptr);
}
// Set a unpack alignment of 1. Otherwise, computeRowPitch() will compute the wrong value,
// leading to a crash in memcpy() when capturing the texture contents.
gl::PixelUnpackState &currentUnpackState = replayState.getUnpackState();
if (currentUnpackState.alignment != 1)
{
cap(CapturePixelStorei(replayState, true, GL_UNPACK_ALIGNMENT, 1));
replayState.getMutablePrivateStateForCapture()->setUnpackAlignment(1);
}
// Capture Texture setup and data.
const gl::TextureManager &textures = apiState.getTextureManagerForCapture();
for (const auto &textureIter : textures)
{
gl::TextureID id = {textureIter.first};
gl::Texture *texture = textureIter.second;
if (id.value == 0)
{
continue;
}
// Track this as a starting resource that may need to be restored.
TrackedResource &trackedTextures =
resourceTracker->getTrackedResource(context->id(), ResourceIDType::Texture);
ResourceSet &startingTextures = trackedTextures.getStartingResources();
startingTextures.insert(id.value);
// For the initial texture creation calls, track in the generate list
ResourceCalls &textureRegenCalls = trackedTextures.getResourceRegenCalls();
CallVector texGenCalls({setupCalls, &textureRegenCalls[id.value]});
// Gen the Texture.
for (std::vector<CallCapture> *calls : texGenCalls)
{
Capture(calls, CaptureGenTextures(replayState, true, 1, &id));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, calls);
}
// For the remaining texture setup calls, track in the restore list
ResourceCalls &textureRestoreCalls = trackedTextures.getResourceRestoreCalls();
CallVector texSetupCalls({setupCalls, &textureRestoreCalls[id.value]});
for (std::vector<CallCapture> *calls : texSetupCalls)
{
Capture(calls, CaptureBindTexture(replayState, true, texture->getType(), id));
}
replayState.setSamplerTexture(context, texture->getType(), texture);
// Capture sampler parameter states.
// TODO(jmadill): More sampler / texture states. http://anglebug.com/3662
gl::SamplerState defaultSamplerState =
gl::SamplerState::CreateDefaultForTarget(texture->getType());
const gl::SamplerState &textureSamplerState = texture->getSamplerState();
auto capTexParam = [&replayState, texture, &texSetupCalls](GLenum pname, GLint param) {
for (std::vector<CallCapture> *calls : texSetupCalls)
{
Capture(calls,
CaptureTexParameteri(replayState, true, texture->getType(), pname, param));
}
};
auto capTexParamf = [&replayState, texture, &texSetupCalls](GLenum pname, GLfloat param) {
for (std::vector<CallCapture> *calls : texSetupCalls)
{
Capture(calls,
CaptureTexParameterf(replayState, true, texture->getType(), pname, param));
}
};
if (textureSamplerState.getMinFilter() != defaultSamplerState.getMinFilter())
{
capTexParam(GL_TEXTURE_MIN_FILTER, textureSamplerState.getMinFilter());
}
if (textureSamplerState.getMagFilter() != defaultSamplerState.getMagFilter())
{
capTexParam(GL_TEXTURE_MAG_FILTER, textureSamplerState.getMagFilter());
}
if (textureSamplerState.getWrapR() != defaultSamplerState.getWrapR())
{
capTexParam(GL_TEXTURE_WRAP_R, textureSamplerState.getWrapR());
}
if (textureSamplerState.getWrapS() != defaultSamplerState.getWrapS())
{
capTexParam(GL_TEXTURE_WRAP_S, textureSamplerState.getWrapS());
}
if (textureSamplerState.getWrapT() != defaultSamplerState.getWrapT())
{
capTexParam(GL_TEXTURE_WRAP_T, textureSamplerState.getWrapT());
}
if (textureSamplerState.getMinLod() != defaultSamplerState.getMinLod())
{
capTexParamf(GL_TEXTURE_MIN_LOD, textureSamplerState.getMinLod());
}
if (textureSamplerState.getMaxLod() != defaultSamplerState.getMaxLod())
{
capTexParamf(GL_TEXTURE_MAX_LOD, textureSamplerState.getMaxLod());
}
if (textureSamplerState.getCompareMode() != defaultSamplerState.getCompareMode())
{
capTexParam(GL_TEXTURE_COMPARE_MODE, textureSamplerState.getCompareMode());
}
if (textureSamplerState.getCompareFunc() != defaultSamplerState.getCompareFunc())
{
capTexParam(GL_TEXTURE_COMPARE_FUNC, textureSamplerState.getCompareFunc());
}
// Texture parameters
if (texture->getSwizzleRed() != GL_RED)
{
capTexParam(GL_TEXTURE_SWIZZLE_R, texture->getSwizzleRed());
}
if (texture->getSwizzleGreen() != GL_GREEN)
{
capTexParam(GL_TEXTURE_SWIZZLE_G, texture->getSwizzleGreen());
}
if (texture->getSwizzleBlue() != GL_BLUE)
{
capTexParam(GL_TEXTURE_SWIZZLE_B, texture->getSwizzleBlue());
}
if (texture->getSwizzleAlpha() != GL_ALPHA)
{
capTexParam(GL_TEXTURE_SWIZZLE_A, texture->getSwizzleAlpha());
}
if (texture->getBaseLevel() != 0)
{
capTexParam(GL_TEXTURE_BASE_LEVEL, texture->getBaseLevel());
}
if (texture->getMaxLevel() != 1000)
{
capTexParam(GL_TEXTURE_MAX_LEVEL, texture->getMaxLevel());
}
// If the texture is immutable, initialize it with TexStorage
if (texture->getImmutableFormat())
{
// We can only call TexStorage *once* on an immutable texture, so it needs special
// handling. To solve this, immutable textures will have a BindTexture and TexStorage as
// part of their textureRegenCalls. The resulting regen sequence will be:
//
// const GLuint glDeleteTextures_texturesPacked_0[] = { gTextureMap[52] };
// glDeleteTextures(1, glDeleteTextures_texturesPacked_0);
// glGenTextures(1, reinterpret_cast<GLuint *>(gReadBuffer));
// UpdateTextureID(52, 0);
// glBindTexture(GL_TEXTURE_2D, gTextureMap[52]);
// glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8, 256, 512);
// Bind the texture first just for textureRegenCalls
Capture(&textureRegenCalls[id.value],
CaptureBindTexture(replayState, true, texture->getType(), id));
// Then add TexStorage to texGenCalls instead of texSetupCalls
for (std::vector<CallCapture> *calls : texGenCalls)
{
CaptureTextureStorage(calls, &replayState, texture);
}
}
// Iterate texture levels and layers.
gl::ImageIndexIterator imageIter = gl::ImageIndexIterator::MakeGeneric(
texture->getType(), 0, texture->getMipmapMaxLevel() + 1, gl::ImageIndex::kEntireLevel,
gl::ImageIndex::kEntireLevel);
while (imageIter.hasNext())
{
gl::ImageIndex index = imageIter.next();
const gl::ImageDesc &desc = texture->getTextureState().getImageDesc(index);
if (desc.size.empty())
{
continue;
}
const gl::InternalFormat &format = *desc.format.info;
bool supportedType = (index.getType() == gl::TextureType::_2D ||
index.getType() == gl::TextureType::_3D ||
index.getType() == gl::TextureType::_2DArray ||
index.getType() == gl::TextureType::Buffer ||
index.getType() == gl::TextureType::CubeMap ||
index.getType() == gl::TextureType::CubeMapArray ||
index.getType() == gl::TextureType::External);
// Check for supported textures
if (!supportedType)
{
ERR() << "Unsupported texture type: " << index.getType();
UNREACHABLE();
}
if (index.getType() == gl::TextureType::Buffer)
{
// The buffer contents are already backed up, but we need to emit the TexBuffer
// binding calls
for (std::vector<CallCapture> *calls : texSetupCalls)
{
CaptureTextureContents(calls, &replayState, texture, index, desc, 0, 0);
}
continue;
}
// create a staging GL_TEXTURE_2D texture to create the eglImage with
gl::TextureID stagingTexId = {maxAccessedResourceIDs[ResourceIDType::Texture] + 1};
if (index.getType() == gl::TextureType::External)
{
Capture(setupCalls, CaptureGenTextures(replayState, true, 1, &stagingTexId));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls);
Capture(setupCalls,
CaptureBindTexture(replayState, true, gl::TextureType::_2D, stagingTexId));
Capture(setupCalls, CaptureTexParameteri(replayState, true, gl::TextureType::_2D,
GL_TEXTURE_MIN_FILTER, GL_NEAREST));
Capture(setupCalls, CaptureTexParameteri(replayState, true, gl::TextureType::_2D,
GL_TEXTURE_MAG_FILTER, GL_NEAREST));
}
if (context->getExtensions().getImageANGLE)
{
// Use ANGLE_get_image to read back pixel data.
angle::MemoryBuffer data;
const gl::Extents extents(desc.size.width, desc.size.height, desc.size.depth);
gl::PixelPackState packState;
packState.alignment = 1;
if (format.paletted)
{
// Read back the uncompressed texture, then re-compress it
// to store in the trace.
angle::MemoryBuffer tmp;
// The uncompressed format (R8G8B8A8) is 4 bytes per texel
bool result = tmp.resize(4 * extents.width * extents.height * extents.depth);
ASSERT(result);
(void)texture->getTexImage(context, packState, nullptr, index.getTarget(),
index.getLevelIndex(), GL_RGBA, GL_UNSIGNED_BYTE,
tmp.data());
CompressPalettedTexture(data, tmp, format, extents);
}
else if (format.compressed)
{
// Calculate the size needed to store the compressed level
GLuint sizeInBytes;
bool result = format.computeCompressedImageSize(extents, &sizeInBytes);
ASSERT(result);
result = data.resize(sizeInBytes);
ASSERT(result);
(void)texture->getCompressedTexImage(context, packState, nullptr,
index.getTarget(), index.getLevelIndex(),
data.data());
}
else
{
GLenum getFormat = format.format;
GLenum getType = format.type;
const gl::PixelUnpackState &unpack = apiState.getUnpackState();
GLuint endByte = 0;
bool unpackSize =
format.computePackUnpackEndByte(getType, extents, unpack, true, &endByte);
ASSERT(unpackSize);
bool result = data.resize(endByte);
ASSERT(result);
(void)texture->getTexImage(context, packState, nullptr, index.getTarget(),
index.getLevelIndex(), getFormat, getType,
data.data());
}
for (std::vector<CallCapture> *calls : texSetupCalls)
{
CaptureTextureContents(calls, &replayState, texture, index, desc,
static_cast<GLuint>(data.size()), data.data());
}
if (index.getType() == gl::TextureType::External)
{
// Look up the attribs used when the image was created
// Firstly, lookup the eglImage ID associated with this texture when the app
// issued glEGLImageTargetTexture2DOES()
auto eglImageIter = resourceTracker->getTextureIDToImageTable().find(id.value);
ASSERT(eglImageIter != resourceTracker->getTextureIDToImageTable().end());
const egl::ImageID eglImageID = eglImageIter->second;
const EGLImage eglImage =
reinterpret_cast<EGLImage>(static_cast<uintptr_t>(eglImageID.value));
// Secondly, lookup the attrib we used to create the eglImage
auto eglImageAttribIter =
resourceTracker->getImageToAttribTable().find(eglImage);
ASSERT(eglImageAttribIter != resourceTracker->getImageToAttribTable().end());
const egl::AttributeMap &retrievedAttribs = eglImageAttribIter->second;
// Create the image on demand with the same attrib retrieved above
CallCapture eglCreateImageKHRCall = egl::CaptureCreateImageKHR(
nullptr, true, nullptr, context->id(), EGL_GL_TEXTURE_2D_KHR,
reinterpret_cast<EGLClientBuffer>(
static_cast<GLuint64>(stagingTexId.value)),
retrievedAttribs, eglImage);
// Convert the CaptureCreateImageKHR CallCapture to the customized CallCapture
std::vector<CallCapture> eglCustomCreateImageKHRCall;
CaptureCustomCreateEGLImage("CreateEGLImageKHR", eglCreateImageKHRCall,
eglCustomCreateImageKHRCall);
ASSERT(eglCustomCreateImageKHRCall.size() > 0);
// Append the customized CallCapture to the setupCalls list
Capture(setupCalls, std::move(eglCustomCreateImageKHRCall[0]));
// Pass the eglImage to the texture that is bound to GL_TEXTURE_EXTERNAL_OES
// target
for (std::vector<CallCapture> *calls : texSetupCalls)
{
Capture(calls,
CaptureEGLImageTargetTexture2DOES(
replayState, true, gl::TextureType::External, eglImageID));
}
// Delete the staging texture
Capture(setupCalls, CaptureDeleteTextures(replayState, true, 1, &stagingTexId));
}
}
else
{
for (std::vector<CallCapture> *calls : texSetupCalls)
{
CaptureTextureContents(calls, &replayState, texture, index, desc, 0, nullptr);
}
}
}
}
// Capture Renderbuffers.
const gl::RenderbufferManager &renderbuffers = apiState.getRenderbufferManagerForCapture();
FramebufferCaptureFuncs framebufferFuncs(context->isGLES1());
for (const auto &renderbufIter : renderbuffers)
{
gl::RenderbufferID id = {renderbufIter.first};
const gl::Renderbuffer *renderbuffer = renderbufIter.second;
// Track this as a starting resource that may need to be restored.
TrackedResource &trackedRenderbuffers =
resourceTracker->getTrackedResource(context->id(), ResourceIDType::Renderbuffer);
ResourceSet &startingRenderbuffers = trackedRenderbuffers.getStartingResources();
startingRenderbuffers.insert(id.value);
// For the initial renderbuffer creation calls, track in the generate list
ResourceCalls &renderbufferRegenCalls = trackedRenderbuffers.getResourceRegenCalls();
CallVector rbGenCalls({setupCalls, &renderbufferRegenCalls[id.value]});
// Generate renderbuffer id.
for (std::vector<CallCapture> *calls : rbGenCalls)
{
Capture(calls, framebufferFuncs.genRenderbuffers(replayState, true, 1, &id));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, calls);
Capture(calls,
framebufferFuncs.bindRenderbuffer(replayState, true, GL_RENDERBUFFER, id));
}
GLenum internalformat = renderbuffer->getFormat().info->internalFormat;
if (renderbuffer->getSamples() > 0)
{
// Note: We could also use extensions if available.
for (std::vector<CallCapture> *calls : rbGenCalls)
{
Capture(calls,
CaptureRenderbufferStorageMultisample(
replayState, true, GL_RENDERBUFFER, renderbuffer->getSamples(),
internalformat, renderbuffer->getWidth(), renderbuffer->getHeight()));
}
}
else
{
for (std::vector<CallCapture> *calls : rbGenCalls)
{
Capture(calls, framebufferFuncs.renderbufferStorage(
replayState, true, GL_RENDERBUFFER, internalformat,
renderbuffer->getWidth(), renderbuffer->getHeight()));
}
}
// TODO: Capture renderbuffer contents. http://anglebug.com/3662
}
// Capture Shaders and Programs.
const gl::ShaderProgramManager &shadersAndPrograms =
apiState.getShaderProgramManagerForCapture();
const gl::ResourceMap<gl::Shader, gl::ShaderProgramID> &shaders =
shadersAndPrograms.getShadersForCapture();
const gl::ResourceMap<gl::Program, gl::ShaderProgramID> &programs =
shadersAndPrograms.getProgramsForCaptureAndPerf();
TrackedResource &trackedShaderPrograms =
resourceTracker->getTrackedResource(context->id(), ResourceIDType::ShaderProgram);
// Capture Program binary state.
gl::ShaderProgramID tempShaderStartID = {resourceTracker->getMaxShaderPrograms()};
for (const auto &programIter : programs)
{
gl::ShaderProgramID id = {programIter.first};
gl::Program *program = programIter.second;
// Unlinked programs don't have an executable. Thus they don't need to be captured.
// Programs are shared by contexts in the share group and only need to be captured once.
if (!program->isLinked())
{
continue;
}
size_t programSetupStart = setupCalls->size();
// Get last linked shader source.
const ProgramSources &linkedSources =
context->getShareGroup()->getFrameCaptureShared()->getProgramSources(id);
// Create two lists for program regen calls
ResourceCalls &shaderProgramRegenCalls = trackedShaderPrograms.getResourceRegenCalls();
CallVector programRegenCalls({setupCalls, &shaderProgramRegenCalls[id.value]});
for (std::vector<CallCapture> *calls : programRegenCalls)
{
CallCapture createProgram = CaptureCreateProgram(replayState, true, id.value);
CaptureCustomShaderProgram("CreateProgram", createProgram, *calls);
}
// Create two lists for program restore calls
ResourceCalls &shaderProgramRestoreCalls = trackedShaderPrograms.getResourceRestoreCalls();
CallVector programRestoreCalls({setupCalls, &shaderProgramRestoreCalls[id.value]});
for (std::vector<CallCapture> *calls : programRestoreCalls)
{
GenerateLinkedProgram(context, replayState, resourceTracker, calls, program, id,
tempShaderStartID, linkedSources);
}
// Update the program in replayState
if (!replayState.getProgram() || replayState.getProgram()->id() != program->id())
{
// Note: We don't do this in GenerateLinkedProgram because it can't modify state
(void)replayState.setProgram(context, program);
}
resourceTracker->getTrackedResource(context->id(), ResourceIDType::ShaderProgram)
.getStartingResources()
.insert(id.value);
resourceTracker->setShaderProgramType(id, ShaderProgramType::ProgramType);
size_t programSetupEnd = setupCalls->size();
// Mark the range of calls used to setup this program
frameCaptureShared->markResourceSetupCallsInactive(
setupCalls, ResourceIDType::ShaderProgram, id.value,
gl::Range<size_t>(programSetupStart, programSetupEnd));
}
// Handle shaders.
for (const auto &shaderIter : shaders)
{
gl::ShaderProgramID id = {shaderIter.first};
gl::Shader *shader = shaderIter.second;
// Skip shaders scheduled for deletion.
// Shaders are shared by contexts in the share group and only need to be captured once.
if (shader->hasBeenDeleted())
{
continue;
}
size_t shaderSetupStart = setupCalls->size();
// Create two lists for shader regen calls
ResourceCalls &shaderProgramRegenCalls = trackedShaderPrograms.getResourceRegenCalls();
CallVector shaderRegenCalls({setupCalls, &shaderProgramRegenCalls[id.value]});
for (std::vector<CallCapture> *calls : shaderRegenCalls)
{
CallCapture createShader =
CaptureCreateShader(replayState, true, shader->getType(), id.value);
CaptureCustomShaderProgram("CreateShader", createShader, *calls);
}
std::string shaderSource = shader->getSourceString();
const char *sourcePointer = shaderSource.empty() ? nullptr : shaderSource.c_str();
// Create two lists for shader restore calls
ResourceCalls &shaderProgramRestoreCalls = trackedShaderPrograms.getResourceRestoreCalls();
CallVector shaderRestoreCalls({setupCalls, &shaderProgramRestoreCalls[id.value]});
// This does not handle some more tricky situations like attaching shaders to a non-linked
// program. Or attaching uncompiled shaders. Or attaching and then deleting a shader.
// TODO(jmadill): Handle trickier program uses. http://anglebug.com/3662
if (shader->isCompiled(context))
{
const std::string &capturedSource =
context->getShareGroup()->getFrameCaptureShared()->getShaderSource(id);
if (capturedSource != shaderSource)
{
ASSERT(!capturedSource.empty());
sourcePointer = capturedSource.c_str();
}
for (std::vector<CallCapture> *calls : shaderRestoreCalls)
{
Capture(calls,
CaptureShaderSource(replayState, true, id, 1, &sourcePointer, nullptr));
Capture(calls, CaptureCompileShader(replayState, true, id));
}
}
if (sourcePointer &&
(!shader->isCompiled(context) || sourcePointer != shaderSource.c_str()))
{
for (std::vector<CallCapture> *calls : shaderRestoreCalls)
{
Capture(calls,
CaptureShaderSource(replayState, true, id, 1, &sourcePointer, nullptr));
}
}
size_t shaderSetupEnd = setupCalls->size();
// Mark the range of calls used to setup this shader
frameCaptureShared->markResourceSetupCallsInactive(
setupCalls, ResourceIDType::ShaderProgram, id.value,
gl::Range<size_t>(shaderSetupStart, shaderSetupEnd));
resourceTracker->getTrackedResource(context->id(), ResourceIDType::ShaderProgram)
.getStartingResources()
.insert(id.value);
resourceTracker->setShaderProgramType(id, ShaderProgramType::ShaderType);
}
// Capture Sampler Objects
const gl::SamplerManager &samplers = apiState.getSamplerManagerForCapture();
for (const auto &samplerIter : samplers)
{
gl::SamplerID samplerID = {samplerIter.first};
// Don't gen the sampler if we've seen it before, since they are shared across the context
// share group.
cap(CaptureGenSamplers(replayState, true, 1, &samplerID));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls);
gl::Sampler *sampler = samplerIter.second;
if (!sampler)
{
continue;
}
gl::SamplerState defaultSamplerState;
if (sampler->getMinFilter() != defaultSamplerState.getMinFilter())
{
cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_MIN_FILTER,
sampler->getMinFilter()));
}
if (sampler->getMagFilter() != defaultSamplerState.getMagFilter())
{
cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_MAG_FILTER,
sampler->getMagFilter()));
}
if (sampler->getWrapS() != defaultSamplerState.getWrapS())
{
cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_WRAP_S,
sampler->getWrapS()));
}
if (sampler->getWrapR() != defaultSamplerState.getWrapR())
{
cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_WRAP_R,
sampler->getWrapR()));
}
if (sampler->getWrapT() != defaultSamplerState.getWrapT())
{
cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_WRAP_T,
sampler->getWrapT()));
}
if (sampler->getMinLod() != defaultSamplerState.getMinLod())
{
cap(CaptureSamplerParameterf(replayState, true, samplerID, GL_TEXTURE_MIN_LOD,
sampler->getMinLod()));
}
if (sampler->getMaxLod() != defaultSamplerState.getMaxLod())
{
cap(CaptureSamplerParameterf(replayState, true, samplerID, GL_TEXTURE_MAX_LOD,
sampler->getMaxLod()));
}
if (sampler->getCompareMode() != defaultSamplerState.getCompareMode())
{
cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_COMPARE_MODE,
sampler->getCompareMode()));
}
if (sampler->getCompareFunc() != defaultSamplerState.getCompareFunc())
{
cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_COMPARE_FUNC,
sampler->getCompareFunc()));
}
}
// Capture Sync Objects
const gl::SyncManager &syncs = apiState.getSyncManagerForCapture();
for (const auto &syncIter : syncs)
{
gl::SyncID syncID = {syncIter.first};
const gl::Sync *sync = syncIter.second;
GLsync syncObject = gl::unsafe_int_to_pointer_cast<GLsync>(syncID.value);
if (!sync)
{
continue;
}
CallCapture fenceSync =
CaptureFenceSync(replayState, true, sync->getCondition(), sync->getFlags(), syncObject);
CaptureCustomFenceSync(fenceSync, *setupCalls);
CaptureFenceSyncResetCalls(context, replayState, resourceTracker, syncID, syncObject, sync);
resourceTracker->getStartingFenceSyncs().insert(syncID);
}
// Capture EGL Sync Objects
const egl::SyncMap eglSyncMap = context->getDisplay()->getSyncsForCapture();
for (const auto &eglSyncIter : eglSyncMap)
{
egl::SyncID eglSyncID = {eglSyncIter.first};
const egl::Sync *eglSync = eglSyncIter.second;
EGLSync eglSyncObject = gl::unsafe_int_to_pointer_cast<EGLSync>(eglSyncID.value);
if (!eglSync)
{
continue;
}
CallCapture createEGLSync =
CaptureCreateSync(nullptr, true, context->getDisplay(), eglSync->getType(),
eglSync->getAttributeMap(), eglSyncObject);
CaptureCustomCreateEGLSync("CreateEGLSyncKHR", createEGLSync, *setupCalls);
CaptureEGLSyncResetCalls(context, replayState, resourceTracker, eglSyncID, eglSyncObject,
eglSync);
resourceTracker->getTrackedResource(context->id(), ResourceIDType::egl_Sync)
.getStartingResources()
.insert(eglSyncID.value);
}
GLint contextUnpackAlignment = context->getState().getUnpackState().alignment;
if (currentUnpackState.alignment != contextUnpackAlignment)
{
cap(CapturePixelStorei(replayState, true, GL_UNPACK_ALIGNMENT, contextUnpackAlignment));
replayState.getMutablePrivateStateForCapture()->setUnpackAlignment(contextUnpackAlignment);
}
}
void CaptureMidExecutionSetup(const gl::Context *context,
std::vector<CallCapture> *setupCalls,
CallResetMap &resetCalls,
std::vector<CallCapture> *shareGroupSetupCalls,
ResourceIDToSetupCallsMap *resourceIDToSetupCalls,
ResourceTracker *resourceTracker,
gl::State &replayState,
bool validationEnabled)
{
const gl::State &apiState = context->getState();
// Small helper function to make the code more readable.
auto cap = [setupCalls](CallCapture &&call) { setupCalls->emplace_back(std::move(call)); };
cap(egl::CaptureMakeCurrent(nullptr, true, nullptr, {0}, {0}, context->id(), EGL_TRUE));
// Vertex input states. Must happen after buffer data initialization. Do not capture on GLES1.
if (!context->isGLES1())
{
CaptureDefaultVertexAttribs(replayState, apiState, setupCalls);
}
// Capture vertex array objects
VertexArrayCaptureFuncs vertexArrayFuncs(context->isGLES1());
const gl::VertexArrayMap &vertexArrayMap = context->getVertexArraysForCapture();
gl::VertexArrayID boundVertexArrayID = {0};
for (const auto &vertexArrayIter : vertexArrayMap)
{
TrackedResource &trackedVertexArrays =
resourceTracker->getTrackedResource(context->id(), ResourceIDType::VertexArray);
gl::VertexArrayID vertexArrayID = {vertexArrayIter.first};
// Track this as a starting resource that may need to be restored
resourceTracker->getTrackedResource(context->id(), ResourceIDType::VertexArray)
.getStartingResources()
.insert(vertexArrayID.value);
// Create two lists of calls for initial setup
ResourceCalls &vertexArrayRegenCalls = trackedVertexArrays.getResourceRegenCalls();
CallVector vertexArrayGenCalls({setupCalls, &vertexArrayRegenCalls[vertexArrayID.value]});
if (vertexArrayID.value != 0)
{
// Gen the vertex array
for (std::vector<CallCapture> *calls : vertexArrayGenCalls)
{
Capture(calls,
vertexArrayFuncs.genVertexArrays(replayState, true, 1, &vertexArrayID));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, calls);
}
}
// Create two lists of calls for populating the vertex array
ResourceCalls &vertexArrayRestoreCalls = trackedVertexArrays.getResourceRestoreCalls();
CallVector vertexArraySetupCalls(
{setupCalls, &vertexArrayRestoreCalls[vertexArrayID.value]});
if (vertexArrayIter.second)
{
const gl::VertexArray *vertexArray = vertexArrayIter.second;
// Populate the vertex array
for (std::vector<CallCapture> *calls : vertexArraySetupCalls)
{
// Bind the vertexArray (if needed) and populate it
if (vertexArrayID != boundVertexArrayID)
{
Capture(calls,
vertexArrayFuncs.bindVertexArray(replayState, true, vertexArrayID));
}
CaptureVertexArrayState(calls, context, vertexArray, &replayState);
}
boundVertexArrayID = vertexArrayID;
}
}
// Bind the current vertex array
const gl::VertexArray *currentVertexArray = apiState.getVertexArray();
if (currentVertexArray->id() != boundVertexArrayID)
{
cap(vertexArrayFuncs.bindVertexArray(replayState, true, currentVertexArray->id()));
}
// Track the calls necessary to bind the vertex array back to initial state
Capture(&resetCalls[angle::EntryPoint::GLBindVertexArray],
vertexArrayFuncs.bindVertexArray(replayState, true, currentVertexArray->id()));
// Capture indexed buffer bindings.
const gl::BufferVector &uniformIndexedBuffers =
apiState.getOffsetBindingPointerUniformBuffers();
const gl::BufferVector &atomicCounterIndexedBuffers =
apiState.getOffsetBindingPointerAtomicCounterBuffers();
const gl::BufferVector &shaderStorageIndexedBuffers =
apiState.getOffsetBindingPointerShaderStorageBuffers();
CaptureIndexedBuffers(replayState, uniformIndexedBuffers, gl::BufferBinding::Uniform,
setupCalls);
CaptureIndexedBuffers(replayState, atomicCounterIndexedBuffers,
gl::BufferBinding::AtomicCounter, setupCalls);
CaptureIndexedBuffers(replayState, shaderStorageIndexedBuffers,
gl::BufferBinding::ShaderStorage, setupCalls);
// Capture Buffer bindings.
const gl::BoundBufferMap &boundBuffers = apiState.getBoundBuffersForCapture();
for (gl::BufferBinding binding : angle::AllEnums<gl::BufferBinding>())
{
gl::BufferID bufferID = boundBuffers[binding].id();
// Filter out redundant buffer binding commands. Note that the code in the previous section
// only binds to ARRAY_BUFFER. Therefore we only check the array binding against the binding
// we set earlier.
bool isArray = binding == gl::BufferBinding::Array;
const gl::Buffer *arrayBuffer = replayState.getArrayBuffer();
if ((isArray && arrayBuffer && arrayBuffer->id() != bufferID) ||
(!isArray && bufferID.value != 0))
{
cap(CaptureBindBuffer(replayState, true, binding, bufferID));
replayState.setBufferBinding(context, binding, boundBuffers[binding].get());
}
// Restore all buffer bindings for Reset
if (bufferID.value != 0)
{
CaptureBufferBindingResetCalls(replayState, resourceTracker, binding, bufferID);
}
}
// Set a unpack alignment of 1. Otherwise, computeRowPitch() will compute the wrong value,
// leading to a crash in memcpy() when capturing the texture contents.
gl::PixelUnpackState &currentUnpackState = replayState.getUnpackState();
if (currentUnpackState.alignment != 1)
{
cap(CapturePixelStorei(replayState, true, GL_UNPACK_ALIGNMENT, 1));
replayState.getMutablePrivateStateForCapture()->setUnpackAlignment(1);
}
// Capture Texture setup and data.
const gl::TextureBindingMap &apiBoundTextures = apiState.getBoundTexturesForCapture();
// Set Texture bindings.
for (gl::TextureType textureType : angle::AllEnums<gl::TextureType>())
{
const gl::TextureBindingVector &apiBindings = apiBoundTextures[textureType];
const gl::TextureBindingVector &replayBindings =
replayState.getBoundTexturesForCapture()[textureType];
ASSERT(apiBindings.size() == replayBindings.size());
for (size_t bindingIndex = 0; bindingIndex < apiBindings.size(); ++bindingIndex)
{
gl::TextureID apiTextureID = apiBindings[bindingIndex].id();
gl::TextureID replayTextureID = replayBindings[bindingIndex].id();
if (apiTextureID != replayTextureID)
{
if (replayState.getActiveSampler() != bindingIndex)
{
cap(CaptureActiveTexture(replayState, true,
GL_TEXTURE0 + static_cast<GLenum>(bindingIndex)));
replayState.getMutablePrivateStateForCapture()->setActiveSampler(
static_cast<unsigned int>(bindingIndex));
}
cap(CaptureBindTexture(replayState, true, textureType, apiTextureID));
replayState.setSamplerTexture(context, textureType,
apiBindings[bindingIndex].get());
}
}
}
// Capture Texture Environment
if (context->isGLES1())
{
const gl::Caps &caps = context->getCaps();
for (GLuint unit = 0; unit < caps.maxMultitextureUnits; ++unit)
{
CaptureTextureEnvironmentState(setupCalls, &replayState, &apiState, unit);
}
}
// Set active Texture.
if (replayState.getActiveSampler() != apiState.getActiveSampler())
{
cap(CaptureActiveTexture(replayState, true,
GL_TEXTURE0 + static_cast<GLenum>(apiState.getActiveSampler())));
replayState.getMutablePrivateStateForCapture()->setActiveSampler(
apiState.getActiveSampler());
}
// Set Renderbuffer binding.
FramebufferCaptureFuncs framebufferFuncs(context->isGLES1());
const gl::RenderbufferManager &renderbuffers = apiState.getRenderbufferManagerForCapture();
gl::RenderbufferID currentRenderbuffer = {0};
for (const auto &renderbufIter : renderbuffers)
{
currentRenderbuffer = renderbufIter.second->id();
}
if (currentRenderbuffer != apiState.getRenderbufferId())
{
cap(framebufferFuncs.bindRenderbuffer(replayState, true, GL_RENDERBUFFER,
apiState.getRenderbufferId()));
}
// Capture Framebuffers.
const gl::FramebufferManager &framebuffers = apiState.getFramebufferManagerForCapture();
gl::FramebufferID currentDrawFramebuffer = {0};
gl::FramebufferID currentReadFramebuffer = {0};
for (const auto &framebufferIter : framebuffers)
{
gl::FramebufferID id = {framebufferIter.first};
const gl::Framebuffer *framebuffer = framebufferIter.second;
// The default Framebuffer exists (by default).
if (framebuffer->isDefault())
{
continue;
}
// Track this as a starting resource that may need to be restored
TrackedResource &trackedFramebuffers =
resourceTracker->getTrackedResource(context->id(), ResourceIDType::Framebuffer);
ResourceSet &startingFramebuffers = trackedFramebuffers.getStartingResources();
startingFramebuffers.insert(id.value);
// Create two lists of calls for initial setup
ResourceCalls &framebufferRegenCalls = trackedFramebuffers.getResourceRegenCalls();
CallVector framebufferGenCalls({setupCalls, &framebufferRegenCalls[id.value]});
// Gen the framebuffer
for (std::vector<CallCapture> *calls : framebufferGenCalls)
{
Capture(calls, framebufferFuncs.genFramebuffers(replayState, true, 1, &id));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, calls);
}
// Create two lists of calls for remaining setup calls. One for setup, and one for restore
// during reset.
ResourceCalls &framebufferRestoreCalls = trackedFramebuffers.getResourceRestoreCalls();
CallVector framebufferSetupCalls({setupCalls, &framebufferRestoreCalls[id.value]});
for (std::vector<CallCapture> *calls : framebufferSetupCalls)
{
Capture(calls, framebufferFuncs.bindFramebuffer(replayState, true, GL_FRAMEBUFFER, id));
}
currentDrawFramebuffer = currentReadFramebuffer = id;
// Color Attachments.
for (const gl::FramebufferAttachment &colorAttachment : framebuffer->getColorAttachments())
{
if (!colorAttachment.isAttached())
{
continue;
}
for (std::vector<CallCapture> *calls : framebufferSetupCalls)
{
CaptureFramebufferAttachment(calls, replayState, framebufferFuncs, colorAttachment);
}
}
const gl::FramebufferAttachment *depthAttachment = framebuffer->getDepthAttachment();
if (depthAttachment)
{
ASSERT(depthAttachment->getBinding() == GL_DEPTH_ATTACHMENT ||
depthAttachment->getBinding() == GL_DEPTH_STENCIL_ATTACHMENT);
for (std::vector<CallCapture> *calls : framebufferSetupCalls)
{
CaptureFramebufferAttachment(calls, replayState, framebufferFuncs,
*depthAttachment);
}
}
const gl::FramebufferAttachment *stencilAttachment = framebuffer->getStencilAttachment();
if (stencilAttachment)
{
ASSERT(stencilAttachment->getBinding() == GL_STENCIL_ATTACHMENT ||
depthAttachment->getBinding() == GL_DEPTH_STENCIL_ATTACHMENT);
for (std::vector<CallCapture> *calls : framebufferSetupCalls)
{
CaptureFramebufferAttachment(calls, replayState, framebufferFuncs,
*stencilAttachment);
}
}
gl::FramebufferState defaultFramebufferState(
context->getCaps(), framebuffer->getState().id(),
framebuffer->getState().getFramebufferSerial());
const gl::DrawBuffersVector<GLenum> &defaultDrawBufferStates =
defaultFramebufferState.getDrawBufferStates();
const gl::DrawBuffersVector<GLenum> &drawBufferStates = framebuffer->getDrawBufferStates();
if (drawBufferStates != defaultDrawBufferStates)
{
for (std::vector<CallCapture> *calls : framebufferSetupCalls)
{
Capture(calls, CaptureDrawBuffers(replayState, true,
static_cast<GLsizei>(drawBufferStates.size()),
drawBufferStates.data()));
}
}
}
// Capture framebuffer bindings.
if (apiState.getDrawFramebuffer())
{
ASSERT(apiState.getReadFramebuffer());
gl::FramebufferID stateReadFramebuffer = apiState.getReadFramebuffer()->id();
gl::FramebufferID stateDrawFramebuffer = apiState.getDrawFramebuffer()->id();
if (stateDrawFramebuffer == stateReadFramebuffer)
{
if (currentDrawFramebuffer != stateDrawFramebuffer ||
currentReadFramebuffer != stateReadFramebuffer)
{
cap(framebufferFuncs.bindFramebuffer(replayState, true, GL_FRAMEBUFFER,
stateDrawFramebuffer));
currentDrawFramebuffer = currentReadFramebuffer = stateDrawFramebuffer;
}
}
else
{
if (currentDrawFramebuffer != stateDrawFramebuffer)
{
cap(framebufferFuncs.bindFramebuffer(replayState, true, GL_DRAW_FRAMEBUFFER,
currentDrawFramebuffer));
currentDrawFramebuffer = stateDrawFramebuffer;
}
if (currentReadFramebuffer != stateReadFramebuffer)
{
cap(framebufferFuncs.bindFramebuffer(replayState, true, GL_READ_FRAMEBUFFER,
replayState.getReadFramebuffer()->id()));
currentReadFramebuffer = stateReadFramebuffer;
}
}
}
// Capture Program Pipelines
const gl::ProgramPipelineManager *programPipelineManager =
apiState.getProgramPipelineManagerForCapture();
for (const auto &ppoIterator : *programPipelineManager)
{
gl::ProgramPipeline *pipeline = ppoIterator.second;
gl::ProgramPipelineID id = {ppoIterator.first};
cap(CaptureGenProgramPipelines(replayState, true, 1, &id));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls);
// PPOs can contain graphics and compute programs, so loop through all shader types rather
// than just the linked ones since getLinkedShaderStages() will return either only graphics
// or compute stages.
for (gl::ShaderType shaderType : gl::AllShaderTypes())
{
gl::Program *program = pipeline->getShaderProgram(shaderType);
if (!program)
{
continue;
}
ASSERT(program->isLinked());
GLbitfield gLbitfield = GetBitfieldFromShaderType(shaderType);
cap(CaptureUseProgramStages(replayState, true, pipeline->id(), gLbitfield,
program->id()));
// Set this program as active so it will be generated in Setup
// Note: We aren't filtering ProgramPipelines, so this could be setting programs
// active that aren't actually used.
MarkResourceIDActive(ResourceIDType::ShaderProgram, program->id().value,
shareGroupSetupCalls, resourceIDToSetupCalls);
}
gl::Program *program = pipeline->getActiveShaderProgram();
if (program)
{
cap(CaptureActiveShaderProgram(replayState, true, id, program->id()));
}
}
// For now we assume the installed program executable is the same as the current program.
// TODO(jmadill): Handle installed program executable. http://anglebug.com/3662
if (!context->isGLES1())
{
// If we have a program bound in the API, or if there is no program bound to the API at
// time of capture and we bound a program for uniform updates during MEC, we must add
// a set program call to replay the correct states.
GLuint currentProgram = 0;
if (apiState.getProgram())
{
cap(CaptureUseProgram(replayState, true, apiState.getProgram()->id()));
CaptureUpdateCurrentProgram(setupCalls->back(), 0, setupCalls);
(void)replayState.setProgram(context, apiState.getProgram());
// Set this program as active so it will be generated in Setup
MarkResourceIDActive(ResourceIDType::ShaderProgram, apiState.getProgram()->id().value,
shareGroupSetupCalls, resourceIDToSetupCalls);
currentProgram = apiState.getProgram()->id().value;
}
else if (replayState.getProgram())
{
cap(CaptureUseProgram(replayState, true, {0}));
CaptureUpdateCurrentProgram(setupCalls->back(), 0, setupCalls);
(void)replayState.setProgram(context, nullptr);
}
// Track the calls necessary to reset active program back to initial state
Capture(&resetCalls[angle::EntryPoint::GLUseProgram],
CaptureUseProgram(replayState, true, {currentProgram}));
CaptureUpdateCurrentProgram((&resetCalls[angle::EntryPoint::GLUseProgram])->back(), 0,
&resetCalls[angle::EntryPoint::GLUseProgram]);
// Same for program pipelines as for programs, see comment above.
if (apiState.getProgramPipeline())
{
cap(CaptureBindProgramPipeline(replayState, true, apiState.getProgramPipeline()->id()));
}
else if (replayState.getProgramPipeline())
{
cap(CaptureBindProgramPipeline(replayState, true, {0}));
}
}
// Create existing queries. Note that queries may be genned and not yet started. In that
// case the queries will exist in the query map as nullptr entries.
const gl::QueryMap &queryMap = context->getQueriesForCapture();
for (gl::QueryMap::Iterator queryIter = queryMap.beginWithNull();
queryIter != queryMap.endWithNull(); ++queryIter)
{
ASSERT(queryIter->first);
gl::QueryID queryID = {queryIter->first};
cap(CaptureGenQueries(replayState, true, 1, &queryID));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls);
gl::Query *query = queryIter->second;
if (query)
{
gl::QueryType queryType = query->getType();
// Begin the query to generate the object
cap(CaptureBeginQuery(replayState, true, queryType, queryID));
// End the query if it was not active
if (!IsQueryActive(apiState, queryID))
{
cap(CaptureEndQuery(replayState, true, queryType));
}
}
}
// Transform Feedback
const gl::TransformFeedbackMap &xfbMap = context->getTransformFeedbacksForCapture();
for (const auto &xfbIter : xfbMap)
{
gl::TransformFeedbackID xfbID = {xfbIter.first};
// Do not capture the default XFB object.
if (xfbID.value == 0)
{
continue;
}
cap(CaptureGenTransformFeedbacks(replayState, true, 1, &xfbID));
MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls);
gl::TransformFeedback *xfb = xfbIter.second;
if (!xfb)
{
// The object was never created
continue;
}
// Bind XFB to create the object
cap(CaptureBindTransformFeedback(replayState, true, GL_TRANSFORM_FEEDBACK, xfbID));
// Bind the buffers associated with this XFB object
for (size_t i = 0; i < xfb->getIndexedBufferCount(); ++i)
{
const gl::OffsetBindingPointer<gl::Buffer> &xfbBuffer = xfb->getIndexedBuffer(i);
// Note: Buffers bound with BindBufferBase can be used with BindBuffer
cap(CaptureBindBufferRange(replayState, true, gl::BufferBinding::TransformFeedback, 0,
xfbBuffer.id(), xfbBuffer.getOffset(), xfbBuffer.getSize()));
}
if (xfb->isActive() || xfb->isPaused())
{
// We don't support active XFB in MEC yet
UNIMPLEMENTED();
}
}
// Bind the current XFB buffer after populating XFB objects
gl::TransformFeedback *currentXFB = apiState.getCurrentTransformFeedback();
if (currentXFB)
{
cap(CaptureBindTransformFeedback(replayState, true, GL_TRANSFORM_FEEDBACK,
currentXFB->id()));
}
// Bind samplers
const gl::SamplerBindingVector &samplerBindings = apiState.getSamplers();
for (GLuint bindingIndex = 0; bindingIndex < static_cast<GLuint>(samplerBindings.size());
++bindingIndex)
{
gl::SamplerID samplerID = samplerBindings[bindingIndex].id();
if (samplerID.value != 0)
{
cap(CaptureBindSampler(replayState, true, bindingIndex, samplerID));
}
}
// Capture Image Texture bindings
const std::vector<gl::ImageUnit> &imageUnits = apiState.getImageUnits();
for (GLuint bindingIndex = 0; bindingIndex < static_cast<GLuint>(imageUnits.size());
++bindingIndex)
{
const gl::ImageUnit &imageUnit = imageUnits[bindingIndex];
if (imageUnit.texture == 0)
{
continue;
}
cap(CaptureBindImageTexture(replayState, true, bindingIndex, imageUnit.texture.id(),
imageUnit.level, imageUnit.layered, imageUnit.layer,
imageUnit.access, imageUnit.format));
}
// Capture GL Context states.
auto capCap = [cap, &replayState](GLenum capEnum, bool capValue) {
if (capValue)
{
cap(CaptureEnable(replayState, true, capEnum));
}
else
{
cap(CaptureDisable(replayState, true, capEnum));
}
};
// Capture GLES1 context states.
if (context->isGLES1())
{
const bool currentTextureState = apiState.getEnableFeature(GL_TEXTURE_2D);
const bool defaultTextureState = replayState.getEnableFeature(GL_TEXTURE_2D);
if (currentTextureState != defaultTextureState)
{
capCap(GL_TEXTURE_2D, currentTextureState);
}
cap(CaptureMatrixMode(replayState, true, gl::MatrixType::Projection));
for (angle::Mat4 projectionMatrix :
apiState.gles1().getMatrixStack(gl::MatrixType::Projection))
{
cap(CapturePushMatrix(replayState, true));
cap(CaptureLoadMatrixf(replayState, true, projectionMatrix.elements().data()));
}
cap(CaptureMatrixMode(replayState, true, gl::MatrixType::Modelview));
for (angle::Mat4 modelViewMatrix :
apiState.gles1().getMatrixStack(gl::MatrixType::Modelview))
{
cap(CapturePushMatrix(replayState, true));
cap(CaptureLoadMatrixf(replayState, true, modelViewMatrix.elements().data()));
}
gl::MatrixType currentMatrixMode = apiState.gles1().getMatrixMode();
if (currentMatrixMode != gl::MatrixType::Modelview)
{
cap(CaptureMatrixMode(replayState, true, currentMatrixMode));
}
// Alpha Test state
const bool currentAlphaTestState = apiState.getEnableFeature(GL_ALPHA_TEST);
const bool defaultAlphaTestState = replayState.getEnableFeature(GL_ALPHA_TEST);
if (currentAlphaTestState != defaultAlphaTestState)
{
capCap(GL_ALPHA_TEST, currentAlphaTestState);
}
const gl::AlphaTestParameters currentAlphaTestParameters =
apiState.gles1().getAlphaTestParameters();
const gl::AlphaTestParameters defaultAlphaTestParameters =
replayState.gles1().getAlphaTestParameters();
if (currentAlphaTestParameters != defaultAlphaTestParameters)
{
cap(CaptureAlphaFunc(replayState, true, currentAlphaTestParameters.func,
currentAlphaTestParameters.ref));
}
}
// Rasterizer state. Missing ES 3.x features.
const gl::RasterizerState &defaultRasterState = replayState.getRasterizerState();
const gl::RasterizerState &currentRasterState = apiState.getRasterizerState();
if (currentRasterState.cullFace != defaultRasterState.cullFace)
{
capCap(GL_CULL_FACE, currentRasterState.cullFace);
}
if (currentRasterState.cullMode != defaultRasterState.cullMode)
{
cap(CaptureCullFace(replayState, true, currentRasterState.cullMode));
}
if (currentRasterState.frontFace != defaultRasterState.frontFace)
{
cap(CaptureFrontFace(replayState, true, currentRasterState.frontFace));
}
if (currentRasterState.polygonMode != defaultRasterState.polygonMode)
{
if (context->getExtensions().polygonModeNV)
{
cap(CapturePolygonModeNV(replayState, true, GL_FRONT_AND_BACK,
currentRasterState.polygonMode));
}
else if (context->getExtensions().polygonModeANGLE)
{
cap(CapturePolygonModeANGLE(replayState, true, GL_FRONT_AND_BACK,
currentRasterState.polygonMode));
}
else
{
UNREACHABLE();
}
}
if (currentRasterState.polygonOffsetPoint != defaultRasterState.polygonOffsetPoint)
{
capCap(GL_POLYGON_OFFSET_POINT_NV, currentRasterState.polygonOffsetPoint);
}
if (currentRasterState.polygonOffsetLine != defaultRasterState.polygonOffsetLine)
{
capCap(GL_POLYGON_OFFSET_LINE_NV, currentRasterState.polygonOffsetLine);
}
if (currentRasterState.polygonOffsetFill != defaultRasterState.polygonOffsetFill)
{
capCap(GL_POLYGON_OFFSET_FILL, currentRasterState.polygonOffsetFill);
}
if (currentRasterState.polygonOffsetFactor != defaultRasterState.polygonOffsetFactor ||
currentRasterState.polygonOffsetUnits != defaultRasterState.polygonOffsetUnits ||
currentRasterState.polygonOffsetClamp != defaultRasterState.polygonOffsetClamp)
{
if (currentRasterState.polygonOffsetClamp == 0.0f)
{
cap(CapturePolygonOffset(replayState, true, currentRasterState.polygonOffsetFactor,
currentRasterState.polygonOffsetUnits));
}
else
{
cap(CapturePolygonOffsetClampEXT(
replayState, true, currentRasterState.polygonOffsetFactor,
currentRasterState.polygonOffsetUnits, currentRasterState.polygonOffsetClamp));
}
}
if (currentRasterState.depthClamp != defaultRasterState.depthClamp)
{
capCap(GL_DEPTH_CLAMP_EXT, currentRasterState.depthClamp);
}
// pointDrawMode/multiSample are only used in the D3D back-end right now.
if (currentRasterState.rasterizerDiscard != defaultRasterState.rasterizerDiscard)
{
capCap(GL_RASTERIZER_DISCARD, currentRasterState.rasterizerDiscard);
}
if (currentRasterState.dither != defaultRasterState.dither)
{
capCap(GL_DITHER, currentRasterState.dither);
}
// Depth/stencil state.
const gl::DepthStencilState &defaultDSState = replayState.getDepthStencilState();
const gl::DepthStencilState &currentDSState = apiState.getDepthStencilState();
if (defaultDSState.depthFunc != currentDSState.depthFunc)
{
cap(CaptureDepthFunc(replayState, true, currentDSState.depthFunc));
}
if (defaultDSState.depthMask != currentDSState.depthMask)
{
cap(CaptureDepthMask(replayState, true, gl::ConvertToGLBoolean(currentDSState.depthMask)));
}
if (defaultDSState.depthTest != currentDSState.depthTest)
{
capCap(GL_DEPTH_TEST, currentDSState.depthTest);
}
if (defaultDSState.stencilTest != currentDSState.stencilTest)
{
capCap(GL_STENCIL_TEST, currentDSState.stencilTest);
}
if (currentDSState.stencilFunc == currentDSState.stencilBackFunc &&
currentDSState.stencilMask == currentDSState.stencilBackMask)
{
// Front and back are equal
if (defaultDSState.stencilFunc != currentDSState.stencilFunc ||
defaultDSState.stencilMask != currentDSState.stencilMask ||
apiState.getStencilRef() != 0)
{
cap(CaptureStencilFunc(replayState, true, currentDSState.stencilFunc,
apiState.getStencilRef(), currentDSState.stencilMask));
}
}
else
{
// Front and back are separate
if (defaultDSState.stencilFunc != currentDSState.stencilFunc ||
defaultDSState.stencilMask != currentDSState.stencilMask ||
apiState.getStencilRef() != 0)
{
cap(CaptureStencilFuncSeparate(replayState, true, GL_FRONT, currentDSState.stencilFunc,
apiState.getStencilRef(), currentDSState.stencilMask));
}
if (defaultDSState.stencilBackFunc != currentDSState.stencilBackFunc ||
defaultDSState.stencilBackMask != currentDSState.stencilBackMask ||
apiState.getStencilBackRef() != 0)
{
cap(CaptureStencilFuncSeparate(
replayState, true, GL_BACK, currentDSState.stencilBackFunc,
apiState.getStencilBackRef(), currentDSState.stencilBackMask));
}
}
if (currentDSState.stencilFail == currentDSState.stencilBackFail &&
currentDSState.stencilPassDepthFail == currentDSState.stencilBackPassDepthFail &&
currentDSState.stencilPassDepthPass == currentDSState.stencilBackPassDepthPass)
{
// Front and back are equal
if (defaultDSState.stencilFail != currentDSState.stencilFail ||
defaultDSState.stencilPassDepthFail != currentDSState.stencilPassDepthFail ||
defaultDSState.stencilPassDepthPass != currentDSState.stencilPassDepthPass)
{
cap(CaptureStencilOp(replayState, true, currentDSState.stencilFail,
currentDSState.stencilPassDepthFail,
currentDSState.stencilPassDepthPass));
}
}
else
{
// Front and back are separate
if (defaultDSState.stencilFail != currentDSState.stencilFail ||
defaultDSState.stencilPassDepthFail != currentDSState.stencilPassDepthFail ||
defaultDSState.stencilPassDepthPass != currentDSState.stencilPassDepthPass)
{
cap(CaptureStencilOpSeparate(replayState, true, GL_FRONT, currentDSState.stencilFail,
currentDSState.stencilPassDepthFail,
currentDSState.stencilPassDepthPass));
}
if (defaultDSState.stencilBackFail != currentDSState.stencilBackFail ||
defaultDSState.stencilBackPassDepthFail != currentDSState.stencilBackPassDepthFail ||
defaultDSState.stencilBackPassDepthPass != currentDSState.stencilBackPassDepthPass)
{
cap(CaptureStencilOpSeparate(replayState, true, GL_BACK, currentDSState.stencilBackFail,
currentDSState.stencilBackPassDepthFail,
currentDSState.stencilBackPassDepthPass));
}
}
if (currentDSState.stencilWritemask == currentDSState.stencilBackWritemask)
{
// Front and back are equal
if (defaultDSState.stencilWritemask != currentDSState.stencilWritemask)
{
cap(CaptureStencilMask(replayState, true, currentDSState.stencilWritemask));
}
}
else
{
// Front and back are separate
if (defaultDSState.stencilWritemask != currentDSState.stencilWritemask)
{
cap(CaptureStencilMaskSeparate(replayState, true, GL_FRONT,
currentDSState.stencilWritemask));
}
if (defaultDSState.stencilBackWritemask != currentDSState.stencilBackWritemask)
{
cap(CaptureStencilMaskSeparate(replayState, true, GL_BACK,
currentDSState.stencilBackWritemask));
}
}
// Blend state.
const gl::BlendState &defaultBlendState = replayState.getBlendState();
const gl::BlendState &currentBlendState = apiState.getBlendState();
if (currentBlendState.blend != defaultBlendState.blend)
{
capCap(GL_BLEND, currentBlendState.blend);
}
if (currentBlendState.sourceBlendRGB != defaultBlendState.sourceBlendRGB ||
currentBlendState.destBlendRGB != defaultBlendState.destBlendRGB ||
currentBlendState.sourceBlendAlpha != defaultBlendState.sourceBlendAlpha ||
currentBlendState.destBlendAlpha != defaultBlendState.destBlendAlpha)
{
if (context->isGLES1())
{
// Even though their states are tracked independently, in GLES1 blendAlpha
// and blendRGB cannot be set separately and are always equal
cap(CaptureBlendFunc(replayState, true, currentBlendState.sourceBlendRGB,
currentBlendState.destBlendRGB));
Capture(&resetCalls[angle::EntryPoint::GLBlendFunc],
CaptureBlendFunc(replayState, true, currentBlendState.sourceBlendRGB,
currentBlendState.destBlendRGB));
}
else
{
// Always use BlendFuncSeparate for non-GLES1 as it covers all cases
cap(CaptureBlendFuncSeparate(
replayState, true, currentBlendState.sourceBlendRGB, currentBlendState.destBlendRGB,
currentBlendState.sourceBlendAlpha, currentBlendState.destBlendAlpha));
Capture(&resetCalls[angle::EntryPoint::GLBlendFuncSeparate],
CaptureBlendFuncSeparate(replayState, true, currentBlendState.sourceBlendRGB,
currentBlendState.destBlendRGB,
currentBlendState.sourceBlendAlpha,
currentBlendState.destBlendAlpha));
}
}
if (currentBlendState.blendEquationRGB != defaultBlendState.blendEquationRGB ||
currentBlendState.blendEquationAlpha != defaultBlendState.blendEquationAlpha)
{
// Similarly to BlendFunc, using BlendEquation in some cases complicates Reset.
cap(CaptureBlendEquationSeparate(replayState, true, currentBlendState.blendEquationRGB,
currentBlendState.blendEquationAlpha));
Capture(&resetCalls[angle::EntryPoint::GLBlendEquationSeparate],
CaptureBlendEquationSeparate(replayState, true, currentBlendState.blendEquationRGB,
currentBlendState.blendEquationAlpha));
}
if (currentBlendState.colorMaskRed != defaultBlendState.colorMaskRed ||
currentBlendState.colorMaskGreen != defaultBlendState.colorMaskGreen ||
currentBlendState.colorMaskBlue != defaultBlendState.colorMaskBlue ||
currentBlendState.colorMaskAlpha != defaultBlendState.colorMaskAlpha)
{
cap(CaptureColorMask(replayState, true,
gl::ConvertToGLBoolean(currentBlendState.colorMaskRed),
gl::ConvertToGLBoolean(currentBlendState.colorMaskGreen),
gl::ConvertToGLBoolean(currentBlendState.colorMaskBlue),
gl::ConvertToGLBoolean(currentBlendState.colorMaskAlpha)));
Capture(&resetCalls[angle::EntryPoint::GLColorMask],
CaptureColorMask(replayState, true,
gl::ConvertToGLBoolean(currentBlendState.colorMaskRed),
gl::ConvertToGLBoolean(currentBlendState.colorMaskGreen),
gl::ConvertToGLBoolean(currentBlendState.colorMaskBlue),
gl::ConvertToGLBoolean(currentBlendState.colorMaskAlpha)));
}
const gl::ColorF &currentBlendColor = apiState.getBlendColor();
if (currentBlendColor != gl::ColorF())
{
cap(CaptureBlendColor(replayState, true, currentBlendColor.red, currentBlendColor.green,
currentBlendColor.blue, currentBlendColor.alpha));
Capture(&resetCalls[angle::EntryPoint::GLBlendColor],
CaptureBlendColor(replayState, true, currentBlendColor.red, currentBlendColor.green,
currentBlendColor.blue, currentBlendColor.alpha));
}
// Pixel storage states.
gl::PixelPackState &currentPackState = replayState.getPackState();
if (currentPackState.alignment != apiState.getPackAlignment())
{
cap(CapturePixelStorei(replayState, true, GL_PACK_ALIGNMENT, apiState.getPackAlignment()));
currentPackState.alignment = apiState.getPackAlignment();
}
if (currentPackState.rowLength != apiState.getPackRowLength())
{
cap(CapturePixelStorei(replayState, true, GL_PACK_ROW_LENGTH, apiState.getPackRowLength()));
currentPackState.rowLength = apiState.getPackRowLength();
}
if (currentPackState.skipRows != apiState.getPackSkipRows())
{
cap(CapturePixelStorei(replayState, true, GL_PACK_SKIP_ROWS, apiState.getPackSkipRows()));
currentPackState.skipRows = apiState.getPackSkipRows();
}
if (currentPackState.skipPixels != apiState.getPackSkipPixels())
{
cap(CapturePixelStorei(replayState, true, GL_PACK_SKIP_PIXELS,
apiState.getPackSkipPixels()));
currentPackState.skipPixels = apiState.getPackSkipPixels();
}
// We set unpack alignment above, no need to change it here
ASSERT(currentUnpackState.alignment == 1);
if (currentUnpackState.rowLength != apiState.getUnpackRowLength())
{
cap(CapturePixelStorei(replayState, true, GL_UNPACK_ROW_LENGTH,
apiState.getUnpackRowLength()));
currentUnpackState.rowLength = apiState.getUnpackRowLength();
}
if (currentUnpackState.skipRows != apiState.getUnpackSkipRows())
{
cap(CapturePixelStorei(replayState, true, GL_UNPACK_SKIP_ROWS,
apiState.getUnpackSkipRows()));
currentUnpackState.skipRows = apiState.getUnpackSkipRows();
}
if (currentUnpackState.skipPixels != apiState.getUnpackSkipPixels())
{
cap(CapturePixelStorei(replayState, true, GL_UNPACK_SKIP_PIXELS,
apiState.getUnpackSkipPixels()));
currentUnpackState.skipPixels = apiState.getUnpackSkipPixels();
}
if (currentUnpackState.imageHeight != apiState.getUnpackImageHeight())
{
cap(CapturePixelStorei(replayState, true, GL_UNPACK_IMAGE_HEIGHT,
apiState.getUnpackImageHeight()));
currentUnpackState.imageHeight = apiState.getUnpackImageHeight();
}
if (currentUnpackState.skipImages != apiState.getUnpackSkipImages())
{
cap(CapturePixelStorei(replayState, true, GL_UNPACK_SKIP_IMAGES,
apiState.getUnpackSkipImages()));
currentUnpackState.skipImages = apiState.getUnpackSkipImages();
}
// Clear state. Missing ES 3.x features.
// TODO(http://anglebug.com/3662): Complete state capture.
const gl::ColorF &currentClearColor = apiState.getColorClearValue();
if (currentClearColor != gl::ColorF())
{
cap(CaptureClearColor(replayState, true, currentClearColor.red, currentClearColor.green,
currentClearColor.blue, currentClearColor.alpha));
}
if (apiState.getDepthClearValue() != 1.0f)
{
cap(CaptureClearDepthf(replayState, true, apiState.getDepthClearValue()));
}
if (apiState.getStencilClearValue() != 0)
{
cap(CaptureClearStencil(replayState, true, apiState.getStencilClearValue()));
}
// Viewport / scissor / clipping planes.
const gl::Rectangle &currentViewport = apiState.getViewport();
if (currentViewport != gl::Rectangle())
{
cap(CaptureViewport(replayState, true, currentViewport.x, currentViewport.y,
currentViewport.width, currentViewport.height));
}
if (apiState.getNearPlane() != 0.0f || apiState.getFarPlane() != 1.0f)
{
cap(CaptureDepthRangef(replayState, true, apiState.getNearPlane(), apiState.getFarPlane()));
}
if (apiState.getClipOrigin() != gl::ClipOrigin::LowerLeft ||
apiState.getClipDepthMode() != gl::ClipDepthMode::NegativeOneToOne)
{
cap(CaptureClipControlEXT(replayState, true, apiState.getClipOrigin(),
apiState.getClipDepthMode()));
}
if (apiState.isScissorTestEnabled())
{
capCap(GL_SCISSOR_TEST, apiState.isScissorTestEnabled());
}
const gl::Rectangle &currentScissor = apiState.getScissor();
if (currentScissor != gl::Rectangle())
{
cap(CaptureScissor(replayState, true, currentScissor.x, currentScissor.y,
currentScissor.width, currentScissor.height));
}
// Allow the replayState object to be destroyed conveniently.
replayState.setBufferBinding(context, gl::BufferBinding::Array, nullptr);
// Clean up the replay state.
replayState.reset(context);
GLint contextUnpackAlignment = context->getState().getUnpackState().alignment;
if (currentUnpackState.alignment != contextUnpackAlignment)
{
cap(CapturePixelStorei(replayState, true, GL_UNPACK_ALIGNMENT, contextUnpackAlignment));
replayState.getMutablePrivateStateForCapture()->setUnpackAlignment(contextUnpackAlignment);
}
if (validationEnabled)
{
CaptureValidateSerializedState(context, setupCalls);
}
}
bool SkipCall(EntryPoint entryPoint)
{
switch (entryPoint)
{
case EntryPoint::GLDebugMessageCallback:
case EntryPoint::GLDebugMessageCallbackKHR:
case EntryPoint::GLDebugMessageControl:
case EntryPoint::GLDebugMessageControlKHR:
case EntryPoint::GLDebugMessageInsert:
case EntryPoint::GLDebugMessageInsertKHR:
case EntryPoint::GLGetDebugMessageLog:
case EntryPoint::GLGetDebugMessageLogKHR:
case EntryPoint::GLGetObjectLabel:
case EntryPoint::GLGetObjectLabelEXT:
case EntryPoint::GLGetObjectLabelKHR:
case EntryPoint::GLGetObjectPtrLabelKHR:
case EntryPoint::GLGetPointervKHR:
case EntryPoint::GLInsertEventMarkerEXT:
case EntryPoint::GLLabelObjectEXT:
case EntryPoint::GLObjectLabel:
case EntryPoint::GLObjectLabelKHR:
case EntryPoint::GLObjectPtrLabelKHR:
case EntryPoint::GLPopDebugGroupKHR:
case EntryPoint::GLPopGroupMarkerEXT:
case EntryPoint::GLPushDebugGroupKHR:
case EntryPoint::GLPushGroupMarkerEXT:
// Purposefully skip entry points from:
// - KHR_debug
// - EXT_debug_label
// - EXT_debug_marker
// There is no need to capture these for replaying a trace in our harness
return true;
case EntryPoint::GLGetActiveUniform:
case EntryPoint::GLGetActiveUniformsiv:
// Skip these calls because:
// - We don't use the return values.
// - Active uniform counts can vary between platforms due to cross stage optimizations
// and asking about uniforms above GL_ACTIVE_UNIFORMS triggers errors.
return true;
case EntryPoint::GLGetActiveAttrib:
// Skip these calls because:
// - We don't use the return values.
// - Same as uniforms, the value can vary, asking above GL_ACTIVE_ATTRIBUTES is an error
return true;
case EntryPoint::GLGetActiveUniformBlockiv:
case EntryPoint::GLGetActiveUniformName:
case EntryPoint::GLGetActiveUniformBlockName:
// Skip these calls because:
// - We don't use the return values.
// - It reduces the number of references to the uniform block index map.
return true;
case EntryPoint::EGLChooseConfig:
case EntryPoint::EGLGetProcAddress:
case EntryPoint::EGLGetConfigAttrib:
case EntryPoint::EGLGetConfigs:
case EntryPoint::EGLGetSyncAttrib:
case EntryPoint::EGLGetSyncAttribKHR:
case EntryPoint::EGLQuerySurface:
// Skip these calls because:
// - Some EGL types and pointer parameters aren't yet implemented in EGL capture.
return true;
case EntryPoint::EGLPrepareSwapBuffersANGLE:
// Skip this call because:
// - eglPrepareSwapBuffersANGLE is automatically called by eglSwapBuffers
return true;
case EntryPoint::EGLSwapBuffers:
// Skip these calls because:
// - Swap is handled specially by the trace harness.
return true;
default:
break;
}
return false;
}
template <typename ParamValueType>
struct ParamValueTrait
{
static_assert(sizeof(ParamValueType) == 0, "invalid ParamValueType");
};
template <>
struct ParamValueTrait<gl::FramebufferID>
{
static constexpr const char *name = "framebufferPacked";
static const ParamType typeID = ParamType::TFramebufferID;
};
std::string GetBaseName(const std::string &nameWithPath)
{
std::vector<std::string> result = angle::SplitString(
nameWithPath, "/\\", WhitespaceHandling::TRIM_WHITESPACE, SplitResult::SPLIT_WANT_NONEMPTY);
ASSERT(!result.empty());
return result.back();
}
template <>
struct ParamValueTrait<gl::BufferID>
{
static constexpr const char *name = "bufferPacked";
static const ParamType typeID = ParamType::TBufferID;
};
template <>
struct ParamValueTrait<gl::RenderbufferID>
{
static constexpr const char *name = "renderbufferPacked";
static const ParamType typeID = ParamType::TRenderbufferID;
};
template <>
struct ParamValueTrait<gl::TextureID>
{
static constexpr const char *name = "texturePacked";
static const ParamType typeID = ParamType::TTextureID;
};
} // namespace
FrameCapture::FrameCapture() = default;
FrameCapture::~FrameCapture() = default;
void FrameCapture::reset()
{
mSetupCalls.clear();
}
FrameCaptureShared::FrameCaptureShared()
: mLastContextId{0},
mEnabled(true),
mSerializeStateEnabled(false),
mCompression(true),
mClientVertexArrayMap{},
mFrameIndex(1),
mCaptureStartFrame(1),
mCaptureEndFrame(0),
mClientArraySizes{},
mReadBufferSize(0),
mResourceIDBufferSize(0),
mHasResourceType{},
mResourceIDToSetupCalls{},
mMaxAccessedResourceIDs{},
mCaptureTrigger(0),
mCaptureActive(false),
mWindowSurfaceContextID({0})
{
reset();
std::string enabledFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kEnabledVarName, kAndroidEnabled);
if (enabledFromEnv == "0")
{
mEnabled = false;
}
std::string pathFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kOutDirectoryVarName, kAndroidOutDir);
if (pathFromEnv.empty())
{
mOutDirectory = GetDefaultOutDirectory();
}
else
{
mOutDirectory = pathFromEnv;
}
// Ensure the capture path ends with a slash.
if (mOutDirectory.back() != '\\' && mOutDirectory.back() != '/')
{
mOutDirectory += '/';
}
std::string startFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kFrameStartVarName, kAndroidFrameStart);
if (!startFromEnv.empty())
{
mCaptureStartFrame = atoi(startFromEnv.c_str());
}
if (mCaptureStartFrame < 1)
{
WARN() << "Cannot use a capture start frame less than 1.";
mCaptureStartFrame = 1;
}
std::string endFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kFrameEndVarName, kAndroidFrameEnd);
if (!endFromEnv.empty())
{
mCaptureEndFrame = atoi(endFromEnv.c_str());
}
std::string captureTriggerFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kTriggerVarName, kAndroidTrigger);
if (!captureTriggerFromEnv.empty())
{
mCaptureTrigger = atoi(captureTriggerFromEnv.c_str());
// If the trigger has been populated, ignore the other frame range variables by setting them
// to unreasonable values. This isn't perfect, but it is effective.
mCaptureStartFrame = mCaptureEndFrame = std::numeric_limits<uint32_t>::max();
INFO() << "Capture trigger detected, disabling capture start/end frame.";
}
std::string labelFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kCaptureLabelVarName, kAndroidCaptureLabel);
if (!labelFromEnv.empty())
{
// Optional label to provide unique file names and namespaces
mCaptureLabel = labelFromEnv;
}
std::string compressionFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kCompressionVarName, kAndroidCompression);
if (compressionFromEnv == "0")
{
mCompression = false;
}
std::string serializeStateFromEnv = angle::GetEnvironmentVar(kSerializeStateVarName);
if (serializeStateFromEnv == "1")
{
mSerializeStateEnabled = true;
}
std::string validateSerialiedStateFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kValidationVarName, kAndroidValidation);
if (validateSerialiedStateFromEnv == "1")
{
mValidateSerializedState = true;
}
mValidationExpression =
GetEnvironmentVarOrUnCachedAndroidProperty(kValidationExprVarName, kAndroidValidationExpr);
if (!mValidationExpression.empty())
{
INFO() << "Validation expression is " << kValidationExprVarName;
}
std::string trimEnabledFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kTrimEnabledVarName, kAndroidTrimEnabled);
if (trimEnabledFromEnv == "0")
{
mTrimEnabled = false;
}
// TODO: Remove. http://anglebug.com/7753
std::string sourceExtFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kSourceExtVarName, kAndroidSourceExt);
if (!sourceExtFromEnv.empty())
{
if (sourceExtFromEnv == "c" || sourceExtFromEnv == "cpp")
{
mReplayWriter.setSourceFileExtension(sourceExtFromEnv.c_str());
}
else
{
WARN() << "Invalid capture source extension: " << sourceExtFromEnv;
}
}
std::string sourceSizeFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kSourceSizeVarName, kAndroidSourceSize);
if (!sourceSizeFromEnv.empty())
{
int sourceSize = atoi(sourceSizeFromEnv.c_str());
if (sourceSize < 0)
{
WARN() << "Invalid capture source size: " << sourceSize;
}
else
{
mReplayWriter.setSourceFileSizeThreshold(sourceSize);
}
}
std::string forceShadowFromEnv =
GetEnvironmentVarOrUnCachedAndroidProperty(kForceShadowVarName, kAndroidForceShadow);
if (forceShadowFromEnv == "1")
{
INFO() << "Force enabling shadow memory for coherent buffer tracking.";
mCoherentBufferTracker.enableShadowMemory();
}
if (mFrameIndex == mCaptureStartFrame)
{
// Capture is starting from the first frame, so set the capture active to ensure all GLES
// commands issued are handled correctly by maybeCapturePreCallUpdates() and
// maybeCapturePostCallUpdates().
setCaptureActive();
}
if (mCaptureEndFrame < mCaptureStartFrame)
{
mEnabled = false;
}
mReplayWriter.setCaptureLabel(mCaptureLabel);
}
FrameCaptureShared::~FrameCaptureShared() = default;
PageRange::PageRange(size_t start, size_t end) : start(start), end(end) {}
PageRange::~PageRange() = default;
AddressRange::AddressRange() {}
AddressRange::AddressRange(uintptr_t start, size_t size) : start(start), size(size) {}
AddressRange::~AddressRange() = default;
uintptr_t AddressRange::end()
{
return start + size;
}
CoherentBuffer::CoherentBuffer(uintptr_t start,
size_t size,
size_t pageSize,
bool isShadowMemoryEnabled)
: mPageSize(pageSize),
mShadowMemoryEnabled(isShadowMemoryEnabled),
mBufferStart(start),
mShadowMemory(nullptr),
mShadowDirty(false)
{
if (mShadowMemoryEnabled)
{
// Shadow memory needs to have at least the size of one page, to not protect outside.
size_t numShadowPages = (size / pageSize) + 1;
mShadowMemory = AlignedAlloc(numShadowPages * pageSize, pageSize);
ASSERT(mShadowMemory != nullptr);
start = reinterpret_cast<uintptr_t>(mShadowMemory);
}
mRange.start = start;
mRange.size = size;
mProtectionRange.start = rx::roundDownPow2(start, pageSize);
uintptr_t protectionEnd = rx::roundUpPow2(start + size, pageSize);
mProtectionRange.size = protectionEnd - mProtectionRange.start;
mPageCount = mProtectionRange.size / pageSize;
mProtectionStartPage = mProtectionRange.start / mPageSize;
mProtectionEndPage = mProtectionStartPage + mPageCount;
mDirtyPages = std::vector<bool>(mPageCount);
mDirtyPages.assign(mPageCount, true);
}
std::vector<PageRange> CoherentBuffer::getDirtyPageRanges()
{
std::vector<PageRange> dirtyPageRanges;
bool inDirty = false;
for (size_t i = 0; i < mPageCount; i++)
{
if (!inDirty && mDirtyPages[i])
{
// Found start of a dirty range
inDirty = true;
// Set end page as last page initially
dirtyPageRanges.push_back(PageRange(i, mPageCount));
}
else if (inDirty && !mDirtyPages[i])
{
// Found end of a dirty range
inDirty = false;
dirtyPageRanges.back().end = i;
}
}
return dirtyPageRanges;
}
AddressRange CoherentBuffer::getRange()
{
return mRange;
}
AddressRange CoherentBuffer::getDirtyAddressRange(const PageRange &dirtyPageRange)
{
AddressRange range;
if (dirtyPageRange.start == 0)
{
// First page, use non page aligned buffer start.
range.start = mRange.start;
}
else
{
range.start = mProtectionRange.start + dirtyPageRange.start * mPageSize;
}
if (dirtyPageRange.end == mPageCount)
{
// Last page, use non page aligned buffer end.
range.size = mRange.end() - range.start;
}
else
{
range.size = (dirtyPageRange.end - dirtyPageRange.start) * mPageSize;
// This occurs when a buffer occupies 2 pages, but is smaller than a page.
if (mRange.end() < range.end())
{
range.size = mRange.end() - range.start;
}
}
// Dirty range must be in buffer
ASSERT(range.start >= mRange.start && mRange.end() >= range.end());
return range;
}
CoherentBuffer::~CoherentBuffer()
{
if (mShadowMemory != nullptr)
{
AlignedFree(mShadowMemory);
}
}
bool CoherentBuffer::isDirty()
{
return std::find(mDirtyPages.begin(), mDirtyPages.end(), true) != mDirtyPages.end();
}
bool CoherentBuffer::contains(size_t page, size_t *relativePage)
{
bool isInProtectionRange = page >= mProtectionStartPage && page < mProtectionEndPage;
if (!isInProtectionRange)
{
return false;
}
*relativePage = page - mProtectionStartPage;
ASSERT(page >= mProtectionStartPage);
return true;
}
void CoherentBuffer::protectPageRange(const PageRange &pageRange)
{
for (size_t i = pageRange.start; i < pageRange.end; i++)
{
setDirty(i, false);
}
}
void CoherentBuffer::protectAll()
{
for (size_t i = 0; i < mPageCount; i++)
{
setDirty(i, false);
}
}
void CoherentBuffer::updateBufferMemory()
{
memcpy(reinterpret_cast<void *>(mBufferStart), reinterpret_cast<void *>(mRange.start),
mRange.size);
}
void CoherentBuffer::updateShadowMemory()
{
memcpy(reinterpret_cast<void *>(mRange.start), reinterpret_cast<void *>(mBufferStart),
mRange.size);
mShadowDirty = false;
}
void CoherentBuffer::setDirty(size_t relativePage, bool dirty)
{
if (mDirtyPages[relativePage] == dirty)
{
// The page is already set.
// This can happen when tracked buffers overlap in a page.
return;
}
uintptr_t pageStart = mProtectionRange.start + relativePage * mPageSize;
// Last page end must be the same as protection end
if (relativePage + 1 == mPageCount)
{
ASSERT(mProtectionRange.end() == pageStart + mPageSize);
}
bool ret;
if (dirty)
{
ret = UnprotectMemory(pageStart, mPageSize);
}
else
{
ret = ProtectMemory(pageStart, mPageSize);
}
if (!ret)
{
ERR() << "Could not set protection for buffer page " << relativePage << " at "
<< reinterpret_cast<void *>(pageStart) << " with size " << mPageSize;
}
mDirtyPages[relativePage] = dirty;
}
void CoherentBuffer::removeProtection(PageSharingType sharingType)
{
uintptr_t start = mProtectionRange.start;
size_t size = mProtectionRange.size;
switch (sharingType)
{
case PageSharingType::FirstShared:
case PageSharingType::FirstAndLastShared:
start += mPageSize;
break;
default:
break;
}
switch (sharingType)
{
case PageSharingType::FirstShared:
case PageSharingType::LastShared:
size -= mPageSize;
break;
case PageSharingType::FirstAndLastShared:
size -= (2 * mPageSize);
break;
default:
break;
}
if (size == 0)
{
return;
}
if (!UnprotectMemory(start, size))
{
ERR() << "Could not remove protection for buffer at " << start << " with size " << size;
}
}
bool CoherentBufferTracker::canProtectDirectly(gl::Context *context)
{
gl::BufferID bufferId = context->createBuffer();
gl::BufferBinding targetPacked = gl::BufferBinding::Array;
context->bindBuffer(targetPacked, bufferId);
// Allocate 2 pages so we will always have a full aligned page to protect
GLsizei size = static_cast<GLsizei>(mPageSize * 2);
context->bufferStorage(targetPacked, size, nullptr,
GL_DYNAMIC_STORAGE_BIT_EXT | GL_MAP_WRITE_BIT |
GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT);
gl::Buffer *buffer = context->getBuffer(bufferId);
angle::Result result = buffer->mapRange(
context, 0, size, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT);
if (result != angle::Result::Continue)
{
ERR() << "Failed to mapRange of buffer.";
}
void *map = buffer->getMapPointer();
if (map == nullptr)
{
ERR() << "Failed to getMapPointer of buffer.";
}
// Test mprotect
auto start = reinterpret_cast<uintptr_t>(map);
// Only protect a whole page inside the allocated memory
uintptr_t protectionStart = rx::roundUpPow2(start, mPageSize);
uintptr_t protectionEnd = protectionStart + mPageSize;
ASSERT(protectionStart < protectionEnd);
angle::PageFaultCallback callback = [](uintptr_t address) {
return angle::PageFaultHandlerRangeType::InRange;
};
std::unique_ptr<angle::PageFaultHandler> handler(CreatePageFaultHandler(callback));
if (!handler->enable())
{
GLboolean unmapResult;
if (buffer->unmap(context, &unmapResult) != angle::Result::Continue)
{
ERR() << "Could not unmap buffer.";
}
context->bindBuffer(targetPacked, {0});
// Page fault handler could not be enabled, memory can't be protected directly.
return false;
}
size_t protectionSize = protectionEnd - protectionStart;
ASSERT(protectionSize == mPageSize);
bool canProtect = angle::ProtectMemory(protectionStart, protectionSize);
if (canProtect)
{
angle::UnprotectMemory(protectionStart, protectionSize);
}
// Clean up
handler->disable();
GLboolean unmapResult;
if (buffer->unmap(context, &unmapResult) != angle::Result::Continue)
{
ERR() << "Could not unmap buffer.";
}
context->bindBuffer(targetPacked, {0});
context->deleteBuffer(buffer->id());
return canProtect;
}
CoherentBufferTracker::CoherentBufferTracker() : mEnabled(false), mShadowMemoryEnabled(false)
{
mPageSize = GetPageSize();
}
CoherentBufferTracker::~CoherentBufferTracker()
{
disable();
}
PageFaultHandlerRangeType CoherentBufferTracker::handleWrite(uintptr_t address)
{
std::lock_guard<std::mutex> lock(mMutex);
auto pagesInBuffers = getBufferPagesForAddress(address);
if (pagesInBuffers.empty())
{
ERR() << "Didn't find a tracked buffer containing " << reinterpret_cast<void *>(address);
}
for (const auto &page : pagesInBuffers)
{
std::shared_ptr<CoherentBuffer> buffer = page.first;
size_t relativePage = page.second;
buffer->setDirty(relativePage, true);
}
return pagesInBuffers.empty() ? PageFaultHandlerRangeType::OutOfRange
: PageFaultHandlerRangeType::InRange;
}
HashMap<std::shared_ptr<CoherentBuffer>, size_t> CoherentBufferTracker::getBufferPagesForAddress(
uintptr_t address)
{
HashMap<std::shared_ptr<CoherentBuffer>, size_t> foundPages;
#if defined(ANGLE_PLATFORM_ANDROID)
size_t page;
if (mShadowMemoryEnabled)
{
// Starting with Android 11 heap pointers get a tag which is stripped by the POSIX mprotect
// callback. We need to add this tag manually to the untagged pointer in order to determine
// the corresponding page.
// See: https://source.android.com/docs/security/test/tagged-pointers
// TODO(http://anglebug.com/7402): Determine when heap pointer tagging is not enabled.
constexpr unsigned long long POINTER_TAG = 0xb400000000000000;
unsigned long long taggedAddress = address | POINTER_TAG;
page = static_cast<size_t>(taggedAddress / mPageSize);
}
else
{
// VMA allocated memory pointers are not tagged.
page = address / mPageSize;
}
#else
size_t page = address / mPageSize;
#endif
for (const auto &pair : mBuffers)
{
std::shared_ptr<CoherentBuffer> buffer = pair.second;
size_t relativePage;
if (buffer->contains(page, &relativePage))
{
foundPages.insert(std::make_pair(buffer, relativePage));
}
}
return foundPages;
}
bool CoherentBufferTracker::isDirty(gl::BufferID id)
{
return mBuffers[id.value]->isDirty();
}
void CoherentBufferTracker::enable()
{
if (mEnabled)
{
return;
}
PageFaultCallback callback = [this](uintptr_t address) { return handleWrite(address); };
// This needs to be initialized after canProtectDirectly ran and can only be initialized once.
if (!mPageFaultHandler)
{
mPageFaultHandler = std::unique_ptr<PageFaultHandler>(CreatePageFaultHandler(callback));
}
bool ret = mPageFaultHandler->enable();
if (ret)
{
mEnabled = true;
}
else
{
ERR() << "Could not enable page fault handler.";
}
}
bool CoherentBufferTracker::haveBuffer(gl::BufferID id)
{
return mBuffers.find(id.value) != mBuffers.end();
}
void CoherentBufferTracker::onEndFrame()
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mEnabled)
{
return;
}
// Remove protection from all buffers
for (const auto &pair : mBuffers)
{
std::shared_ptr<CoherentBuffer> buffer = pair.second;
buffer->removeProtection(PageSharingType::NoneShared);
}
disable();
}
void CoherentBufferTracker::disable()
{
if (!mEnabled)
{
return;
}
if (mPageFaultHandler->disable())
{
mEnabled = false;
}
else
{
ERR() << "Could not disable page fault handler.";
}
if (mShadowMemoryEnabled && mBuffers.size() > 0)
{
WARN() << "Disabling coherent buffer tracking while leaving shadow memory without "
"synchronization. Expect rendering artifacts after capture ends.";
}
}
uintptr_t CoherentBufferTracker::addBuffer(gl::BufferID id, uintptr_t start, size_t size)
{
std::lock_guard<std::mutex> lock(mMutex);
if (haveBuffer(id))
{
auto buffer = mBuffers[id.value];
return buffer->getRange().start;
}
auto buffer = std::make_shared<CoherentBuffer>(start, size, mPageSize, mShadowMemoryEnabled);
uintptr_t realOrShadowStart = buffer->getRange().start;
mBuffers.insert(std::make_pair(id.value, std::move(buffer)));
return realOrShadowStart;
}
void CoherentBufferTracker::maybeUpdateShadowMemory()
{
for (const auto &pair : mBuffers)
{
std::shared_ptr<CoherentBuffer> cb = pair.second;
if (cb->isShadowDirty())
{
cb->removeProtection(PageSharingType::NoneShared);
cb->updateShadowMemory();
cb->protectAll();
}
}
}
void CoherentBufferTracker::markAllShadowDirty()
{
for (const auto &pair : mBuffers)
{
std::shared_ptr<CoherentBuffer> cb = pair.second;
cb->markShadowDirty();
}
}
PageSharingType CoherentBufferTracker::doesBufferSharePage(gl::BufferID id)
{
bool firstPageShared = false;
bool lastPageShared = false;
std::shared_ptr<CoherentBuffer> buffer = mBuffers[id.value];
AddressRange range = buffer->getRange();
size_t firstPage = range.start / mPageSize;
size_t lastPage = range.end() / mPageSize;
for (const auto &pair : mBuffers)
{
gl::BufferID otherId = {pair.first};
if (otherId != id)
{
std::shared_ptr<CoherentBuffer> otherBuffer = pair.second;
size_t relativePage;
if (otherBuffer->contains(firstPage, &relativePage))
{
firstPageShared = true;
}
else if (otherBuffer->contains(lastPage, &relativePage))
{
lastPageShared = true;
}
}
}
if (firstPageShared && !lastPageShared)
{
return PageSharingType::FirstShared;
}
else if (!firstPageShared && lastPageShared)
{
return PageSharingType::LastShared;
}
else if (firstPageShared && lastPageShared)
{
return PageSharingType::FirstAndLastShared;
}
else
{
return PageSharingType::NoneShared;
}
}
void CoherentBufferTracker::removeBuffer(gl::BufferID id)
{
std::lock_guard<std::mutex> lock(mMutex);
if (!haveBuffer(id))
{
return;
}
// Synchronize graphics buffer memory before the buffer is removed from the tracker.
if (mShadowMemoryEnabled)
{
mBuffers[id.value]->updateBufferMemory();
}
// If the buffer shares pages with other tracked buffers,
// don't unprotect the overlapping pages.
PageSharingType sharingType = doesBufferSharePage(id);
mBuffers[id.value]->removeProtection(sharingType);
mBuffers.erase(id.value);
}
void *FrameCaptureShared::maybeGetShadowMemoryPointer(gl::Buffer *buffer,
GLsizeiptr length,
GLbitfield access)
{
if (!(access & GL_MAP_COHERENT_BIT_EXT) || !mCoherentBufferTracker.isShadowMemoryEnabled())
{
return buffer->getMapPointer();
}
mCoherentBufferTracker.enable();
uintptr_t realMapPointer = reinterpret_cast<uintptr_t>(buffer->getMapPointer());
return (void *)mCoherentBufferTracker.addBuffer(buffer->id(), realMapPointer, length);
}
void FrameCaptureShared::determineMemoryProtectionSupport(gl::Context *context)
{
// Skip this test if shadow memory was force enabled or shadow memory requirement was detected
// previously
if (mCoherentBufferTracker.isShadowMemoryEnabled())
{
return;
}
// These known devices must use shadow memory
HashMap<std::string, std::vector<std::string>> denyList = {
{"Google", {"Pixel 6", "Pixel 6 Pro", "Pixel 6a", "Pixel 7", "Pixel 7 Pro"}},
};
angle::SystemInfo info;
angle::GetSystemInfo(&info);
bool isDeviceDenyListed = false;
if (denyList.find(info.machineManufacturer) != denyList.end())
{
const std::vector<std::string> &models = denyList[info.machineManufacturer];
isDeviceDenyListed =
std::find(models.begin(), models.end(), info.machineModelName) != models.end();
}
if (isDeviceDenyListed)
{
WARN() << "Direct memory protection not possible on deny listed device '"
<< info.machineModelName
<< "', enabling shadow memory for coherent buffer tracking.";
mCoherentBufferTracker.enableShadowMemory();
}
else
{
// Device is not on deny listed. Run a test if we actually can protect directly. Do this
// only on assertion enabled builds.
ASSERT(mCoherentBufferTracker.canProtectDirectly(context));
}
}
void FrameCaptureShared::trackBufferMapping(const gl::Context *context,
CallCapture *call,
gl::BufferID id,
gl::Buffer *buffer,
GLintptr offset,
GLsizeiptr length,
bool writable,
bool coherent)
{
// Track that the buffer was mapped
mResourceTracker.setBufferMapped(context->id(), id.value);
if (writable)
{
// If this buffer was mapped writable, we don't have any visibility into what
// happens to it. Therefore, remember the details about it, and we'll read it back
// on Unmap to repopulate it during replay.
mBufferDataMap[id] = std::make_pair(offset, length);
// Track that this buffer was potentially modified
mResourceTracker.getTrackedResource(context->id(), ResourceIDType::Buffer)
.setModifiedResource(id.value);
// Track coherent buffer
// Check if capture is active to not initialize the coherent buffer tracker on the
// first coherent glMapBufferRange call.
if (coherent && isCaptureActive())
{
mCoherentBufferTracker.enable();
// When not using shadow memory, adding buffers to the tracking happens here instead of
// during mapping
if (!mCoherentBufferTracker.isShadowMemoryEnabled())
{
uintptr_t data = reinterpret_cast<uintptr_t>(buffer->getMapPointer());
mCoherentBufferTracker.addBuffer(id, data, length);
}
}
}
}
void FrameCaptureShared::trackTextureUpdate(const gl::Context *context, const CallCapture &call)
{
int index = 0;
std::string paramName = "targetPacked";
ParamType paramType = ParamType::TTextureTarget;
// Some calls provide the textureID directly
// For the rest, look it up based on the currently bound texture
switch (call.entryPoint)
{
case EntryPoint::GLCompressedCopyTextureCHROMIUM:
index = 1;
paramName = "destIdPacked";
paramType = ParamType::TTextureID;
break;
case EntryPoint::GLCopyTextureCHROMIUM:
case EntryPoint::GLCopySubTextureCHROMIUM:
case EntryPoint::GLCopyTexture3DANGLE:
index = 3;
paramName = "destIdPacked";
paramType = ParamType::TTextureID;
break;
case EntryPoint::GLCopyImageSubData:
case EntryPoint::GLCopyImageSubDataEXT:
case EntryPoint::GLCopyImageSubDataOES:
index = 7;
paramName = "dstTarget";
paramType = ParamType::TGLenum;
break;
default:
break;
}
GLuint id = 0;
switch (paramType)
{
case ParamType::TTextureTarget:
{
gl::TextureTarget targetPacked =
call.params.getParam(paramName.c_str(), ParamType::TTextureTarget, index)
.value.TextureTargetVal;
gl::TextureType textureType = gl::TextureTargetToType(targetPacked);
gl::Texture *texture = context->getState().getTargetTexture(textureType);
id = texture->id().value;
break;
}
case ParamType::TTextureID:
{
gl::TextureID destIDPacked =
call.params.getParam(paramName.c_str(), ParamType::TTextureID, index)
.value.TextureIDVal;
id = destIDPacked.value;
break;
}
case ParamType::TGLenum:
{
GLenum target =
call.params.getParam(paramName.c_str(), ParamType::TGLenum, index).value.GLenumVal;
if (target == GL_TEXTURE_CUBE_MAP)
{
// CopyImageSubData doesn't support cube faces, but PackedParams requires one
target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
}
gl::TextureTarget targetPacked = gl::PackParam<gl::TextureTarget>(target);
gl::TextureType textureType = gl::TextureTargetToType(targetPacked);
gl::Texture *texture = context->getState().getTargetTexture(textureType);
id = texture->id().value;
break;
}
default:
ERR() << "Unhandled paramType= " << static_cast<int>(paramType);
UNREACHABLE();
break;
}
// Mark it as modified
mResourceTracker.getTrackedResource(context->id(), ResourceIDType::Texture)
.setModifiedResource(id);
}
void FrameCaptureShared::trackDefaultUniformUpdate(const gl::Context *context,
const CallCapture &call)
{
DefaultUniformType defaultUniformType = GetDefaultUniformType(call);
GLuint programID = 0;
int location = 0;
// We track default uniform updates by program and location, so look them up in parameters
if (defaultUniformType == DefaultUniformType::CurrentProgram)
{
programID = context->getActiveLinkedProgram()->id().value;
location = call.params.getParam("locationPacked", ParamType::TUniformLocation, 0)
.value.UniformLocationVal.value;
}
else
{
ASSERT(defaultUniformType == DefaultUniformType::SpecifiedProgram);
programID = call.params.getParam("programPacked", ParamType::TShaderProgramID, 0)
.value.ShaderProgramIDVal.value;
location = call.params.getParam("locationPacked", ParamType::TUniformLocation, 1)
.value.UniformLocationVal.value;
}
const TrackedResource &trackedShaderProgram =
mResourceTracker.getTrackedResource(context->id(), ResourceIDType::ShaderProgram);
const ResourceSet &startingPrograms = trackedShaderProgram.getStartingResources();
const ResourceSet &programsToRegen = trackedShaderProgram.getResourcesToRegen();
// If this program was in our starting set, track its uniform updates. Unless it was deleted,
// then its uniforms will all be regenned along wih with the program.
if (startingPrograms.find(programID) != startingPrograms.end() &&
programsToRegen.find(programID) == programsToRegen.end())
{
// Track that we need to set this default uniform value again
mResourceTracker.setModifiedDefaultUniform({programID}, {location});
}
}
void FrameCaptureShared::trackVertexArrayUpdate(const gl::Context *context, const CallCapture &call)
{
// Look up the currently bound vertex array
gl::VertexArrayID id = context->getState().getVertexArray()->id();
// Mark it as modified
mResourceTracker.getTrackedResource(context->id(), ResourceIDType::VertexArray)
.setModifiedResource(id.value);
}
void FrameCaptureShared::updateCopyImageSubData(CallCapture &call)
{
// This call modifies srcName and dstName to no longer be object IDs (GLuint), but actual
// packed types that can remapped using gTextureMap and gRenderbufferMap
GLint srcName = call.params.getParam("srcName", ParamType::TGLuint, 0).value.GLuintVal;
GLenum srcTarget = call.params.getParam("srcTarget", ParamType::TGLenum, 1).value.GLenumVal;
switch (srcTarget)
{
case GL_RENDERBUFFER:
{
// Convert the GLuint to RenderbufferID
gl::RenderbufferID srcRenderbufferID = {static_cast<GLuint>(srcName)};
call.params.setValueParamAtIndex("srcName", ParamType::TRenderbufferID,
srcRenderbufferID, 0);
break;
}
case GL_TEXTURE_2D:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_3D:
case GL_TEXTURE_CUBE_MAP:
{
// Convert the GLuint to TextureID
gl::TextureID srcTextureID = {static_cast<GLuint>(srcName)};
call.params.setValueParamAtIndex("srcName", ParamType::TTextureID, srcTextureID, 0);
break;
}
default:
ERR() << "Unhandled srcTarget = " << srcTarget;
UNREACHABLE();
break;
}
// Change dstName to the appropriate type based on dstTarget
GLint dstName = call.params.getParam("dstName", ParamType::TGLuint, 6).value.GLuintVal;
GLenum dstTarget = call.params.getParam("dstTarget", ParamType::TGLenum, 7).value.GLenumVal;
switch (dstTarget)
{
case GL_RENDERBUFFER:
{
// Convert the GLuint to RenderbufferID
gl::RenderbufferID dstRenderbufferID = {static_cast<GLuint>(dstName)};
call.params.setValueParamAtIndex("dstName", ParamType::TRenderbufferID,
dstRenderbufferID, 6);
break;
}
case GL_TEXTURE_2D:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_3D:
case GL_TEXTURE_CUBE_MAP:
{
// Convert the GLuint to TextureID
gl::TextureID dstTextureID = {static_cast<GLuint>(dstName)};
call.params.setValueParamAtIndex("dstName", ParamType::TTextureID, dstTextureID, 6);
break;
}
default:
ERR() << "Unhandled dstTarget = " << dstTarget;
UNREACHABLE();
break;
}
}
void FrameCaptureShared::overrideProgramBinary(const gl::Context *context,
CallCapture &inCall,
std::vector<CallCapture> &outCalls)
{
// Program binaries are inherently non-portable, even between two ANGLE builds.
// If an application is using glProgramBinary in the middle of a trace, we need to replace
// those calls with an equivalent sequence of portable calls.
//
// For example, here is a sequence an app could use for glProgramBinary:
//
// gShaderProgramMap[42] = glCreateProgram();
// glProgramBinary(gShaderProgramMap[42], GL_PROGRAM_BINARY_ANGLE, gBinaryData[x], 1000);
// glGetProgramiv(gShaderProgramMap[42], GL_LINK_STATUS, gReadBuffer);
// glGetProgramiv(gShaderProgramMap[42], GL_PROGRAM_BINARY_LENGTH, gReadBuffer);
//
// With this override, the glProgramBinary call will be replaced like so:
//
// gShaderProgramMap[42] = glCreateProgram();
// === Begin override ===
// gShaderProgramMap[43] = glCreateShader(GL_VERTEX_SHADER);
// glShaderSource(gShaderProgramMap[43], 1, string_0, &gBinaryData[100]);
// glCompileShader(gShaderProgramMap[43]);
// glAttachShader(gShaderProgramMap[42], gShaderProgramMap[43]);
// glDeleteShader(gShaderProgramMap[43]);
// gShaderProgramMap[43] = glCreateShader(GL_FRAGMENT_SHADER);
// glShaderSource(gShaderProgramMap[43], 1, string_1, &gBinaryData[200]);
// glCompileShader(gShaderProgramMap[43]);
// glAttachShader(gShaderProgramMap[42], gShaderProgramMap[43]);
// glDeleteShader(gShaderProgramMap[43]);
// glBindAttribLocation(gShaderProgramMap[42], 0, "attrib1");
// glBindAttribLocation(gShaderProgramMap[42], 1, "attrib2");
// glLinkProgram(gShaderProgramMap[42]);
// UpdateUniformLocation(gShaderProgramMap[42], "foo", 0, 20);
// UpdateUniformLocation(gShaderProgramMap[42], "bar", 72, 1);
// glUseProgram(gShaderProgramMap[42]);
// UpdateCurrentProgram(gShaderProgramMap[42]);
// glUniform4fv(gUniformLocations[gCurrentProgram][0], 20, &gBinaryData[300]);
// glUniform1iv(gUniformLocations[gCurrentProgram][72], 1, &gBinaryData[400]);
// === End override ===
// glGetProgramiv(gShaderProgramMap[42], GL_LINK_STATUS, gReadBuffer);
// glGetProgramiv(gShaderProgramMap[42], GL_PROGRAM_BINARY_LENGTH, gReadBuffer);
//
// To facilitate this override, we are serializing each shader stage source into the binary
// itself. See Program::serialize and Program::deserialize. Once extracted from the binary,
// they will be available via getProgramSources.
gl::ShaderProgramID id = inCall.params.getParam("programPacked", ParamType::TShaderProgramID, 0)
.value.ShaderProgramIDVal;
gl::Program *program = context->getProgramResolveLink(id);
ASSERT(program);
mResourceTracker.onShaderProgramAccess(id);
gl::ShaderProgramID tempShaderStartID = {mResourceTracker.getMaxShaderPrograms()};
GenerateLinkedProgram(context, context->getState(), &mResourceTracker, &outCalls, program, id,
tempShaderStartID, getProgramSources(id));
}
void FrameCaptureShared::captureCustomMapBufferFromContext(const gl::Context *context,
const char *entryPointName,
CallCapture &call,
std::vector<CallCapture> &callsOut)
{
gl::BufferBinding binding =
call.params.getParam("targetPacked", ParamType::TBufferBinding, 0).value.BufferBindingVal;
gl::Buffer *buffer = context->getState().getTargetBuffer(binding);
if (call.entryPoint == EntryPoint::GLMapBufferRange ||
call.entryPoint == EntryPoint::GLMapBufferRangeEXT)
{
GLintptr offset = call.params.getParam("offset", ParamType::TGLintptr, 1).value.GLintptrVal;
GLsizeiptr length =
call.params.getParam("length", ParamType::TGLsizeiptr, 2).value.GLsizeiptrVal;
GLbitfield access =
call.params.getParam("access", ParamType::TGLbitfield, 3).value.GLbitfieldVal;
trackBufferMapping(context, &call, buffer->id(), buffer, offset, length,
access & GL_MAP_WRITE_BIT, access & GL_MAP_COHERENT_BIT_EXT);
}
else
{
ASSERT(call.entryPoint == EntryPoint::GLMapBufferOES);
GLenum access = call.params.getParam("access", ParamType::TGLenum, 1).value.GLenumVal;
bool writeAccess =
(access == GL_WRITE_ONLY_OES || access == GL_WRITE_ONLY || access == GL_READ_WRITE);
trackBufferMapping(context, &call, buffer->id(), buffer, 0,
static_cast<GLsizeiptr>(buffer->getSize()), writeAccess, false);
}
CaptureCustomMapBuffer(entryPointName, call, callsOut, buffer->id());
}
void FrameCaptureShared::maybeOverrideEntryPoint(const gl::Context *context,
CallCapture &inCall,
std::vector<CallCapture> &outCalls)
{
switch (inCall.entryPoint)
{
case EntryPoint::GLCopyImageSubData:
case EntryPoint::GLCopyImageSubDataEXT:
case EntryPoint::GLCopyImageSubDataOES:
{
// We must look at the src and dst target types to determine which remap table to use
updateCopyImageSubData(inCall);
outCalls.emplace_back(std::move(inCall));
break;
}
case EntryPoint::GLProgramBinary:
case EntryPoint::GLProgramBinaryOES:
{
// Binary formats are not portable at all, so replace the calls with full linking
// sequence
overrideProgramBinary(context, inCall, outCalls);
break;
}
case EntryPoint::GLUniformBlockBinding:
{
CaptureCustomUniformBlockBinding(inCall, outCalls);
break;
}
case EntryPoint::GLMapBufferRange:
{
captureCustomMapBufferFromContext(context, "MapBufferRange", inCall, outCalls);
break;
}
case EntryPoint::GLMapBufferRangeEXT:
{
captureCustomMapBufferFromContext(context, "MapBufferRangeEXT", inCall, outCalls);
break;
}
case EntryPoint::GLMapBuffer:
{
// Currently desktop GL is not implemented.
UNREACHABLE();
break;
}
case EntryPoint::GLMapBufferOES:
{
captureCustomMapBufferFromContext(context, "MapBufferOES", inCall, outCalls);
break;
}
case EntryPoint::GLCreateShader:
{
CaptureCustomShaderProgram("CreateShader", inCall, outCalls);
break;
}
case EntryPoint::GLCreateProgram:
{
CaptureCustomShaderProgram("CreateProgram", inCall, outCalls);
break;
}
case EntryPoint::GLCreateShaderProgramv:
{
CaptureCustomShaderProgram("CreateShaderProgramv", inCall, outCalls);
break;
}
case EntryPoint::GLFenceSync:
{
CaptureCustomFenceSync(inCall, outCalls);
break;
}
case EntryPoint::EGLCreateImage:
{
CaptureCustomCreateEGLImage("CreateEGLImage", inCall, outCalls);
break;
}
case EntryPoint::EGLCreateImageKHR:
{
CaptureCustomCreateEGLImage("CreateEGLImageKHR", inCall, outCalls);
break;
}
case EntryPoint::EGLCreateSync:
{
CaptureCustomCreateEGLSync("CreateEGLSync", inCall, outCalls);
break;
}
case EntryPoint::EGLCreateSyncKHR:
{
CaptureCustomCreateEGLSync("CreateEGLSyncKHR", inCall, outCalls);
break;
}
case EntryPoint::EGLCreatePbufferSurface:
{
CaptureCustomCreatePbufferSurface(inCall, outCalls);
break;
}
case EntryPoint::EGLCreateNativeClientBufferANDROID:
{
CaptureCustomCreateNativeClientbuffer(inCall, outCalls);
break;
}
default:
{
// Pass the single call through
outCalls.emplace_back(std::move(inCall));
break;
}
}
}
void FrameCaptureShared::maybeCaptureCoherentBuffers(const gl::Context *context)
{
if (!isCaptureActive())
{
return;
}
std::lock_guard<std::mutex> lock(mCoherentBufferTracker.mMutex);
for (const auto &pair : mCoherentBufferTracker.mBuffers)
{
gl::BufferID id = {pair.first};
if (mCoherentBufferTracker.isDirty(id))
{
captureCoherentBufferSnapshot(context, id);
}
}
}
void FrameCaptureShared::maybeCaptureDrawArraysClientData(const gl::Context *context,
CallCapture &call,
size_t instanceCount)
{
if (!context->getStateCache().hasAnyActiveClientAttrib())
{
return;
}
// Get counts from paramBuffer.
GLint firstVertex =
call.params.getParamFlexName("first", "start", ParamType::TGLint, 1).value.GLintVal;
GLsizei drawCount = call.params.getParam("count", ParamType::TGLsizei, 2).value.GLsizeiVal;
captureClientArraySnapshot(context, firstVertex + drawCount, instanceCount);
}
void FrameCaptureShared::maybeCaptureDrawElementsClientData(const gl::Context *context,
CallCapture &call,
size_t instanceCount)
{
if (!context->getStateCache().hasAnyActiveClientAttrib())
{
return;
}
// if the count is zero then the index evaluation is not valid and we wouldn't be drawing
// anything anyway, so skip capturing
GLsizei count = call.params.getParam("count", ParamType::TGLsizei, 1).value.GLsizeiVal;
if (count == 0)
{
return;
}
gl::DrawElementsType drawElementsType =
call.params.getParam("typePacked", ParamType::TDrawElementsType, 2)
.value.DrawElementsTypeVal;
const void *indices =
call.params.getParam("indices", ParamType::TvoidConstPointer, 3).value.voidConstPointerVal;
gl::IndexRange indexRange;
bool restart = context->getState().isPrimitiveRestartEnabled();
gl::Buffer *elementArrayBuffer = context->getState().getVertexArray()->getElementArrayBuffer();
if (elementArrayBuffer)
{
size_t offset = reinterpret_cast<size_t>(indices);
(void)elementArrayBuffer->getIndexRange(context, drawElementsType, offset, count, restart,
&indexRange);
}
else
{
ASSERT(indices);
indexRange = gl::ComputeIndexRange(drawElementsType, indices, count, restart);
}
// index starts from 0
captureClientArraySnapshot(context, indexRange.end + 1, instanceCount);
}
template <typename AttribT, typename FactoryT>
void CreateEGLImagePreCallUpdate(const CallCapture &call,
ResourceTracker &resourceTracker,
ParamType paramType,
FactoryT factory)
{
EGLImage image = call.params.getReturnValue().value.EGLImageVal;
const ParamCapture &param = call.params.getParam("attrib_list", paramType, 4);
const AttribT *attribs =
param.data.empty() ? nullptr : reinterpret_cast<const AttribT *>(param.data[0].data());
egl::AttributeMap attributeMap = factory(attribs);
attributeMap.initializeWithoutValidation();
resourceTracker.getImageToAttribTable().insert(
std::pair<EGLImage, egl::AttributeMap>(image, attributeMap));
}
void FrameCaptureShared::maybeCapturePreCallUpdates(
const gl::Context *context,
CallCapture &call,
std::vector<CallCapture> *shareGroupSetupCalls,
ResourceIDToSetupCallsMap *resourceIDToSetupCalls)
{
switch (call.entryPoint)
{
case EntryPoint::GLVertexAttribPointer:
case EntryPoint::GLVertexPointer:
case EntryPoint::GLColorPointer:
case EntryPoint::GLTexCoordPointer:
case EntryPoint::GLNormalPointer:
case EntryPoint::GLPointSizePointerOES:
{
// Get array location
GLuint index = 0;
if (call.entryPoint == EntryPoint::GLVertexAttribPointer)
{
index = call.params.getParam("index", ParamType::TGLuint, 0).value.GLuintVal;
}
else
{
gl::ClientVertexArrayType type;
switch (call.entryPoint)
{
case EntryPoint::GLVertexPointer:
type = gl::ClientVertexArrayType::Vertex;
break;
case EntryPoint::GLColorPointer:
type = gl::ClientVertexArrayType::Color;
break;
case EntryPoint::GLTexCoordPointer:
type = gl::ClientVertexArrayType::TextureCoord;
break;
case EntryPoint::GLNormalPointer:
type = gl::ClientVertexArrayType::Normal;
break;
case EntryPoint::GLPointSizePointerOES:
type = gl::ClientVertexArrayType::PointSize;
break;
default:
UNREACHABLE();
type = gl::ClientVertexArrayType::InvalidEnum;
}
index = gl::GLES1Renderer::VertexArrayIndex(type, context->getState().gles1());
}
if (call.params.hasClientArrayData())
{
mClientVertexArrayMap[index] = static_cast<int>(mFrameCalls.size());
}
else
{
mClientVertexArrayMap[index] = -1;
}
break;
}
case EntryPoint::GLGenFramebuffers:
case EntryPoint::GLGenFramebuffersOES:
{
GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
const gl::FramebufferID *framebufferIDs =
call.params.getParam("framebuffersPacked", ParamType::TFramebufferIDPointer, 1)
.value.FramebufferIDPointerVal;
for (GLsizei i = 0; i < count; i++)
{
handleGennedResource(context, framebufferIDs[i]);
}
break;
}
case EntryPoint::GLBindFramebuffer:
case EntryPoint::GLBindFramebufferOES:
maybeGenResourceOnBind<gl::FramebufferID>(context, call);
break;
case EntryPoint::GLGenRenderbuffers:
case EntryPoint::GLGenRenderbuffersOES:
{
GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
const gl::RenderbufferID *renderbufferIDs =
call.params.getParam("renderbuffersPacked", ParamType::TRenderbufferIDPointer, 1)
.value.RenderbufferIDPointerVal;
for (GLsizei i = 0; i < count; i++)
{
handleGennedResource(context, renderbufferIDs[i]);
}
break;
}
case EntryPoint::GLBindRenderbuffer:
case EntryPoint::GLBindRenderbufferOES:
maybeGenResourceOnBind<gl::RenderbufferID>(context, call);
break;
case EntryPoint::GLDeleteRenderbuffers:
case EntryPoint::GLDeleteRenderbuffersOES:
{
// Look up how many renderbuffers are being deleted
GLsizei n = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
// Look up the pointer to list of renderbuffers
const gl::RenderbufferID *renderbufferIDs =
call.params
.getParam("renderbuffersPacked", ParamType::TRenderbufferIDConstPointer, 1)
.value.RenderbufferIDConstPointerVal;
// For each renderbuffer listed for deletion
for (int32_t i = 0; i < n; ++i)
{
// If we're capturing, track what renderbuffers have been deleted
handleDeletedResource(context, renderbufferIDs[i]);
}
break;
}
case EntryPoint::GLGenTextures:
{
GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
const gl::TextureID *textureIDs =
call.params.getParam("texturesPacked", ParamType::TTextureIDPointer, 1)
.value.TextureIDPointerVal;
for (GLsizei i = 0; i < count; i++)
{
// If we're capturing, track what new textures have been genned
handleGennedResource(context, textureIDs[i]);
}
break;
}
case EntryPoint::GLBindTexture:
maybeGenResourceOnBind<gl::TextureID>(context, call);
break;
case EntryPoint::GLDeleteBuffers:
{
GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
const gl::BufferID *bufferIDs =
call.params.getParam("buffersPacked", ParamType::TBufferIDConstPointer, 1)
.value.BufferIDConstPointerVal;
for (GLsizei i = 0; i < count; i++)
{
// For each buffer being deleted, check our backup of data and remove it
const auto &bufferDataInfo = mBufferDataMap.find(bufferIDs[i]);
if (bufferDataInfo != mBufferDataMap.end())
{
mBufferDataMap.erase(bufferDataInfo);
}
// If we're capturing, track what buffers have been deleted
handleDeletedResource(context, bufferIDs[i]);
}
break;
}
case EntryPoint::GLGenBuffers:
{
GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
const gl::BufferID *bufferIDs =
call.params.getParam("buffersPacked", ParamType::TBufferIDPointer, 1)
.value.BufferIDPointerVal;
for (GLsizei i = 0; i < count; i++)
{
handleGennedResource(context, bufferIDs[i]);
}
break;
}
case EntryPoint::GLBindBuffer:
maybeGenResourceOnBind<gl::BufferID>(context, call);
break;
case EntryPoint::GLDeleteProgramPipelines:
case EntryPoint::GLDeleteProgramPipelinesEXT:
{
GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
const gl::ProgramPipelineID *pipelineIDs =
call.params
.getParam("pipelinesPacked", ParamType::TProgramPipelineIDConstPointer, 1)
.value.ProgramPipelineIDPointerVal;
for (GLsizei i = 0; i < count; i++)
{
handleDeletedResource(context, pipelineIDs[i]);
}
break;
}
case EntryPoint::GLGenProgramPipelines:
case EntryPoint::GLGenProgramPipelinesEXT:
{
GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
const gl::ProgramPipelineID *pipelineIDs =
call.params.getParam("pipelinesPacked", ParamType::TProgramPipelineIDPointer, 1)
.value.ProgramPipelineIDPointerVal;
for (GLsizei i = 0; i < count; i++)
{
handleGennedResource(context, pipelineIDs[i]);
}
break;
}
case EntryPoint::GLDeleteSync:
{
gl::SyncID sync =
call.params.getParam("syncPacked", ParamType::TSyncID, 0).value.SyncIDVal;
FrameCaptureShared *frameCaptureShared =
context->getShareGroup()->getFrameCaptureShared();
// If we're capturing, track which fence sync has been deleted
if (frameCaptureShared->isCaptureActive())
{
mResourceTracker.setDeletedFenceSync(sync);
}
break;
}
case EntryPoint::GLDrawArrays:
{
maybeCaptureDrawArraysClientData(context, call, 1);
maybeCaptureCoherentBuffers(context);
break;
}
case EntryPoint::GLDrawArraysInstanced:
case EntryPoint::GLDrawArraysInstancedANGLE:
case EntryPoint::GLDrawArraysInstancedEXT:
{
GLsizei instancecount =
call.params.getParamFlexName("instancecount", "primcount", ParamType::TGLsizei, 3)
.value.GLsizeiVal;
maybeCaptureDrawArraysClientData(context, call, instancecount);
maybeCaptureCoherentBuffers(context);
break;
}
case EntryPoint::GLDrawElements:
{
maybeCaptureDrawElementsClientData(context, call, 1);
maybeCaptureCoherentBuffers(context);
break;
}
case EntryPoint::GLDrawElementsInstanced:
case EntryPoint::GLDrawElementsInstancedANGLE:
case EntryPoint::GLDrawElementsInstancedEXT:
{
GLsizei instancecount =
call.params.getParamFlexName("instancecount", "primcount", ParamType::TGLsizei, 4)
.value.GLsizeiVal;
maybeCaptureDrawElementsClientData(context, call, instancecount);
maybeCaptureCoherentBuffers(context);
break;
}
case EntryPoint::GLCreateShaderProgramv:
{
// Refresh the cached shader sources.
// The command CreateShaderProgramv() creates a stand-alone program from an array of
// null-terminated source code strings for a single shader type, so we need update the
// Shader and Program sources, similar to GLCompileShader + GLLinkProgram handling.
gl::ShaderProgramID programID = {call.params.getReturnValue().value.GLuintVal};
const ParamCapture &paramCapture =
call.params.getParam("typePacked", ParamType::TShaderType, 0);
const ParamCapture &lineCount = call.params.getParam("count", ParamType::TGLsizei, 1);
const ParamCapture &strings =
call.params.getParam("strings", ParamType::TGLcharConstPointerPointer, 2);
std::ostringstream sourceString;
for (int i = 0; i < lineCount.value.GLsizeiVal; ++i)
{
sourceString << strings.value.GLcharConstPointerPointerVal[i];
}
gl::ShaderType shaderType = paramCapture.value.ShaderTypeVal;
ProgramSources source;
source[shaderType] = sourceString.str();
setProgramSources(programID, source);
handleGennedResource(context, programID);
mResourceTracker.setShaderProgramType(programID, ShaderProgramType::ProgramType);
break;
}
case EntryPoint::GLCreateProgram:
{
// If we're capturing, track which programs have been created
gl::ShaderProgramID programID = {call.params.getReturnValue().value.GLuintVal};
handleGennedResource(context, programID);
mResourceTracker.setShaderProgramType(programID, ShaderProgramType::ProgramType);
break;
}
case EntryPoint::GLDeleteProgram:
{
// If we're capturing, track which programs have been deleted
const ParamCapture &param =
call.params.getParam("programPacked", ParamType::TShaderProgramID, 0);
handleDeletedResource(context, param.value.ShaderProgramIDVal);
// If this assert fires, it means a ShaderProgramID has changed from program to shader
// which is unsupported
ASSERT(mResourceTracker.getShaderProgramType(param.value.ShaderProgramIDVal) ==
ShaderProgramType::ProgramType);
break;
}
case EntryPoint::GLCreateShader:
{
// If we're capturing, track which shaders have been created
gl::ShaderProgramID shaderID = {call.params.getReturnValue().value.GLuintVal};
handleGennedResource(context, shaderID);
mResourceTracker.setShaderProgramType(shaderID, ShaderProgramType::ShaderType);
break;
}
case EntryPoint::GLDeleteShader:
{
// If we're capturing, track which shaders have been deleted
const ParamCapture &param =
call.params.getParam("shaderPacked", ParamType::TShaderProgramID, 0);
handleDeletedResource(context, param.value.ShaderProgramIDVal);
// If this assert fires, it means a ShaderProgramID has changed from shader to program
// which is unsupported
ASSERT(mResourceTracker.getShaderProgramType(param.value.ShaderProgramIDVal) ==
ShaderProgramType::ShaderType);
break;
}
case EntryPoint::GLCompileShader:
{
// Refresh the cached shader sources.
gl::ShaderProgramID shaderID =
call.params.getParam("shaderPacked", ParamType::TShaderProgramID, 0)
.value.ShaderProgramIDVal;
const gl::Shader *shader = context->getShader(shaderID);
// Shaders compiled for ProgramBinary will not have a shader created
if (shader)
{
setShaderSource(shaderID, shader->getSourceString());
}
break;
}
case EntryPoint::GLLinkProgram:
{
// Refresh the cached program sources.
gl::ShaderProgramID programID =
call.params.getParam("programPacked", ParamType::TShaderProgramID, 0)
.value.ShaderProgramIDVal;
const gl::Program *program = context->getProgramResolveLink(programID);
// Programs linked in support of ProgramBinary will not have attached shaders
if (program->getState().hasAttachedShader())
{
setProgramSources(programID, GetAttachedProgramSources(program));
}
break;
}
case EntryPoint::GLCompressedTexImage1D:
case EntryPoint::GLCompressedTexSubImage1D:
{
UNIMPLEMENTED();
break;
}
case EntryPoint::GLDeleteTextures:
{
// Free any TextureLevelDataMap entries being tracked for this texture
// This is to cover the scenario where a texture has been created, its
// levels cached, then texture deleted and recreated, receiving the same ID
// Look up how many textures are being deleted
GLsizei n = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
// Look up the pointer to list of textures
const gl::TextureID *textureIDs =
call.params.getParam("texturesPacked", ParamType::TTextureIDConstPointer, 1)
.value.TextureIDConstPointerVal;
// For each texture listed for deletion
for (int32_t i = 0; i < n; ++i)
{
// If we're capturing, track what textures have been deleted
handleDeletedResource(context, textureIDs[i]);
}
break;
}
case EntryPoint::GLMapBuffer:
case EntryPoint::GLMapBufferOES:
{
gl::BufferBinding target =
call.params.getParam("targetPacked", ParamType::TBufferBinding, 0)
.value.BufferBindingVal;
GLbitfield access =
call.params.getParam("access", ParamType::TGLenum, 1).value.GLenumVal;
gl::Buffer *buffer = context->getState().getTargetBuffer(target);
GLintptr offset = 0;
GLsizeiptr length = static_cast<GLsizeiptr>(buffer->getSize());
bool writable =
access == GL_WRITE_ONLY_OES || access == GL_WRITE_ONLY || access == GL_READ_WRITE;
FrameCaptureShared *frameCaptureShared =
context->getShareGroup()->getFrameCaptureShared();
frameCaptureShared->trackBufferMapping(context, &call, buffer->id(), buffer, offset,
length, writable, false);
break;
}
case EntryPoint::GLUnmapNamedBuffer:
{
UNIMPLEMENTED();
break;
}
case EntryPoint::GLUnmapBuffer:
case EntryPoint::GLUnmapBufferOES:
{
// See if we need to capture the buffer contents
captureMappedBufferSnapshot(context, call);
// Track that the buffer was unmapped, for use during state reset
gl::BufferBinding target =
call.params.getParam("targetPacked", ParamType::TBufferBinding, 0)
.value.BufferBindingVal;
gl::Buffer *buffer = context->getState().getTargetBuffer(target);
mResourceTracker.setBufferUnmapped(context->id(), buffer->id().value);
// Remove from CoherentBufferTracker
mCoherentBufferTracker.removeBuffer(buffer->id());
break;
}
case EntryPoint::GLBufferData:
case EntryPoint::GLBufferSubData:
{
gl::BufferBinding target =
call.params.getParam("targetPacked", ParamType::TBufferBinding, 0)
.value.BufferBindingVal;
gl::Buffer *buffer = context->getState().getTargetBuffer(target);
// Track that this buffer's contents have been modified
mResourceTracker.getTrackedResource(context->id(), ResourceIDType::Buffer)
.setModifiedResource(buffer->id().value);
// BufferData is equivalent to UnmapBuffer, for what we're tracking.
// From the ES 3.1 spec in BufferData section:
// If any portion of the buffer object is mapped in the current context or any
// context current to another thread, it is as though UnmapBuffer (see section
// 6.3.1) is executed in each such context prior to deleting the existing data
// store.
// Track that the buffer was unmapped, for use during state reset
mResourceTracker.setBufferUnmapped(context->id(), buffer->id().value);
break;
}
case EntryPoint::GLCopyBufferSubData:
{
maybeCaptureCoherentBuffers(context);
break;
}
case EntryPoint::GLFinish:
{
// When using shadow memory we might need to synchronize it here.
if (mCoherentBufferTracker.isShadowMemoryEnabled())
{
mCoherentBufferTracker.maybeUpdateShadowMemory();
}
break;
}
case EntryPoint::GLDeleteFramebuffers:
case EntryPoint::GLDeleteFramebuffersOES:
{
// Look up how many framebuffers are being deleted
GLsizei n = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
// Look up the pointer to list of framebuffers
const gl::FramebufferID *framebufferIDs =
call.params.getParam("framebuffersPacked", ParamType::TFramebufferIDConstPointer, 1)
.value.FramebufferIDConstPointerVal;
// For each framebuffer listed for deletion
for (int32_t i = 0; i < n; ++i)
{
// If we're capturing, track what framebuffers have been deleted
handleDeletedResource(context, framebufferIDs[i]);
}
break;
}
case EntryPoint::GLUseProgram:
{
if (isCaptureActive())
{
context->getFrameCapture()->getStateResetHelper().setEntryPointDirty(
EntryPoint::GLUseProgram);
}
break;
}
case EntryPoint::GLGenVertexArrays:
case EntryPoint::GLGenVertexArraysOES:
{
GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
const gl::VertexArrayID *arrayIDs =
call.params.getParam("arraysPacked", ParamType::TVertexArrayIDPointer, 1)
.value.VertexArrayIDPointerVal;
for (GLsizei i = 0; i < count; i++)
{
handleGennedResource(context, arrayIDs[i]);
}
break;
}
case EntryPoint::GLDeleteVertexArrays:
case EntryPoint::GLDeleteVertexArraysOES:
{
GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
const gl::VertexArrayID *arrayIDs =
call.params.getParam("arraysPacked", ParamType::TVertexArrayIDConstPointer, 1)
.value.VertexArrayIDConstPointerVal;
for (GLsizei i = 0; i < count; i++)
{
// If we're capturing, track which vertex arrays have been deleted
handleDeletedResource(context, arrayIDs[i]);
}
break;
}
case EntryPoint::GLBindVertexArray:
case EntryPoint::GLBindVertexArrayOES:
{
if (isCaptureActive())
{
context->getFrameCapture()->getStateResetHelper().setEntryPointDirty(
EntryPoint::GLBindVertexArray);
}
break;
}
case EntryPoint::GLBlendFunc:
{
if (isCaptureActive())
{
context->getFrameCapture()->getStateResetHelper().setEntryPointDirty(
EntryPoint::GLBlendFunc);
}
break;
}
case EntryPoint::GLBlendFuncSeparate:
{
if (isCaptureActive())
{
context->getFrameCapture()->getStateResetHelper().setEntryPointDirty(
EntryPoint::GLBlendFuncSeparate);
}
break;
}
case EntryPoint::GLBlendEquation:
case EntryPoint::GLBlendEquationSeparate:
{
if (isCaptureActive())
{
context->getFrameCapture()->getStateResetHelper().setEntryPointDirty(
EntryPoint::GLBlendEquationSeparate);
}
break;
}
case EntryPoint::GLColorMask:
{
if (isCaptureActive())
{
context->getFrameCapture()->getStateResetHelper().setEntryPointDirty(
EntryPoint::GLColorMask);
}
break;
}
case EntryPoint::GLBlendColor:
{
if (isCaptureActive())
{
context->getFrameCapture()->getStateResetHelper().setEntryPointDirty(
EntryPoint::GLBlendColor);
}
break;
}
case EntryPoint::GLEGLImageTargetTexture2DOES:
{
gl::TextureType target =
call.params.getParam("targetPacked", ParamType::TTextureType, 0)
.value.TextureTypeVal;
egl::ImageID imageID =
call.params.getParam("imagePacked", ParamType::TImageID, 1).value.ImageIDVal;
mResourceTracker.getTextureIDToImageTable().insert(std::pair<GLuint, egl::ImageID>(
context->getState().getTargetTexture(target)->getId(), imageID));
break;
}
case EntryPoint::EGLCreateImage:
{
CreateEGLImagePreCallUpdate<EGLAttrib>(call, mResourceTracker,
ParamType::TEGLAttribPointer,
egl::AttributeMap::CreateFromAttribArray);
break;
}
case EntryPoint::EGLCreateImageKHR:
{
CreateEGLImagePreCallUpdate<EGLint>(call, mResourceTracker, ParamType::TEGLintPointer,
egl::AttributeMap::CreateFromIntArray);
break;
}
case EntryPoint::EGLCreateSync:
case EntryPoint::EGLCreateSyncKHR:
{
egl::SyncID eglSyncID = call.params.getReturnValue().value.egl_SyncIDVal;
FrameCaptureShared *frameCaptureShared =
context->getShareGroup()->getFrameCaptureShared();
// If we're capturing, track which egl sync has been created
if (frameCaptureShared->isCaptureActive())
{
handleGennedResource(context, eglSyncID);
}
break;
}
case EntryPoint::EGLDestroySync:
case EntryPoint::EGLDestroySyncKHR:
{
egl::SyncID eglSyncID =
call.params.getParam("syncPacked", ParamType::Tegl_SyncID, 1).value.egl_SyncIDVal;
FrameCaptureShared *frameCaptureShared =
context->getShareGroup()->getFrameCaptureShared();
// If we're capturing, track which EGL sync has been deleted
if (frameCaptureShared->isCaptureActive())
{
handleDeletedResource(context, eglSyncID);
}
break;
}
case EntryPoint::GLDispatchCompute:
{
// When using shadow memory we need to update the real memory here
if (mCoherentBufferTracker.isShadowMemoryEnabled())
{
maybeCaptureCoherentBuffers(context);
}
break;
}
default:
break;
}
if (IsTextureUpdate(call))
{
// If this call modified texture contents, track it for possible reset
trackTextureUpdate(context, call);
}
if (isCaptureActive() && GetDefaultUniformType(call) != DefaultUniformType::None)
{
trackDefaultUniformUpdate(context, call);
}
if (IsVertexArrayUpdate(call))
{
trackVertexArrayUpdate(context, call);
}
updateReadBufferSize(call.params.getReadBufferSize());
std::vector<gl::ShaderProgramID> shaderProgramIDs;
if (FindShaderProgramIDsInCall(call, shaderProgramIDs))
{
for (gl::ShaderProgramID shaderProgramID : shaderProgramIDs)
{
mResourceTracker.onShaderProgramAccess(shaderProgramID);
if (isCaptureActive())
{
// Track that this call referenced a ShaderProgram, setting it active for Setup
MarkResourceIDActive(ResourceIDType::ShaderProgram, shaderProgramID.value,
shareGroupSetupCalls, resourceIDToSetupCalls);
}
}
}
}
template <typename ParamValueType>
void FrameCaptureShared::maybeGenResourceOnBind(const gl::Context *context, CallCapture &call)
{
const char *paramName = ParamValueTrait<ParamValueType>::name;
const ParamType paramType = ParamValueTrait<ParamValueType>::typeID;
const ParamCapture &param = call.params.getParam(paramName, paramType, 1);
const ParamValueType id = AccessParamValue<ParamValueType>(paramType, param.value);
// Don't inject the default resource or resources that are already generated
if (id.value != 0 && !resourceIsGenerated(context, id))
{
handleGennedResource(context, id);
ResourceIDType resourceIDType = GetResourceIDTypeFromParamType(param.type);
const char *resourceName = GetResourceIDTypeName(resourceIDType);
std::stringstream updateFuncNameStr;
updateFuncNameStr << "Set" << resourceName << "ID";
std::string updateFuncName = updateFuncNameStr.str();
ParamBuffer params;
params.addValueParam("id", ParamType::TGLuint, id.value);
mFrameCalls.emplace_back(updateFuncName, std::move(params));
}
}
void FrameCaptureShared::updateResourceCountsFromParamCapture(const ParamCapture &param,
ResourceIDType idType)
{
if (idType != ResourceIDType::InvalidEnum)
{
mHasResourceType.set(idType);
// Capture resource IDs for non-pointer types.
if (strcmp(ParamTypeToString(param.type), "GLuint") == 0)
{
mMaxAccessedResourceIDs[idType] =
std::max(mMaxAccessedResourceIDs[idType], param.value.GLuintVal);
}
// Capture resource IDs for pointer types.
if (strstr(ParamTypeToString(param.type), "GLuint *") != nullptr)
{
if (param.data.size() == 1u)
{
const GLuint *dataPtr = reinterpret_cast<const GLuint *>(param.data[0].data());
size_t numHandles = param.data[0].size() / sizeof(GLuint);
for (size_t handleIndex = 0; handleIndex < numHandles; ++handleIndex)
{
mMaxAccessedResourceIDs[idType] =
std::max(mMaxAccessedResourceIDs[idType], dataPtr[handleIndex]);
}
}
}
if (idType == ResourceIDType::Sync)
{
mMaxAccessedResourceIDs[idType] =
std::max(mMaxAccessedResourceIDs[idType], param.value.GLuintVal);
}
}
}
void FrameCaptureShared::updateResourceCountsFromCallCapture(const CallCapture &call)
{
for (const ParamCapture &param : call.params.getParamCaptures())
{
ResourceIDType idType = GetResourceIDTypeFromParamType(param.type);
updateResourceCountsFromParamCapture(param, idType);
}
// Update resource IDs in the return value. Return values types are not stored as resource IDs,
// but instead are stored as GLuints. Therefore we need to explicitly label the resource ID type
// when we call update. Currently only shader and program creation are explicitly tracked.
switch (call.entryPoint)
{
case EntryPoint::GLCreateShader:
case EntryPoint::GLCreateProgram:
updateResourceCountsFromParamCapture(call.params.getReturnValue(),
ResourceIDType::ShaderProgram);
break;
case EntryPoint::GLFenceSync:
updateResourceCountsFromParamCapture(call.params.getReturnValue(),
ResourceIDType::Sync);
break;
case EntryPoint::EGLCreateSync:
case EntryPoint::EGLCreateSyncKHR:
updateResourceCountsFromParamCapture(call.params.getReturnValue(),
ResourceIDType::egl_Sync);
break;
default:
break;
}
}
void FrameCaptureShared::captureCall(gl::Context *context, CallCapture &&inCall, bool isCallValid)
{
if (SkipCall(inCall.entryPoint))
{
return;
}
if (isCallValid)
{
// If the context ID has changed, then we need to inject an eglMakeCurrent() call. Only do
// this if there is more than 1 context in the share group to avoid unnecessary
// eglMakeCurrent() calls.
size_t contextCount = context->getShareGroup()->getContexts().size();
if (contextCount > 1 && mLastContextId != context->id())
{
// Inject the eglMakeCurrent() call. Ignore the display and surface.
CallCapture makeCurrentCall =
egl::CaptureMakeCurrent(nullptr, true, nullptr, {0}, {0}, context->id(), EGL_TRUE);
mFrameCalls.emplace_back(std::move(makeCurrentCall));
mLastContextId = context->id();
}
// Update resource counts before we override entry points with custom calls.
updateResourceCountsFromCallCapture(inCall);
std::vector<CallCapture> outCalls;
maybeOverrideEntryPoint(context, inCall, outCalls);
// Need to loop on any new calls we added during override
for (CallCapture &call : outCalls)
{
// During capture, consider all frame calls active
if (isCaptureActive())
{
call.isActive = true;
}
maybeCapturePreCallUpdates(context, call, &mShareGroupSetupCalls,
&mResourceIDToSetupCalls);
mFrameCalls.emplace_back(std::move(call));
maybeCapturePostCallUpdates(context);
}
// Evaluate the validation expression to determine if we insert a validation checkpoint.
// This lets the user pick a subset of calls to check instead of checking every call.
if (mValidateSerializedState && !mValidationExpression.empty())
{
// Example substitution for frame #2, call #110:
// Before: (call == 2) && (frame >= 100) && (frame <= 120) && ((frame % 10) == 0)
// After: (2 == 2) && (110 >= 100) && (110 <= 120) && ((110 % 10) == 0)
// Evaluates to 1.0.
std::string expression = mValidationExpression;
angle::ReplaceAllSubstrings(&expression, "frame", std::to_string(mFrameIndex));
angle::ReplaceAllSubstrings(&expression, "call", std::to_string(mFrameCalls.size()));
double result = ceval_result(expression);
if (result > 0)
{
CaptureValidateSerializedState(context, &mFrameCalls);
}
}
}
else
{
const int maxInvalidCallLogs = 3;
size_t &callCount = isCaptureActive() ? mInvalidCallCountsActive[inCall.entryPoint]
: mInvalidCallCountsInactive[inCall.entryPoint];
callCount++;
if (callCount <= maxInvalidCallLogs)
{
std::ostringstream msg;
msg << "FrameCapture (capture " << (isCaptureActive() ? "active" : "inactive")
<< "): Not capturing invalid call to " << GetEntryPointName(inCall.entryPoint);
if (callCount == maxInvalidCallLogs)
{
msg << " (will no longer repeat for this entry point)";
}
INFO() << msg.str();
}
}
}
void FrameCaptureShared::maybeCapturePostCallUpdates(const gl::Context *context)
{
// Process resource ID updates.
if (isCaptureActive())
{
MaybeCaptureUpdateResourceIDs(context, &mResourceTracker, &mFrameCalls);
}
CallCapture &lastCall = mFrameCalls.back();
switch (lastCall.entryPoint)
{
case EntryPoint::GLCreateShaderProgramv:
{
gl::ShaderProgramID programId;
programId.value = lastCall.params.getReturnValue().value.GLuintVal;
const gl::Program *program = context->getProgramResolveLink(programId);
CaptureUpdateUniformLocations(program, &mFrameCalls);
CaptureUpdateUniformBlockIndexes(program, &mFrameCalls);
break;
}
case EntryPoint::GLLinkProgram:
{
const ParamCapture &param =
lastCall.params.getParam("programPacked", ParamType::TShaderProgramID, 0);
const gl::Program *program =
context->getProgramResolveLink(param.value.ShaderProgramIDVal);
CaptureUpdateUniformLocations(program, &mFrameCalls);
CaptureUpdateUniformBlockIndexes(program, &mFrameCalls);
break;
}
case EntryPoint::GLUseProgram:
CaptureUpdateCurrentProgram(lastCall, 0, &mFrameCalls);
break;
case EntryPoint::GLActiveShaderProgram:
CaptureUpdateCurrentProgram(lastCall, 1, &mFrameCalls);
break;
case EntryPoint::GLDeleteProgram:
{
const ParamCapture &param =
lastCall.params.getParam("programPacked", ParamType::TShaderProgramID, 0);
CaptureDeleteUniformLocations(param.value.ShaderProgramIDVal, &mFrameCalls);
break;
}
case EntryPoint::GLShaderSource:
{
lastCall.params.setValueParamAtIndex("count", ParamType::TGLsizei, 1, 1);
ParamCapture &paramLength =
lastCall.params.getParam("length", ParamType::TGLintConstPointer, 3);
paramLength.data.resize(1);
// Set the length parameter to {-1} to signal that the actual string length
// is to be used. Since we store the parameter blob as an array of four uint8_t
// values, we have to pass the binary equivalent of -1.
paramLength.data[0] = {0xff, 0xff, 0xff, 0xff};
break;
}
case EntryPoint::GLBufferData:
case EntryPoint::GLBufferSubData:
{
// When using shadow memory we need to update it from real memory here
if (mCoherentBufferTracker.isShadowMemoryEnabled())
{
gl::BufferBinding target =
lastCall.params.getParam("targetPacked", ParamType::TBufferBinding, 0)
.value.BufferBindingVal;
gl::Buffer *buffer = context->getState().getTargetBuffer(target);
if (mCoherentBufferTracker.haveBuffer(buffer->id()))
{
std::shared_ptr<CoherentBuffer> cb =
mCoherentBufferTracker.mBuffers[buffer->id().value];
cb->removeProtection(PageSharingType::NoneShared);
cb->updateShadowMemory();
cb->protectAll();
}
}
break;
}
case EntryPoint::GLCopyBufferSubData:
{
// When using shadow memory, we need to mark the buffer shadowDirty bit to true
// so it will be synchronized with real memory on the next glFinish call.
if (mCoherentBufferTracker.isShadowMemoryEnabled())
{
gl::BufferBinding target =
lastCall.params.getParam("writeTargetPacked", ParamType::TBufferBinding, 1)
.value.BufferBindingVal;
gl::Buffer *buffer = context->getState().getTargetBuffer(target);
if (mCoherentBufferTracker.haveBuffer(buffer->id()))
{
std::shared_ptr<CoherentBuffer> cb =
mCoherentBufferTracker.mBuffers[buffer->id().value];
// This needs to be synced on glFinish
cb->markShadowDirty();
}
}
break;
}
case EntryPoint::GLDispatchCompute:
{
// When using shadow memory, we need to mark all buffer's shadowDirty bit to true
// so they will be synchronized with real memory on the next glFinish call.
if (mCoherentBufferTracker.isShadowMemoryEnabled())
{
mCoherentBufferTracker.markAllShadowDirty();
}
break;
}
default:
break;
}
}
void FrameCaptureShared::captureClientArraySnapshot(const gl::Context *context,
size_t vertexCount,
size_t instanceCount)
{
const gl::VertexArray *vao = context->getState().getVertexArray();
// Capture client array data.
for (size_t attribIndex : context->getStateCache().getActiveClientAttribsMask())
{
const gl::VertexAttribute &attrib = vao->getVertexAttribute(attribIndex);
const gl::VertexBinding &binding = vao->getVertexBinding(attrib.bindingIndex);
int callIndex = mClientVertexArrayMap[attribIndex];
if (callIndex != -1)
{
size_t count = vertexCount;
if (binding.getDivisor() > 0)
{
count = rx::UnsignedCeilDivide(static_cast<uint32_t>(instanceCount),
binding.getDivisor());
}
// The last capture element doesn't take up the full stride.
size_t bytesToCapture = (count - 1) * binding.getStride() + attrib.format->pixelBytes;
CallCapture &call = mFrameCalls[callIndex];
ParamCapture &param = call.params.getClientArrayPointerParameter();
ASSERT(param.type == ParamType::TvoidConstPointer);
ParamBuffer updateParamBuffer;
updateParamBuffer.addValueParam<GLint>("arrayIndex", ParamType::TGLint,
static_cast<uint32_t>(attribIndex));
ParamCapture updateMemory("pointer", ParamType::TvoidConstPointer);
CaptureMemory(param.value.voidConstPointerVal, bytesToCapture, &updateMemory);
updateParamBuffer.addParam(std::move(updateMemory));
updateParamBuffer.addValueParam<GLuint64>("size", ParamType::TGLuint64, bytesToCapture);
mFrameCalls.emplace_back("UpdateClientArrayPointer", std::move(updateParamBuffer));
mClientArraySizes[attribIndex] =
std::max(mClientArraySizes[attribIndex], bytesToCapture);
}
}
}
void FrameCaptureShared::captureCoherentBufferSnapshot(const gl::Context *context, gl::BufferID id)
{
if (!hasBufferData(id))
{
// This buffer was not marked writable
return;
}
const gl::State &apiState = context->getState();
const gl::BufferManager &buffers = apiState.getBufferManagerForCapture();
gl::Buffer *buffer = buffers.getBuffer(id);
if (!buffer)
{
// Could not find buffer binding
return;
}
ASSERT(buffer->isMapped());
std::shared_ptr<angle::CoherentBuffer> coherentBuffer =
mCoherentBufferTracker.mBuffers[id.value];
std::vector<PageRange> dirtyPageRanges = coherentBuffer->getDirtyPageRanges();
if (mCoherentBufferTracker.isShadowMemoryEnabled() && !dirtyPageRanges.empty())
{
coherentBuffer->updateBufferMemory();
}
AddressRange wholeRange = coherentBuffer->getRange();
for (PageRange &pageRange : dirtyPageRanges)
{
// Write protect the memory already, so the app is blocked on writing during our capture
coherentBuffer->protectPageRange(pageRange);
// Create the parameters to our helper for use during replay
ParamBuffer dataParamBuffer;
// Pass in the target buffer ID
dataParamBuffer.addValueParam("dest", ParamType::TGLuint, buffer->id().value);
// Capture the current buffer data with a binary param
ParamCapture captureData("source", ParamType::TvoidConstPointer);
AddressRange dirtyRange = coherentBuffer->getDirtyAddressRange(pageRange);
CaptureMemory(reinterpret_cast<void *>(dirtyRange.start), dirtyRange.size, &captureData);
dataParamBuffer.addParam(std::move(captureData));
// Also track its size for use with memcpy
dataParamBuffer.addValueParam<GLsizeiptr>("size", ParamType::TGLsizeiptr,
static_cast<GLsizeiptr>(dirtyRange.size));
if (wholeRange.start != dirtyRange.start)
{
// Capture with offset
GLsizeiptr offset = dirtyRange.start - wholeRange.start;
ASSERT(offset > 0);
// The dirty page range is not at the start of the buffer, track the offset.
dataParamBuffer.addValueParam<GLsizeiptr>("offset", ParamType::TGLsizeiptr, offset);
// Call the helper that populates the buffer with captured data
mFrameCalls.emplace_back("UpdateClientBufferDataWithOffset",
std::move(dataParamBuffer));
}
else
{
// Call the helper that populates the buffer with captured data
mFrameCalls.emplace_back("UpdateClientBufferData", std::move(dataParamBuffer));
}
}
}
void FrameCaptureShared::captureMappedBufferSnapshot(const gl::Context *context,
const CallCapture &call)
{
// If the buffer was mapped writable, we need to restore its data, since we have no
// visibility into what the client did to the buffer while mapped.
// This sequence will result in replay calls like this:
// ...
// gMappedBufferData[gBufferMap[42]] = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 65536,
// GL_MAP_WRITE_BIT);
// ...
// UpdateClientBufferData(42, &gBinaryData[164631024], 65536);
// glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
// ...
// Re-map the buffer, using the info we tracked about the buffer
gl::BufferBinding target =
call.params.getParam("targetPacked", ParamType::TBufferBinding, 0).value.BufferBindingVal;
FrameCaptureShared *frameCaptureShared = context->getShareGroup()->getFrameCaptureShared();
gl::Buffer *buffer = context->getState().getTargetBuffer(target);
if (!frameCaptureShared->hasBufferData(buffer->id()))
{
// This buffer was not marked writable, so we did not back it up
return;
}
std::pair<GLintptr, GLsizeiptr> bufferDataOffsetAndLength =
frameCaptureShared->getBufferDataOffsetAndLength(buffer->id());
GLintptr offset = bufferDataOffsetAndLength.first;
GLsizeiptr length = bufferDataOffsetAndLength.second;
// Map the buffer so we can copy its contents out
ASSERT(!buffer->isMapped());
angle::Result result = buffer->mapRange(context, offset, length, GL_MAP_READ_BIT);
if (result != angle::Result::Continue)
{
ERR() << "Failed to mapRange of buffer" << std::endl;
}
const uint8_t *data = reinterpret_cast<const uint8_t *>(buffer->getMapPointer());
// Create the parameters to our helper for use during replay
ParamBuffer dataParamBuffer;
// Pass in the target buffer ID
dataParamBuffer.addValueParam("dest", ParamType::TGLuint, buffer->id().value);
// Capture the current buffer data with a binary param
ParamCapture captureData("source", ParamType::TvoidConstPointer);
CaptureMemory(data, length, &captureData);
dataParamBuffer.addParam(std::move(captureData));
// Also track its size for use with memcpy
dataParamBuffer.addValueParam<GLsizeiptr>("size", ParamType::TGLsizeiptr, length);
// Call the helper that populates the buffer with captured data
mFrameCalls.emplace_back("UpdateClientBufferData", std::move(dataParamBuffer));
// Unmap the buffer and move on
GLboolean dontCare;
(void)buffer->unmap(context, &dontCare);
}
void FrameCaptureShared::checkForCaptureTrigger()
{
// If the capture trigger has not been set, move on
if (mCaptureTrigger == 0)
{
return;
}
// Otherwise, poll the value for a change
std::string captureTriggerStr = GetCaptureTrigger();
if (captureTriggerStr.empty())
{
return;
}
// If the value has changed, use the original value as the frame count
// TODO (anglebug.com/4949): Improve capture at unknown frame time. It is good to
// avoid polling if the feature is not enabled, but not entirely intuitive to set
// a value to zero when you want to trigger it.
uint32_t captureTrigger = atoi(captureTriggerStr.c_str());
if (captureTrigger != mCaptureTrigger)
{
// Start mid-execution capture for the current frame
mCaptureStartFrame = mFrameIndex + 1;
// Use the original trigger value as the frame count
mCaptureEndFrame = mCaptureStartFrame + mCaptureTrigger - 1;
INFO() << "Capture triggered after frame " << mFrameIndex << " for " << mCaptureTrigger
<< " frames";
// Stop polling
mCaptureTrigger = 0;
}
}
void FrameCaptureShared::scanSetupCalls(std::vector<CallCapture> &setupCalls)
{
// Scan all the instructions in the list for tracking
for (CallCapture &call : setupCalls)
{
updateReadBufferSize(call.params.getReadBufferSize());
updateResourceCountsFromCallCapture(call);
}
}
void FrameCaptureShared::runMidExecutionCapture(gl::Context *mainContext)
{
// Set the capture active to ensure all GLES commands issued by the next frame are
// handled correctly by maybeCapturePreCallUpdates() and maybeCapturePostCallUpdates().
setCaptureActive();
// Make sure all pending work for every Context in the share group has completed so all data
// (buffers, textures, etc.) has been updated and no resources are in use.
egl::ShareGroup *shareGroup = mainContext->getShareGroup();
shareGroup->finishAllContexts();
const gl::State &contextState = mainContext->getState();
gl::State mainContextReplayState(
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, contextState.getClientType(),
contextState.getClientVersion(), contextState.getProfileMask(), false, true, true, true,
false, EGL_CONTEXT_PRIORITY_MEDIUM_IMG, contextState.hasRobustAccess(),
contextState.hasProtectedContent());
mainContextReplayState.initializeForCapture(mainContext);
CaptureShareGroupMidExecutionSetup(mainContext, &mShareGroupSetupCalls, &mResourceTracker,
mainContextReplayState, mMaxAccessedResourceIDs);
scanSetupCalls(mShareGroupSetupCalls);
egl::Display *display = mainContext->getDisplay();
egl::Surface *draw = mainContext->getCurrentDrawSurface();
egl::Surface *read = mainContext->getCurrentReadSurface();
for (auto shareContext : shareGroup->getContexts())
{
FrameCapture *frameCapture = shareContext.second->getFrameCapture();
ASSERT(frameCapture->getSetupCalls().empty());
if (shareContext.second->id() == mainContext->id())
{
CaptureMidExecutionSetup(shareContext.second, &frameCapture->getSetupCalls(),
frameCapture->getStateResetHelper().getResetCalls(),
&mShareGroupSetupCalls, &mResourceIDToSetupCalls,
&mResourceTracker, mainContextReplayState,
mValidateSerializedState);
scanSetupCalls(frameCapture->getSetupCalls());
std::stringstream protoStream;
std::stringstream headerStream;
std::stringstream bodyStream;
protoStream << "void "
<< FmtSetupFunction(kNoPartId, mainContext->id(), FuncUsage::Prototype);
std::string proto = protoStream.str();
WriteCppReplayFunctionWithParts(mainContext->id(), ReplayFunc::Setup, mReplayWriter, 1,
&mBinaryData, frameCapture->getSetupCalls(),
headerStream, bodyStream, &mResourceIDBufferSize);
mReplayWriter.addPrivateFunction(proto, headerStream, bodyStream);
}
else
{
const gl::State &shareContextState = shareContext.second->getState();
gl::State auxContextReplayState(
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
shareContextState.getClientType(), shareContextState.getClientVersion(),
shareContextState.getProfileMask(), false, true, true, true, false,
EGL_CONTEXT_PRIORITY_MEDIUM_IMG, shareContextState.hasRobustAccess(),
shareContextState.hasProtectedContent());
auxContextReplayState.initializeForCapture(shareContext.second);
egl::Error error = shareContext.second->makeCurrent(display, draw, read);
if (error.isError())
{
INFO() << "MEC unable to make secondary context current";
}
CaptureMidExecutionSetup(shareContext.second, &frameCapture->getSetupCalls(),
frameCapture->getStateResetHelper().getResetCalls(),
&mShareGroupSetupCalls, &mResourceIDToSetupCalls,
&mResourceTracker, auxContextReplayState,
mValidateSerializedState);
scanSetupCalls(frameCapture->getSetupCalls());
WriteAuxiliaryContextCppSetupReplay(
mReplayWriter, mCompression, mOutDirectory, shareContext.second, mCaptureLabel, 1,
frameCapture->getSetupCalls(), &mBinaryData, mSerializeStateEnabled, *this,
&mResourceIDBufferSize);
}
// Track that this context was created before MEC started
mActiveContexts.insert(shareContext.first);
}
egl::Error error = mainContext->makeCurrent(display, draw, read);
if (error.isError())
{
INFO() << "MEC unable to make main context current again";
}
}
void FrameCaptureShared::onEndFrame(gl::Context *context)
{
if (!enabled() || mFrameIndex > mCaptureEndFrame)
{
setCaptureInactive();
mCoherentBufferTracker.onEndFrame();
return;
}
FrameCapture *frameCapture = context->getFrameCapture();
// Count resource IDs. This is also done on every frame. It could probably be done by
// checking the GL state instead of the calls.
for (const CallCapture &call : mFrameCalls)
{
for (const ParamCapture &param : call.params.getParamCaptures())
{
ResourceIDType idType = GetResourceIDTypeFromParamType(param.type);
if (idType != ResourceIDType::InvalidEnum)
{
mHasResourceType.set(idType);
}
}
}
mWindowSurfaceContextID = context->id();
// On Android, we can trigger a capture during the run
checkForCaptureTrigger();
// Check for MEC. Done after checkForCaptureTrigger(), since that can modify mCaptureStartFrame.
if (mFrameIndex < mCaptureStartFrame)
{
if (mFrameIndex == mCaptureStartFrame - 1)
{
// Trigger MEC.
runMidExecutionCapture(context);
}
mFrameIndex++;
reset();
return;
}
ASSERT(isCaptureActive());
if (!mFrameCalls.empty())
{
mActiveFrameIndices.push_back(getReplayFrameIndex());
}
// Make sure all pending work for every Context in the share group has completed so all data
// (buffers, textures, etc.) has been updated and no resources are in use.
egl::ShareGroup *shareGroup = context->getShareGroup();
shareGroup->finishAllContexts();
// Only validate the first frame for now to save on retracing time.
if (mValidateSerializedState && mFrameIndex == mCaptureStartFrame)
{
CaptureValidateSerializedState(context, &mFrameCalls);
}
writeMainContextCppReplay(context, frameCapture->getSetupCalls(),
frameCapture->getStateResetHelper());
if (mFrameIndex == mCaptureEndFrame)
{
// Write shared MEC after frame sequence so we can eliminate unused assets like programs
WriteShareGroupCppSetupReplay(mReplayWriter, mCompression, mOutDirectory, mCaptureLabel, 1,
1, mShareGroupSetupCalls, &mResourceTracker, &mBinaryData,
mSerializeStateEnabled, mWindowSurfaceContextID,
&mResourceIDBufferSize);
// Save the index files after the last frame.
writeCppReplayIndexFiles(context, false);
SaveBinaryData(mCompression, mOutDirectory, kSharedContextId, mCaptureLabel, mBinaryData);
mBinaryData.clear();
mWroteIndexFile = true;
}
reset();
mFrameIndex++;
}
void FrameCaptureShared::onDestroyContext(const gl::Context *context)
{
if (!mEnabled)
{
return;
}
if (!mWroteIndexFile && mFrameIndex > mCaptureStartFrame)
{
// If context is destroyed before end frame is reached and at least
// 1 frame has been recorded, then write the index files.
// It doesn't make sense to write the index files when no frame has been recorded
mFrameIndex -= 1;
mCaptureEndFrame = mFrameIndex;
writeCppReplayIndexFiles(context, true);
SaveBinaryData(mCompression, mOutDirectory, kSharedContextId, mCaptureLabel, mBinaryData);
mBinaryData.clear();
mWroteIndexFile = true;
}
}
void FrameCaptureShared::onMakeCurrent(const gl::Context *context, const egl::Surface *drawSurface)
{
if (!drawSurface)
{
return;
}
// Track the width, height and color space of the draw surface as provided to makeCurrent
SurfaceParams &params = mDrawSurfaceParams[context->id()];
params.extents = gl::Extents(drawSurface->getWidth(), drawSurface->getHeight(), 1);
params.colorSpace = egl::FromEGLenum<egl::ColorSpace>(drawSurface->getGLColorspace());
}
DataCounters::DataCounters() = default;
DataCounters::~DataCounters() = default;
int DataCounters::getAndIncrement(EntryPoint entryPoint, const std::string &paramName)
{
Counter counterKey = {entryPoint, paramName};
return mData[counterKey]++;
}
DataTracker::DataTracker() = default;
DataTracker::~DataTracker() = default;
StringCounters::StringCounters() = default;
StringCounters::~StringCounters() = default;
int StringCounters::getStringCounter(const std::vector<std::string> &strings)
{
const auto &id = mStringCounterMap.find(strings);
if (id == mStringCounterMap.end())
{
return kStringsNotFound;
}
else
{
return mStringCounterMap[strings];
}
}
void StringCounters::setStringCounter(const std::vector<std::string> &strings, int &counter)
{
ASSERT(counter >= 0);
mStringCounterMap[strings] = counter;
}
TrackedResource::TrackedResource() = default;
TrackedResource::~TrackedResource() = default;
ResourceTracker::ResourceTracker() = default;
ResourceTracker::~ResourceTracker() = default;
StateResetHelper::StateResetHelper() = default;
StateResetHelper::~StateResetHelper() = default;
void StateResetHelper::setDefaultResetCalls(const gl::Context *context,
angle::EntryPoint entryPoint)
{
static const gl::BlendState kDefaultBlendState;
// Populate default reset calls for entrypoints to support looping to beginning
switch (entryPoint)
{
case angle::EntryPoint::GLUseProgram:
{
if (context->getActiveLinkedProgram() &&
context->getActiveLinkedProgram()->id().value != 0)
{
Capture(&mResetCalls[angle::EntryPoint::GLUseProgram],
gl::CaptureUseProgram(context->getState(), true, {0}));
}
break;
}
case angle::EntryPoint::GLBindVertexArray:
{
if (context->getState().getVertexArray()->id().value != 0)
{
VertexArrayCaptureFuncs vertexArrayFuncs(context->isGLES1());
Capture(&mResetCalls[angle::EntryPoint::GLBindVertexArray],
vertexArrayFuncs.bindVertexArray(context->getState(), true, {0}));
}
break;
}
case angle::EntryPoint::GLBlendFunc:
{
Capture(&mResetCalls[angle::EntryPoint::GLBlendFunc],
CaptureBlendFunc(context->getState(), true, kDefaultBlendState.sourceBlendRGB,
kDefaultBlendState.destBlendRGB));
break;
}
case angle::EntryPoint::GLBlendFuncSeparate:
{
Capture(&mResetCalls[angle::EntryPoint::GLBlendFuncSeparate],
CaptureBlendFuncSeparate(
context->getState(), true, kDefaultBlendState.sourceBlendRGB,
kDefaultBlendState.destBlendRGB, kDefaultBlendState.sourceBlendAlpha,
kDefaultBlendState.destBlendAlpha));
break;
}
case angle::EntryPoint::GLBlendEquation:
{
UNREACHABLE(); // GLBlendEquationSeparate is always used instead
break;
}
case angle::EntryPoint::GLBlendEquationSeparate:
{
Capture(&mResetCalls[angle::EntryPoint::GLBlendEquationSeparate],
CaptureBlendEquationSeparate(context->getState(), true,
kDefaultBlendState.blendEquationRGB,
kDefaultBlendState.blendEquationAlpha));
break;
}
case angle::EntryPoint::GLColorMask:
{
Capture(&mResetCalls[angle::EntryPoint::GLColorMask],
CaptureColorMask(context->getState(), true,
gl::ConvertToGLBoolean(kDefaultBlendState.colorMaskRed),
gl::ConvertToGLBoolean(kDefaultBlendState.colorMaskGreen),
gl::ConvertToGLBoolean(kDefaultBlendState.colorMaskBlue),
gl::ConvertToGLBoolean(kDefaultBlendState.colorMaskAlpha)));
break;
}
case angle::EntryPoint::GLBlendColor:
{
Capture(&mResetCalls[angle::EntryPoint::GLBlendColor],
CaptureBlendColor(context->getState(), true, 0, 0, 0, 0));
break;
}
default:
ERR() << "Unhandled entry point in setDefaultResetCalls: "
<< GetEntryPointName(entryPoint);
UNREACHABLE();
break;
}
}
void ResourceTracker::setDeletedFenceSync(gl::SyncID sync)
{
ASSERT(sync.value != 0);
if (mStartingFenceSyncs.find(sync) == mStartingFenceSyncs.end())
{
// This is a fence sync created after MEC was initialized. Ignore it.
return;
}
// In this case, the app is deleting a fence sync we started with, we need to regen on loop.
mFenceSyncsToRegen.insert(sync);
}
void ResourceTracker::setModifiedDefaultUniform(gl::ShaderProgramID programID,
gl::UniformLocation location)
{
// Pull up or create the list of uniform locations for this program and mark one dirty
mDefaultUniformsToReset[programID].insert(location);
}
void ResourceTracker::setDefaultUniformBaseLocation(gl::ShaderProgramID programID,
gl::UniformLocation location,
gl::UniformLocation baseLocation)
{
// Track the base location used to populate arrayed uniforms in Setup
mDefaultUniformBaseLocations[{programID, location}] = baseLocation;
}
TrackedResource &ResourceTracker::getTrackedResource(gl::ContextID contextID, ResourceIDType type)
{
if (IsSharedObjectResource(type))
{
// No need to index with context if shared
return mTrackedResourcesShared[static_cast<uint32_t>(type)];
}
else
{
// For per-context objects, track the resource per-context
return mTrackedResourcesPerContext[contextID][static_cast<uint32_t>(type)];
}
}
void ResourceTracker::getContextIDs(std::set<gl::ContextID> &idsOut)
{
for (const auto &trackedResourceIterator : mTrackedResourcesPerContext)
{
gl::ContextID contextID = trackedResourceIterator.first;
idsOut.insert(contextID);
}
}
void TrackedResource::setGennedResource(GLuint id)
{
if (mStartingResources.find(id) == mStartingResources.end())
{
// This is a resource created after MEC was initialized, track it
mNewResources.insert(id);
}
else
{
// In this case, the app is genning a resource with starting ID after previously deleting it
ASSERT(mResourcesToRegen.find(id) != mResourcesToRegen.end());
// For this, we need to delete it again to recreate it.
mResourcesToDelete.insert(id);
}
}
bool TrackedResource::resourceIsGenerated(GLuint id)
{
return mStartingResources.find(id) != mStartingResources.end() ||
mNewResources.find(id) != mNewResources.end();
}
void TrackedResource::setDeletedResource(GLuint id)
{
if (id == 0)
{
// Ignore ID 0
return;
}
if (mNewResources.find(id) != mNewResources.end())
{
// This is a resource created after MEC was initialized, just clear it, since there will be
// no actions required for it to return to starting state.
mNewResources.erase(id);
return;
}
if (mStartingResources.find(id) != mStartingResources.end())
{
// In this case, the app is deleting a resource we started with, we need to regen on loop
// Mark that we don't need to delete this
mResourcesToDelete.erase(id);
// Generate the resource again
mResourcesToRegen.insert(id);
// Also restore its contents
mResourcesToRestore.insert(id);
}
// If none of the above is true, the app is deleting a resource that was never genned.
}
void TrackedResource::setModifiedResource(GLuint id)
{
// If this was a starting resource, we need to track it for restore
if (mStartingResources.find(id) != mStartingResources.end())
{
mResourcesToRestore.insert(id);
}
}
void ResourceTracker::setBufferMapped(gl::ContextID contextID, GLuint id)
{
// If this was a starting buffer, we may need to restore it to original state during Reset.
// Skip buffers that were deleted after the starting point.
const TrackedResource &trackedBuffers = getTrackedResource(contextID, ResourceIDType::Buffer);
const ResourceSet &startingBuffers = trackedBuffers.getStartingResources();
const ResourceSet &buffersToRegen = trackedBuffers.getResourcesToRegen();
if (startingBuffers.find(id) != startingBuffers.end() &&
buffersToRegen.find(id) == buffersToRegen.end())
{
// Track that its current state is mapped (true)
mStartingBuffersMappedCurrent[id] = true;
}
}
void ResourceTracker::setBufferUnmapped(gl::ContextID contextID, GLuint id)
{
// If this was a starting buffer, we may need to restore it to original state during Reset.
// Skip buffers that were deleted after the starting point.
const TrackedResource &trackedBuffers = getTrackedResource(contextID, ResourceIDType::Buffer);
const ResourceSet &startingBuffers = trackedBuffers.getStartingResources();
const ResourceSet &buffersToRegen = trackedBuffers.getResourcesToRegen();
if (startingBuffers.find(id) != startingBuffers.end() &&
buffersToRegen.find(id) == buffersToRegen.end())
{
// Track that its current state is unmapped (false)
mStartingBuffersMappedCurrent[id] = false;
}
}
bool ResourceTracker::getStartingBuffersMappedCurrent(GLuint id) const
{
const auto &foundBool = mStartingBuffersMappedCurrent.find(id);
ASSERT(foundBool != mStartingBuffersMappedCurrent.end());
return foundBool->second;
}
bool ResourceTracker::getStartingBuffersMappedInitial(GLuint id) const
{
const auto &foundBool = mStartingBuffersMappedInitial.find(id);
ASSERT(foundBool != mStartingBuffersMappedInitial.end());
return foundBool->second;
}
void ResourceTracker::onShaderProgramAccess(gl::ShaderProgramID shaderProgramID)
{
mMaxShaderPrograms = std::max(mMaxShaderPrograms, shaderProgramID.value + 1);
}
bool FrameCaptureShared::isCapturing() const
{
// Currently we will always do a capture up until the last frame. In the future we could improve
// mid execution capture by only capturing between the start and end frames. The only necessary
// reason we need to capture before the start is for attached program and shader sources.
return mEnabled && mFrameIndex <= mCaptureEndFrame;
}
uint32_t FrameCaptureShared::getFrameCount() const
{
return mCaptureEndFrame - mCaptureStartFrame + 1;
}
uint32_t FrameCaptureShared::getReplayFrameIndex() const
{
return mFrameIndex - mCaptureStartFrame + 1;
}
// Serialize trace metadata into a JSON file. The JSON file will be named "trace_prefix.json".
//
// As of writing, it will have the format like so:
// {
// "TraceMetadata":
// {
// "AreClientArraysEnabled" : 1, "CaptureRevision" : 16631, "ConfigAlphaBits" : 8,
// "ConfigBlueBits" : 8, "ConfigDepthBits" : 24, "ConfigGreenBits" : 8,
// ... etc ...
void FrameCaptureShared::writeJSON(const gl::Context *context)
{
const gl::ContextID contextId = context->id();
const SurfaceParams &surfaceParams = mDrawSurfaceParams.at(contextId);
const gl::State &glState = context->getState();
const egl::Config *config = context->getConfig();
const egl::AttributeMap &displayAttribs = context->getDisplay()->getAttributeMap();
unsigned int frameCount = getFrameCount();
JsonSerializer json;
json.startGroup("TraceMetadata");
json.addScalar("CaptureRevision", GetANGLERevision());
json.addScalar("ContextClientMajorVersion", context->getClientMajorVersion());
json.addScalar("ContextClientMinorVersion", context->getClientMinorVersion());
json.addHexValue("DisplayPlatformType", displayAttribs.getAsInt(EGL_PLATFORM_ANGLE_TYPE_ANGLE));
json.addHexValue("DisplayDeviceType",
displayAttribs.getAsInt(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE));
json.addScalar("FrameStart", 1);
json.addScalar("FrameEnd", frameCount);
json.addScalar("DrawSurfaceWidth", surfaceParams.extents.width);
json.addScalar("DrawSurfaceHeight", surfaceParams.extents.height);
json.addHexValue("DrawSurfaceColorSpace", ToEGLenum(surfaceParams.colorSpace));
if (config)
{
json.addScalar("ConfigRedBits", config->redSize);
json.addScalar("ConfigGreenBits", config->greenSize);
json.addScalar("ConfigBlueBits", config->blueSize);
json.addScalar("ConfigAlphaBits", config->alphaSize);
json.addScalar("ConfigDepthBits", config->depthSize);
json.addScalar("ConfigStencilBits", config->stencilSize);
}
else
{
json.addScalar("ConfigRedBits", EGL_DONT_CARE);
json.addScalar("ConfigGreenBits", EGL_DONT_CARE);
json.addScalar("ConfigBlueBits", EGL_DONT_CARE);
json.addScalar("ConfigAlphaBits", EGL_DONT_CARE);
json.addScalar("ConfigDepthBits", EGL_DONT_CARE);
json.addScalar("ConfigStencilBits", EGL_DONT_CARE);
}
json.addBool("IsBinaryDataCompressed", mCompression);
json.addBool("AreClientArraysEnabled", glState.areClientArraysEnabled());
json.addBool("IsBindGeneratesResourcesEnabled", glState.isBindGeneratesResourceEnabled());
json.addBool("IsWebGLCompatibilityEnabled", glState.isWebGL());
json.addBool("IsRobustResourceInitEnabled", glState.isRobustResourceInitEnabled());
json.addBool("IsTrimmingEnabled", mTrimEnabled);
json.endGroup();
{
const std::vector<std::string> &traceFiles = mReplayWriter.getAndResetWrittenFiles();
json.addVectorOfStrings("TraceFiles", traceFiles);
}
json.addScalar("WindowSurfaceContextID", contextId.value);
{
std::stringstream jsonFileNameStream;
jsonFileNameStream << mOutDirectory << FmtCapturePrefix(kNoContextId, mCaptureLabel)
<< ".json";
std::string jsonFileName = jsonFileNameStream.str();
SaveFileHelper saveData(jsonFileName);
saveData.write(reinterpret_cast<const uint8_t *>(json.data()), json.length());
}
}
void FrameCaptureShared::writeCppReplayIndexFiles(const gl::Context *context,
bool writeResetContextCall)
{
// Ensure the last frame is written. This will no-op if the frame is already written.
mReplayWriter.saveFrame();
const gl::ContextID contextId = context->id();
{
std::stringstream header;
header << "#pragma once\n";
header << "\n";
header << "#include <EGL/egl.h>\n";
header << "#include <stdint.h>\n";
std::string includes = header.str();
mReplayWriter.setHeaderPrologue(includes);
}
{
std::stringstream source;
source << "#include \"" << FmtCapturePrefix(contextId, mCaptureLabel) << ".h\"\n";
source << "#include \"trace_fixture.h\"\n";
source << "#include \"angle_trace_gl.h\"\n";
std::string sourcePrologue = source.str();
mReplayWriter.setSourcePrologue(sourcePrologue);
}
{
std::string proto = "void InitReplay(void)";
std::stringstream source;
source << proto << "\n";
source << "{\n";
WriteInitReplayCall(mCompression, source, context->id(), mCaptureLabel,
MaxClientArraySize(mClientArraySizes), mReadBufferSize,
mResourceIDBufferSize, mMaxAccessedResourceIDs);
source << "}\n";
mReplayWriter.addPrivateFunction(proto, std::stringstream(), source);
}
{
std::string proto = "void ReplayFrame(uint32_t frameIndex)";
std::stringstream source;
source << proto << "\n";
source << "{\n";
source << " switch (frameIndex)\n";
source << " {\n";
for (uint32_t frameIndex : mActiveFrameIndices)
{
source << " case " << frameIndex << ":\n";
source << " " << FmtReplayFunction(contextId, FuncUsage::Call, frameIndex)
<< ";\n";
source << " break;\n";
}
source << " default:\n";
source << " break;\n";
source << " }\n";
source << "}\n";
mReplayWriter.addPublicFunction(proto, std::stringstream(), source);
}
if (writeResetContextCall)
{
std::string proto = "void ResetReplay(void)";
std::stringstream source;
source << proto << "\n";
source << "{\n";
source << " // Reset context is empty because context is destroyed before end "
"frame is reached\n";
source << "}\n";
mReplayWriter.addPublicFunction(proto, std::stringstream(), source);
}
if (mSerializeStateEnabled)
{
std::string proto = "const char *GetSerializedContextState(uint32_t frameIndex)";
std::stringstream source;
source << proto << "\n";
source << "{\n";
source << " switch (frameIndex)\n";
source << " {\n";
for (uint32_t frameIndex = 1; frameIndex <= getFrameCount(); ++frameIndex)
{
source << " case " << frameIndex << ":\n";
source << " return "
<< FmtGetSerializedContextStateFunction(contextId, FuncUsage::Call, frameIndex)
<< ";\n";
}
source << " default:\n";
source << " return NULL;\n";
source << " }\n";
source << "}\n";
mReplayWriter.addPublicFunction(proto, std::stringstream(), source);
}
{
std::stringstream fnameStream;
fnameStream << mOutDirectory << FmtCapturePrefix(contextId, mCaptureLabel);
std::string fnamePattern = fnameStream.str();
mReplayWriter.setFilenamePattern(fnamePattern);
}
mReplayWriter.saveIndexFilesAndHeader();
writeJSON(context);
}
void FrameCaptureShared::writeMainContextCppReplay(const gl::Context *context,
const std::vector<CallCapture> &setupCalls,
StateResetHelper &stateResetHelper)
{
ASSERT(mWindowSurfaceContextID == context->id());
{
std::stringstream header;
header << "#include \"" << FmtCapturePrefix(context->id(), mCaptureLabel) << ".h\"\n";
header << "#include \"angle_trace_gl.h\"\n";
std::string headerString = header.str();
mReplayWriter.setSourcePrologue(headerString);
}
uint32_t frameCount = getFrameCount();
uint32_t frameIndex = getReplayFrameIndex();
if (frameIndex == 1)
{
{
std::string proto = "void SetupReplay(void)";
std::stringstream out;
out << proto << "\n";
out << "{\n";
// Setup all of the shared objects.
out << " InitReplay();\n";
if (usesMidExecutionCapture())
{
out << " " << FmtSetupFunction(kNoPartId, kSharedContextId, FuncUsage::Call)
<< ";\n";
// Make sure that the current context is mapped correctly
out << " SetCurrentContextID(" << context->id() << ");\n";
}
// Setup each of the auxiliary contexts.
egl::ShareGroup *shareGroup = context->getShareGroup();
const egl::ContextMap &shareContextMap = shareGroup->getContexts();
for (auto shareContext : shareContextMap)
{
if (shareContext.first == context->id().value)
{
if (usesMidExecutionCapture())
{
// Setup the presentation (this) context first.
out << " " << FmtSetupFunction(kNoPartId, context->id(), FuncUsage::Call)
<< ";\n";
out << "\n";
}
continue;
}
// The SetupReplayContextXX() calls only exist if this is a mid-execution capture
// and we can only call them if they exist, so only output the calls if this is a
// MEC.
if (usesMidExecutionCapture())
{
// Only call SetupReplayContext for secondary contexts that were current before
// MEC started
if (mActiveContexts.find(shareContext.first) != mActiveContexts.end())
{
// TODO(http://anglebug.com/5878): Support capture/replay of
// eglCreateContext() so this block can be moved into SetupReplayContextXX()
// by injecting them into the beginning of the setup call stream.
out << " CreateContext(" << shareContext.first << ");\n";
out << " "
<< FmtSetupFunction(kNoPartId, shareContext.second->id(),
FuncUsage::Call)
<< ";\n";
}
}
}
// If there are other contexts that were initialized, we need to make the main context
// current again.
if (shareContextMap.size() > 1)
{
out << "\n";
out << " eglMakeCurrent(NULL, NULL, NULL, gContextMap2[" << context->id()
<< "]);\n";
}
out << "}\n";
mReplayWriter.addPublicFunction(proto, std::stringstream(), out);
}
}
// Emit code to reset back to starting state
if (frameIndex == frameCount)
{
std::stringstream resetProtoStream;
std::stringstream resetHeaderStream;
std::stringstream resetBodyStream;
resetProtoStream << "void ResetReplay(void)";
resetBodyStream << resetProtoStream.str() << "\n";
resetBodyStream << "{\n";
// Grab the list of contexts to be reset
std::set<gl::ContextID> contextIDs;
mResourceTracker.getContextIDs(contextIDs);
// TODO(http://anglebug.com/5878): Look at moving this into the shared context file since
// it's resetting shared objects.
// TODO(http://anglebug.com/4599): Support function parts when writing Reset functions
// Track whether anything was written during Reset
bool anyResourceReset = false;
// Track whether we changed contexts during Reset
bool contextChanged = false;
// First emit shared object reset, including opaque and context state
{
std::stringstream protoStream;
std::stringstream headerStream;
std::stringstream bodyStream;
protoStream << "void "
<< FmtResetFunction(kNoPartId, kSharedContextId, FuncUsage::Prototype);
bodyStream << protoStream.str() << "\n";
bodyStream << "{\n";
for (ResourceIDType resourceType : AllEnums<ResourceIDType>())
{
if (!IsSharedObjectResource(resourceType))
{
continue;
}
// Use current context for shared reset
MaybeResetResources(context->id(), resourceType, mReplayWriter, bodyStream,
headerStream, &mResourceTracker, &mBinaryData, anyResourceReset,
&mResourceIDBufferSize);
}
// Reset opaque type objects that don't have IDs, so are not ResourceIDTypes.
MaybeResetOpaqueTypeObjects(mReplayWriter, bodyStream, headerStream, context,
&mResourceTracker, &mBinaryData, &mResourceIDBufferSize);
bodyStream << "}\n";
mReplayWriter.addPrivateFunction(protoStream.str(), headerStream, bodyStream);
}
// Emit the call to shared object reset
resetBodyStream << " " << FmtResetFunction(kNoPartId, kSharedContextId, FuncUsage::Call)
<< ";\n";
// Reset our output tracker (Note: This was unused during shared reset)
anyResourceReset = false;
// Walk through all contexts that need Reset
for (const gl::ContextID &contextID : contextIDs)
{
// Create a function to reset each context's non-shared objects
{
std::stringstream protoStream;
std::stringstream headerStream;
std::stringstream bodyStream;
protoStream << "void "
<< FmtResetFunction(kNoPartId, contextID, FuncUsage::Prototype);
bodyStream << protoStream.str() << "\n";
bodyStream << "{\n";
// Build the Reset calls in a separate stream so we can insert before them
std::stringstream resetStream;
for (ResourceIDType resourceType : AllEnums<ResourceIDType>())
{
if (IsSharedObjectResource(resourceType))
{
continue;
}
MaybeResetResources(contextID, resourceType, mReplayWriter, resetStream,
headerStream, &mResourceTracker, &mBinaryData,
anyResourceReset, &mResourceIDBufferSize);
}
// Only call eglMakeCurrent if anything was actually reset in the function and the
// context differs from current
if (anyResourceReset && contextID != context->id())
{
contextChanged = true;
bodyStream << " eglMakeCurrent(NULL, NULL, NULL, gContextMap2["
<< contextID.value << "]);\n\n";
}
// Then append the Reset calls
bodyStream << resetStream.str();
bodyStream << "}\n";
mReplayWriter.addPrivateFunction(protoStream.str(), headerStream, bodyStream);
}
// Emit a call to reset each context's non-shared objects
resetBodyStream << " " << FmtResetFunction(kNoPartId, contextID, FuncUsage::Call)
<< ";\n";
}
// Bind the main context again if we bound any additional contexts
if (contextChanged)
{
resetBodyStream << " eglMakeCurrent(NULL, NULL, NULL, gContextMap2["
<< context->id().value << "]);\n";
}
// Now that we're back on the main context, reset any additional state
resetBodyStream << "\n // Reset main context state\n";
MaybeResetContextState(mReplayWriter, resetBodyStream, resetHeaderStream, &mResourceTracker,
context, &mBinaryData, stateResetHelper, &mResourceIDBufferSize);
resetBodyStream << "}\n";
mReplayWriter.addPublicFunction(resetProtoStream.str(), resetHeaderStream, resetBodyStream);
}
if (!mFrameCalls.empty())
{
std::stringstream protoStream;
protoStream << "void "
<< FmtReplayFunction(context->id(), FuncUsage::Prototype, frameIndex);
std::string proto = protoStream.str();
std::stringstream headerStream;
std::stringstream bodyStream;
WriteCppReplayFunctionWithParts(context->id(), ReplayFunc::Replay, mReplayWriter,
frameIndex, &mBinaryData, mFrameCalls, headerStream,
bodyStream, &mResourceIDBufferSize);
mReplayWriter.addPrivateFunction(proto, headerStream, bodyStream);
}
if (mSerializeStateEnabled)
{
std::string serializedContextString;
if (SerializeContextToString(const_cast<gl::Context *>(context),
&serializedContextString) == Result::Continue)
{
std::stringstream protoStream;
protoStream << "const char *"
<< FmtGetSerializedContextStateFunction(context->id(), FuncUsage::Prototype,
frameIndex);
std::string proto = protoStream.str();
std::stringstream bodyStream;
bodyStream << proto << "\n";
bodyStream << "{\n";
bodyStream << " return " << FmtMultiLineString(serializedContextString) << ";\n";
bodyStream << "}\n";
mReplayWriter.addPrivateFunction(proto, std::stringstream(), bodyStream);
}
}
{
std::stringstream fnamePatternStream;
fnamePatternStream << mOutDirectory << FmtCapturePrefix(context->id(), mCaptureLabel);
std::string fnamePattern = fnamePatternStream.str();
mReplayWriter.setFilenamePattern(fnamePattern);
}
if (mFrameIndex == mCaptureEndFrame)
{
mReplayWriter.saveFrame();
}
else
{
mReplayWriter.saveFrameIfFull();
}
}
void FrameCaptureShared::reset()
{
mFrameCalls.clear();
mClientVertexArrayMap.fill(-1);
// Do not reset replay-specific settings like the maximum read buffer size, client array sizes,
// or the 'has seen' type map. We could refine this into per-frame and per-capture maximums if
// necessary.
}
const std::string &FrameCaptureShared::getShaderSource(gl::ShaderProgramID id) const
{
const auto &foundSources = mCachedShaderSource.find(id);
ASSERT(foundSources != mCachedShaderSource.end());
return foundSources->second;
}
void FrameCaptureShared::setShaderSource(gl::ShaderProgramID id, std::string source)
{
mCachedShaderSource[id] = source;
}
const ProgramSources &FrameCaptureShared::getProgramSources(gl::ShaderProgramID id) const
{
const auto &foundSources = mCachedProgramSources.find(id);
ASSERT(foundSources != mCachedProgramSources.end());
return foundSources->second;
}
void FrameCaptureShared::setProgramSources(gl::ShaderProgramID id, ProgramSources sources)
{
mCachedProgramSources[id] = sources;
}
void FrameCaptureShared::markResourceSetupCallsInactive(std::vector<CallCapture> *setupCalls,
ResourceIDType type,
GLuint id,
gl::Range<size_t> range)
{
if (!mTrimEnabled)
{
return;
}
ASSERT(mResourceIDToSetupCalls[type].find(id) == mResourceIDToSetupCalls[type].end());
// Mark all of the calls that were used to initialize this resource as INACTIVE
for (size_t index : range)
{
(*setupCalls)[index].isActive = false;
}
mResourceIDToSetupCalls[type][id] = range;
}
void CaptureMemory(const void *source, size_t size, ParamCapture *paramCapture)
{
std::vector<uint8_t> data(size);
memcpy(data.data(), source, size);
paramCapture->data.emplace_back(std::move(data));
}
void CaptureString(const GLchar *str, ParamCapture *paramCapture)
{
// include the '\0' suffix
CaptureMemory(str, strlen(str) + 1, paramCapture);
}
void CaptureStringLimit(const GLchar *str, uint32_t limit, ParamCapture *paramCapture)
{
// Write the incoming string up to limit, including null terminator
size_t length = strlen(str) + 1;
if (length > limit)
{
// If too many characters, resize the string to fit in the limit
std::string newStr = str;
newStr.resize(limit - 1);
CaptureString(newStr.c_str(), paramCapture);
}
else
{
CaptureMemory(str, length, paramCapture);
}
}
void CaptureVertexPointerGLES1(const gl::State &glState,
gl::ClientVertexArrayType type,
const void *pointer,
ParamCapture *paramCapture)
{
paramCapture->value.voidConstPointerVal = pointer;
if (!glState.getTargetBuffer(gl::BufferBinding::Array))
{
paramCapture->arrayClientPointerIndex =
gl::GLES1Renderer::VertexArrayIndex(type, glState.gles1());
}
}
gl::Program *GetProgramForCapture(const gl::State &glState, gl::ShaderProgramID handle)
{
gl::Program *program = glState.getShaderProgramManagerForCapture().getProgram(handle);
return program;
}
void CaptureGetActiveUniformBlockivParameters(const gl::State &glState,
gl::ShaderProgramID handle,
gl::UniformBlockIndex uniformBlockIndex,
GLenum pname,
ParamCapture *paramCapture)
{
int numParams = 1;
// From the OpenGL ES 3.0 spec:
// If pname is UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, then a list of the
// active uniform indices for the uniform block identified by uniformBlockIndex is
// returned. The number of elements that will be written to params is the value of
// UNIFORM_BLOCK_ACTIVE_UNIFORMS for uniformBlockIndex
if (pname == GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES)
{
gl::Program *program = GetProgramForCapture(glState, handle);
if (program)
{
gl::QueryActiveUniformBlockiv(program, uniformBlockIndex,
GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &numParams);
}
}
paramCapture->readBufferSizeBytes = sizeof(GLint) * numParams;
}
void CaptureGetParameter(const gl::State &glState,
GLenum pname,
size_t typeSize,
ParamCapture *paramCapture)
{
// kMaxReportedCapabilities is the biggest array we'll need to hold data from glGet calls.
// This value needs to be updated if any new extensions are introduced that would allow for
// more compressed texture formats. The current value is taken from:
// http://opengles.gpuinfo.org/displaycapability.php?name=GL_NUM_COMPRESSED_TEXTURE_FORMATS&esversion=2
constexpr unsigned int kMaxReportedCapabilities = 69;
paramCapture->readBufferSizeBytes = typeSize * kMaxReportedCapabilities;
}
void CaptureGenHandlesImpl(GLsizei n, GLuint *handles, ParamCapture *paramCapture)
{
paramCapture->readBufferSizeBytes = sizeof(GLuint) * n;
CaptureMemory(handles, paramCapture->readBufferSizeBytes, paramCapture);
}
void CaptureShaderStrings(GLsizei count,
const GLchar *const *strings,
const GLint *length,
ParamCapture *paramCapture)
{
// Concat the array elements of the string into one data vector,
// append the terminating zero and use this as the captured shader
// string. The string count and the length array are adjusted
// accordingly in the capture post-processing
std::vector<uint8_t> data;
size_t offset = 0;
for (GLsizei index = 0; index < count; ++index)
{
size_t len = ((length && length[index] >= 0) ? length[index] : strlen(strings[index]));
// Count trailing zeros
uint32_t i = 1;
while (i < len && strings[index][len - i] == 0)
{
i++;
}
// Don't copy trailing zeros
len -= (i - 1);
data.resize(offset + len);
std::copy(strings[index], strings[index] + len, data.begin() + offset);
offset += len;
}
data.push_back(0);
paramCapture->data.emplace_back(std::move(data));
}
// ReplayWriter implementation.
ReplayWriter::ReplayWriter()
: mSourceFileExtension(kDefaultSourceFileExt),
mSourceFileSizeThreshold(kDefaultSourceFileSizeThreshold),
mFrameIndex(1)
{}
ReplayWriter::~ReplayWriter()
{
ASSERT(mPrivateFunctionPrototypes.empty());
ASSERT(mPublicFunctionPrototypes.empty());
ASSERT(mPrivateFunctions.empty());
ASSERT(mPublicFunctions.empty());
ASSERT(mGlobalVariableDeclarations.empty());
ASSERT(mReplayHeaders.empty());
}
void ReplayWriter::setSourceFileExtension(const char *ext)
{
mSourceFileExtension = ext;
}
void ReplayWriter::setSourceFileSizeThreshold(size_t sourceFileSizeThreshold)
{
mSourceFileSizeThreshold = sourceFileSizeThreshold;
}
void ReplayWriter::setFilenamePattern(const std::string &pattern)
{
if (mFilenamePattern != pattern)
{
mFilenamePattern = pattern;
}
}
void ReplayWriter::setCaptureLabel(const std::string &label)
{
mCaptureLabel = label;
}
void ReplayWriter::setSourcePrologue(const std::string &prologue)
{
mSourcePrologue = prologue;
}
void ReplayWriter::setHeaderPrologue(const std::string &prologue)
{
mHeaderPrologue = prologue;
}
void ReplayWriter::addPublicFunction(const std::string &functionProto,
const std::stringstream &headerStream,
const std::stringstream &bodyStream)
{
mPublicFunctionPrototypes.push_back(functionProto);
std::string header = headerStream.str();
std::string body = bodyStream.str();
if (!header.empty())
{
mReplayHeaders.emplace_back(header);
}
if (!body.empty())
{
mPublicFunctions.emplace_back(body);
}
}
void ReplayWriter::addPrivateFunction(const std::string &functionProto,
const std::stringstream &headerStream,
const std::stringstream &bodyStream)
{
mPrivateFunctionPrototypes.push_back(functionProto);
std::string header = headerStream.str();
std::string body = bodyStream.str();
if (!header.empty())
{
mReplayHeaders.emplace_back(header);
}
if (!body.empty())
{
mPrivateFunctions.emplace_back(body);
}
}
std::string ReplayWriter::getInlineVariableName(EntryPoint entryPoint, const std::string &paramName)
{
int counter = mDataTracker.getCounters().getAndIncrement(entryPoint, paramName);
return GetVarName(entryPoint, paramName, counter);
}
std::string ReplayWriter::getInlineStringSetVariableName(EntryPoint entryPoint,
const std::string &paramName,
const std::vector<std::string> &strings,
bool *isNewEntryOut)
{
int counter = mDataTracker.getStringCounters().getStringCounter(strings);
*isNewEntryOut = (counter == kStringsNotFound);
if (*isNewEntryOut)
{
// This is a unique set of strings, so set up their declaration and update the counter
counter = mDataTracker.getCounters().getAndIncrement(entryPoint, paramName);
mDataTracker.getStringCounters().setStringCounter(strings, counter);
std::string varName = GetVarName(entryPoint, paramName, counter);
std::stringstream declStream;
declStream << "const char *const " << varName << "[]";
std::string decl = declStream.str();
mGlobalVariableDeclarations.push_back(decl);
return varName;
}
else
{
return GetVarName(entryPoint, paramName, counter);
}
}
size_t ReplayWriter::getStoredReplaySourceSize() const
{
size_t sum = 0;
for (const std::string &header : mReplayHeaders)
{
sum += header.size();
}
for (const std::string &publicFunc : mPublicFunctions)
{
sum += publicFunc.size();
}
for (const std::string &privateFunc : mPrivateFunctions)
{
sum += privateFunc.size();
}
return sum;
}
// static
std::string ReplayWriter::GetVarName(EntryPoint entryPoint,
const std::string &paramName,
int counter)
{
std::stringstream strstr;
strstr << GetEntryPointName(entryPoint) << "_" << paramName << "_" << counter;
return strstr.str();
}
void ReplayWriter::saveFrame()
{
if (mReplayHeaders.empty() && mPublicFunctions.empty() && mPrivateFunctions.empty())
{
return;
}
ASSERT(!mSourceFileExtension.empty());
std::stringstream strstr;
strstr << mFilenamePattern << "_" << std::setfill('0') << std::setw(3) << mFrameIndex++ << "."
<< mSourceFileExtension;
std::string frameFilePath = strstr.str();
writeReplaySource(frameFilePath);
}
void ReplayWriter::saveFrameIfFull()
{
if (getStoredReplaySourceSize() < mSourceFileSizeThreshold)
{
INFO() << "Merging captured frame: " << getStoredReplaySourceSize()
<< " less than threshold of " << mSourceFileSizeThreshold << " bytes";
return;
}
saveFrame();
}
void ReplayWriter::saveHeader()
{
std::stringstream headerPathStream;
headerPathStream << mFilenamePattern << ".h";
std::string headerPath = headerPathStream.str();
SaveFileHelper saveH(headerPath);
saveH << mHeaderPrologue << "\n";
saveH << "// Public functions are declared in trace_fixture.h.\n";
saveH << "\n";
saveH << "// Private Functions\n";
saveH << "\n";
for (const std::string &proto : mPrivateFunctionPrototypes)
{
saveH << proto << ";\n";
}
saveH << "\n";
saveH << "// Global variables\n";
saveH << "\n";
for (const std::string &globalVar : mGlobalVariableDeclarations)
{
saveH << "extern " << globalVar << ";\n";
}
mPublicFunctionPrototypes.clear();
mPrivateFunctionPrototypes.clear();
mGlobalVariableDeclarations.clear();
addWrittenFile(headerPath);
}
void ReplayWriter::saveIndexFilesAndHeader()
{
ASSERT(!mSourceFileExtension.empty());
std::stringstream sourcePathStream;
sourcePathStream << mFilenamePattern << "." << mSourceFileExtension;
std::string sourcePath = sourcePathStream.str();
writeReplaySource(sourcePath);
saveHeader();
}
void ReplayWriter::saveSetupFile()
{
ASSERT(!mSourceFileExtension.empty());
std::stringstream strstr;
strstr << mFilenamePattern << "." << mSourceFileExtension;
std::string frameFilePath = strstr.str();
writeReplaySource(frameFilePath);
}
void ReplayWriter::writeReplaySource(const std::string &filename)
{
SaveFileHelper saveCpp(filename);
saveCpp << mSourcePrologue << "\n";
for (const std::string &header : mReplayHeaders)
{
saveCpp << header << "\n";
}
saveCpp << "// Private Functions\n";
saveCpp << "\n";
for (const std::string &func : mPrivateFunctions)
{
saveCpp << func << "\n";
}
saveCpp << "// Public Functions\n";
saveCpp << "\n";
if (mFilenamePattern == "cpp")
{
saveCpp << "extern \"C\"\n";
saveCpp << "{\n";
}
for (const std::string &func : mPublicFunctions)
{
saveCpp << func << "\n";
}
if (mFilenamePattern == "cpp")
{
saveCpp << "} // extern \"C\"\n";
}
mReplayHeaders.clear();
mPrivateFunctions.clear();
mPublicFunctions.clear();
addWrittenFile(filename);
}
void ReplayWriter::addWrittenFile(const std::string &filename)
{
std::string writtenFile = GetBaseName(filename);
ASSERT(std::find(mWrittenFiles.begin(), mWrittenFiles.end(), writtenFile) ==
mWrittenFiles.end());
mWrittenFiles.push_back(writtenFile);
}
std::vector<std::string> ReplayWriter::getAndResetWrittenFiles()
{
std::vector<std::string> results = std::move(mWrittenFiles);
std::sort(results.begin(), results.end());
ASSERT(mWrittenFiles.empty());
return results;
}
} // namespace angle
namespace egl
{
angle::ParamCapture CaptureAttributeMap(const egl::AttributeMap &attribMap)
{
switch (attribMap.getType())
{
case AttributeMapType::Attrib:
{
angle::ParamCapture paramCapture("attrib_list", angle::ParamType::TEGLAttribPointer);
if (attribMap.isEmpty())
{
paramCapture.value.EGLAttribPointerVal = nullptr;
}
else
{
std::vector<EGLAttrib> attribs;
for (const auto &[key, value] : attribMap)
{
attribs.push_back(key);
attribs.push_back(value);
}
attribs.push_back(EGL_NONE);
angle::CaptureMemory(attribs.data(), attribs.size() * sizeof(EGLAttrib),
&paramCapture);
}
return paramCapture;
}
case AttributeMapType::Int:
{
angle::ParamCapture paramCapture("attrib_list", angle::ParamType::TEGLintPointer);
if (attribMap.isEmpty())
{
paramCapture.value.EGLintPointerVal = nullptr;
}
else
{
std::vector<EGLint> attribs;
for (const auto &[key, value] : attribMap)
{
attribs.push_back(static_cast<EGLint>(key));
attribs.push_back(static_cast<EGLint>(value));
}
attribs.push_back(EGL_NONE);
angle::CaptureMemory(attribs.data(), attribs.size() * sizeof(EGLint),
&paramCapture);
}
return paramCapture;
}
default:
UNREACHABLE();
return angle::ParamCapture();
}
}
} // namespace egl