Commit 6c7208f9 by Jamie Madill Committed by Commit Bot

Capture/Replay: Implement mid-execution replay.

Mid-execution replay starts the replay from a specific start frame instead of frame 0. Integration tests will then run between the start and end frames. This lets us make much smaller reproduction cases from large benchmarks or applications. We implement mid-execution replay via a cpp "Setup" function. The replay test will run the setup function before the starting frame. Test execution proceeds normally after setup. Currently we do not implement mid-execution capture. We run capture on all frames. Including frames before the start frame. We do this to intercept compiled shaders and programs for easier caching. This could be changed in the future to also start capture mid-execution. Mid- execution capture might require using ProgramBinary calls to capture shader and program data. Many captures are unimplemented. Several comments indicate missing functionality. There's a lot we can add as we explore replaying more complex applications and higher GL versions. We will also need some kind of state reset functionality so we can run the replay in a loop. Bug: angleproject:3611 Change-Id: I51841fc1a64e3622c34e49c85ed8919a9a7c0b20 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1689329 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarCody Northrop <cnorthrop@google.com>
parent b68a279c
...@@ -4,11 +4,12 @@ ANGLE currently supports a limited OpenGL capture and replay framework. ...@@ -4,11 +4,12 @@ ANGLE currently supports a limited OpenGL capture and replay framework.
Limitations: Limitations:
* Many OpenGL ES functions are not yet implemented. * GLES capture has many unimplemented functions.
* EGL capture and replay is not yet supported. * EGL capture and replay is not yet supported.
* Mid-execution is not yet implemented. * Mid-execution capture is supported with the Vulkan back-end.
* Capture only tested on desktop platforms currently. * Mid-execution capture has many unimplemented features.
* Replays currently are implemented as CPP files. * Capture and replay is currently only tested on desktop platforms.
* Binary replay is unimplemented. CPP replay is supported.
## Capturing and replaying an application ## Capturing and replaying an application
...@@ -18,23 +19,28 @@ To build ANGLE with capture and replay enabled update your GN args: ...@@ -18,23 +19,28 @@ To build ANGLE with capture and replay enabled update your GN args:
angle_with_capture_by_default = true angle_with_capture_by_default = true
``` ```
Once built ANGLE will capture the OpenGL ES calls to a CPP replay. By default the replay will be Once built ANGLE will capture the OpenGL ES calls to CPP replay files. By default the replay will be
stored in the current working directory. The capture files will be named according to the pattern stored in the current working directory. The capture files will be named according to the pattern
`angle_capture_context{id}_frame{n}.cpp`. ANGLE will additionally write out data binary blobs for `angle_capture_context{id}_frame{n}.cpp`. Each GL Context currently has its own replay sources.
Texture or Buffer contexts to `angle_capture_context{id}_frame{n}.angledata`. ANGLE will write out data binary blobs for large Texture or Buffer contents to
`angle_capture_context{id}_frame{n}.angledata`. Replay programs must be able to load data from the
corresponding `angledata` files.
## Controlling Frame Capture ## Controlling Frame Capture
Some simple environment variables control frame capture: Some simple environment variables control frame capture:
* `ANGLE_CAPTURE_ENABLED`: * `ANGLE_CAPTURE_ENABLED`:
Can be set to "0" to disable capture entirely. * Set to `0` to disable capture entirely. Default is `1`.
* `ANGLE_CAPTURE_OUT_DIR=<path>`: * `ANGLE_CAPTURE_OUT_DIR=<path>`:
Can specify an alternate replay output directory than the CWD. * Can specify an alternate replay output directory.
Example: `ANGLE_CAPTURE_OUT_DIR=samples/capture_replay` * Example: `ANGLE_CAPTURE_OUT_DIR=samples/capture_replay`. Default is the CWD.
* `ANGLE_CAPTURE_FRAME_START=<n>`:
* Uses mid-execution capture to write "Setup" functions that starts a Context at frame `n`.
* Example: `ANGLE_CAPTURE_FRAME_START=2`. Default is `0`.
* `ANGLE_CAPTURE_FRAME_END=<n>`: * `ANGLE_CAPTURE_FRAME_END=<n>`:
By default ANGLE will capture the first ten frames. This variable can override the default. * By default ANGLE will capture the first ten frames. This variable can override the default.
Example: `ANGLE_CAPTURE_FRAME_END=4` * Example: `ANGLE_CAPTURE_FRAME_END=4`. Default is `10`.
A good way to test out the capture is to use environment variables in conjunction with the sample A good way to test out the capture is to use environment variables in conjunction with the sample
template. For example: template. For example:
...@@ -47,10 +53,11 @@ $ ANGLE_CAPTURE_FRAME_END=4 ANGLE_CAPTURE_OUT_DIR=samples/capture_replay out/Deb ...@@ -47,10 +53,11 @@ $ ANGLE_CAPTURE_FRAME_END=4 ANGLE_CAPTURE_OUT_DIR=samples/capture_replay out/Deb
To run a CPP replay you can use a template located in To run a CPP replay you can use a template located in
[samples/capture_replay](../samples/capture_replay). Update [samples/capture_replay](../samples/capture_replay). Update
[samples/BUILD.gn](../samples/BUILD.gn) to enable the `capture_replay` sample to include your replay: [samples/BUILD.gn](../samples/BUILD.gn) to enable `capture_replay_sample`
sample to include your replay frames:
``` ```
capture_replay("my_sample") { angle_capture_replay_sample("capture_replay_sample") {
sources = [ sources = [
"capture_replay/angle_capture_context1_frame000.cpp", "capture_replay/angle_capture_context1_frame000.cpp",
"capture_replay/angle_capture_context1_frame001.cpp", "capture_replay/angle_capture_context1_frame001.cpp",
...@@ -64,6 +71,8 @@ capture_replay("my_sample") { ...@@ -64,6 +71,8 @@ capture_replay("my_sample") {
Then build and run your replay sample: Then build and run your replay sample:
``` ```
$ autoninja -C out/Debug my_sample $ autoninja -C out/Debug capture_replay
$ ANGLE_CAPTURE_ENABLED=0 out/Debug/my_sample $ ANGLE_CAPTURE_ENABLED=0 out/Debug/capture_replay
``` ```
Note that we specify `ANGLE_CAPTURE_ENABLED=0` to prevent re-capturing your replay.
...@@ -218,7 +218,7 @@ angle_sample("gles1_draw_texture") { ...@@ -218,7 +218,7 @@ angle_sample("gles1_draw_texture") {
] ]
} }
template("capture_replay") { template("angle_capture_replay_sample") {
angle_sample(target_name) { angle_sample(target_name) {
sources = invoker.sources + [ sources = invoker.sources + [
"capture_replay/CaptureReplay.cpp", "capture_replay/CaptureReplay.cpp",
......
...@@ -149,6 +149,9 @@ class PackedEnumMap ...@@ -149,6 +149,9 @@ class PackedEnumMap
T *data() noexcept { return mPrivateData.data(); } T *data() noexcept { return mPrivateData.data(); }
const T *data() const noexcept { return mPrivateData.data(); } const T *data() const noexcept { return mPrivateData.data(); }
bool operator==(const PackedEnumMap &rhs) const { return mPrivateData == rhs.mPrivateData; }
bool operator!=(const PackedEnumMap &rhs) const { return mPrivateData != rhs.mPrivateData; }
private: private:
Storage mPrivateData; Storage mPrivateData;
}; };
...@@ -500,6 +503,13 @@ typename std::enable_if<IsResourceIDType<T>::value, bool>::type operator!=(const ...@@ -500,6 +503,13 @@ typename std::enable_if<IsResourceIDType<T>::value, bool>::type operator!=(const
return lhs.value != rhs.value; return lhs.value != rhs.value;
} }
template <typename T>
typename std::enable_if<IsResourceIDType<T>::value, bool>::type operator<(const T &lhs,
const T &rhs)
{
return lhs.value < rhs.value;
}
// Used to unbox typed values. // Used to unbox typed values.
template <typename ResourceIDType> template <typename ResourceIDType>
GLuint GetIDValue(ResourceIDType id); GLuint GetIDValue(ResourceIDType id);
......
...@@ -16,7 +16,11 @@ ...@@ -16,7 +16,11 @@
#include "common/system_utils.h" #include "common/system_utils.h"
#include "libANGLE/Context.h" #include "libANGLE/Context.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/Shader.h"
#include "libANGLE/VertexArray.h" #include "libANGLE/VertexArray.h"
#include "libANGLE/capture_gles_2_0_autogen.h"
#include "libANGLE/capture_gles_3_0_autogen.h"
#include "libANGLE/gl_enum_utils.h" #include "libANGLE/gl_enum_utils.h"
#if !ANGLE_CAPTURE_ENABLED #if !ANGLE_CAPTURE_ENABLED
...@@ -268,6 +272,7 @@ void WriteBinaryParamReplay(DataCounters *counters, ...@@ -268,6 +272,7 @@ void WriteBinaryParamReplay(DataCounters *counters,
WriteParamStaticVarName(call, param, counter, out); WriteParamStaticVarName(call, param, counter, out);
} }
} }
void WriteCppReplayForCall(const CallCapture &call, void WriteCppReplayForCall(const CallCapture &call,
DataCounters *counters, DataCounters *counters,
std::ostream &out, std::ostream &out,
...@@ -428,16 +433,34 @@ struct SaveFileHelper ...@@ -428,16 +433,34 @@ struct SaveFileHelper
std::string filePath; std::string filePath;
}; };
void SaveBinaryData(const std::string &outDir,
std::ostream &out,
int contextId,
uint32_t frameIndex,
const char *suffix,
const std::vector<uint8_t> &binaryData)
{
std::string binaryDataFileName = GetCaptureFileName(contextId, frameIndex, suffix);
out << " LoadBinaryData(\"" << binaryDataFileName << "\", "
<< static_cast<int>(binaryData.size()) << ");\n";
std::string dataFilepath = GetCaptureFilePath(outDir, contextId, frameIndex, suffix);
SaveFileHelper saveData(dataFilepath, std::ios::binary);
saveData.ofs.write(reinterpret_cast<const char *>(binaryData.data()), binaryData.size());
}
void WriteCppReplay(const std::string &outDir, void WriteCppReplay(const std::string &outDir,
int contextId, int contextId,
uint32_t frameIndex, uint32_t frameIndex,
const std::vector<CallCapture> &calls) const std::vector<CallCapture> &frameCalls,
const std::vector<CallCapture> &setupCalls)
{ {
DataCounters counters; DataCounters counters;
std::stringstream out; std::stringstream out;
std::stringstream header; std::stringstream header;
std::vector<uint8_t> binaryData;
header << "#include \"" << FmtCapturePrefix(contextId) << ".h\"\n"; header << "#include \"" << FmtCapturePrefix(contextId) << ".h\"\n";
header << ""; header << "";
...@@ -445,12 +468,39 @@ void WriteCppReplay(const std::string &outDir, ...@@ -445,12 +468,39 @@ void WriteCppReplay(const std::string &outDir,
header << "namespace\n"; header << "namespace\n";
header << "{\n"; header << "{\n";
if (frameIndex == 0 || !setupCalls.empty())
{
out << "void SetupContext" << Str(contextId) << "Replay()\n";
out << "{\n";
std::stringstream setupCallStream;
std::vector<uint8_t> setupBinaryData;
for (const CallCapture &call : setupCalls)
{
setupCallStream << " ";
WriteCppReplayForCall(call, &counters, setupCallStream, header, &setupBinaryData);
setupCallStream << ";\n";
}
if (!setupBinaryData.empty())
{
SaveBinaryData(outDir, out, contextId, frameIndex, ".setup.angledata", setupBinaryData);
}
out << setupCallStream.str();
out << "}\n";
out << "\n";
}
out << "void " << FmtReplayFunction(contextId, frameIndex) << "\n"; out << "void " << FmtReplayFunction(contextId, frameIndex) << "\n";
out << "{\n"; out << "{\n";
std::stringstream callStream; std::stringstream callStream;
std::vector<uint8_t> binaryData;
for (const CallCapture &call : calls) for (const CallCapture &call : frameCalls)
{ {
callStream << " "; callStream << " ";
WriteCppReplayForCall(call, &counters, callStream, header, &binaryData); WriteCppReplayForCall(call, &counters, callStream, header, &binaryData);
...@@ -459,21 +509,13 @@ void WriteCppReplay(const std::string &outDir, ...@@ -459,21 +509,13 @@ void WriteCppReplay(const std::string &outDir,
if (!binaryData.empty()) if (!binaryData.empty())
{ {
std::string binaryDataFileName = GetCaptureFileName(contextId, frameIndex, ".angledata"); SaveBinaryData(outDir, out, contextId, frameIndex, ".angledata", binaryData);
out << " LoadBinaryData(\"" << binaryDataFileName << "\", "
<< static_cast<int>(binaryData.size()) << ");\n";
std::string dataFilepath = GetCaptureFilePath(outDir, contextId, frameIndex, ".angledata");
SaveFileHelper saveData(dataFilepath, std::ios::binary);
saveData.ofs.write(reinterpret_cast<const char *>(binaryData.data()), binaryData.size());
} }
out << callStream.str(); out << callStream.str();
out << "}\n"; out << "}\n";
header << "} // anonymous namespace\n"; header << "} // namespace\n";
{ {
std::string outString = out.str(); std::string outString = out.str();
...@@ -482,7 +524,7 @@ void WriteCppReplay(const std::string &outDir, ...@@ -482,7 +524,7 @@ void WriteCppReplay(const std::string &outDir,
std::string cppFilePath = GetCaptureFilePath(outDir, contextId, frameIndex, ".cpp"); std::string cppFilePath = GetCaptureFilePath(outDir, contextId, frameIndex, ".cpp");
SaveFileHelper saveCpp(cppFilePath); SaveFileHelper saveCpp(cppFilePath);
saveCpp << headerString << "\n\n" << outString; saveCpp << headerString << "\n" << outString;
} }
} }
...@@ -513,6 +555,7 @@ void WriteCppReplayIndexFiles(const std::string &outDir, ...@@ -513,6 +555,7 @@ void WriteCppReplayIndexFiles(const std::string &outDir,
header << "constexpr uint32_t kReplayFrameStart = " << frameStart << ";\n"; header << "constexpr uint32_t kReplayFrameStart = " << frameStart << ";\n";
header << "constexpr uint32_t kReplayFrameEnd = " << frameEnd << ";\n"; header << "constexpr uint32_t kReplayFrameEnd = " << frameEnd << ";\n";
header << "\n"; header << "\n";
header << "void SetupContext" << contextId << "Replay();\n";
header << "void ReplayContext" << contextId << "Frame(uint32_t frameIndex);\n"; header << "void ReplayContext" << contextId << "Frame(uint32_t frameIndex);\n";
header << "\n"; header << "\n";
for (uint32_t frameIndex = frameStart; frameIndex < frameEnd; ++frameIndex) for (uint32_t frameIndex = frameStart; frameIndex < frameEnd; ++frameIndex)
...@@ -653,7 +696,643 @@ void WriteCppReplayIndexFiles(const std::string &outDir, ...@@ -653,7 +696,643 @@ void WriteCppReplayIndexFiles(const std::string &outDir,
SaveFileHelper saveSource(sourcePath); SaveFileHelper saveSource(sourcePath);
saveSource << sourceContents; saveSource << sourceContents;
} }
} // namespace }
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,
std::vector<CallCapture> *callsOut)
{
GLsizei n = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
ASSERT(param.data.size() == 1);
ResourceIDType resourceIDType = GetResourceIDTypeFromParamType(param.type);
ASSERT(resourceIDType != ResourceIDType::InvalidEnum);
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());
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));
}
}
void MaybeCaptureUpdateResourceIDs(const gl::Context *context, std::vector<CallCapture> *callsOut)
{
const CallCapture &call = callsOut->back();
switch (call.entryPoint)
{
case gl::EntryPoint::GenBuffers:
{
const ParamCapture &buffers =
call.params.getParam("buffersPacked", ParamType::TBufferIDPointer, 1);
CaptureUpdateResourceIDs<gl::BufferID>(context, call, buffers, callsOut);
break;
}
case gl::EntryPoint::GenFencesNV:
{
const ParamCapture &fences =
call.params.getParam("fencesPacked", ParamType::TFenceNVIDPointer, 1);
CaptureUpdateResourceIDs<gl::FenceNVID>(context, call, fences, callsOut);
break;
}
case gl::EntryPoint::GenFramebuffers:
case gl::EntryPoint::GenFramebuffersOES:
{
const ParamCapture &framebuffers =
call.params.getParam("framebuffersPacked", ParamType::TFramebufferIDPointer, 1);
CaptureUpdateResourceIDs<gl::FramebufferID>(context, call, framebuffers, callsOut);
break;
}
case gl::EntryPoint::GenPathsCHROMIUM:
{
// TODO(jmadill): Handle path IDs. http://anglebug.com/3662
break;
}
case gl::EntryPoint::GenProgramPipelines:
{
const ParamCapture &pipelines =
call.params.getParam("pipelinesPacked", ParamType::TProgramPipelineIDPointer, 1);
CaptureUpdateResourceIDs<gl::ProgramPipelineID>(context, call, pipelines, callsOut);
break;
}
case gl::EntryPoint::GenQueries:
case gl::EntryPoint::GenQueriesEXT:
{
const ParamCapture &queries =
call.params.getParam("idsPacked", ParamType::TQueryIDPointer, 1);
CaptureUpdateResourceIDs<gl::QueryID>(context, call, queries, callsOut);
break;
}
case gl::EntryPoint::GenRenderbuffers:
case gl::EntryPoint::GenRenderbuffersOES:
{
const ParamCapture &renderbuffers =
call.params.getParam("renderbuffersPacked", ParamType::TRenderbufferIDPointer, 1);
CaptureUpdateResourceIDs<gl::RenderbufferID>(context, call, renderbuffers, callsOut);
break;
}
case gl::EntryPoint::GenSamplers:
{
const ParamCapture &samplers =
call.params.getParam("samplersPacked", ParamType::TSamplerIDPointer, 1);
CaptureUpdateResourceIDs<gl::SamplerID>(context, call, samplers, callsOut);
break;
}
case gl::EntryPoint::GenSemaphoresEXT:
{
const ParamCapture &semaphores =
call.params.getParam("semaphoresPacked", ParamType::TSemaphoreIDPointer, 1);
CaptureUpdateResourceIDs<gl::SemaphoreID>(context, call, semaphores, callsOut);
break;
}
case gl::EntryPoint::GenTextures:
{
const ParamCapture &textures =
call.params.getParam("texturesPacked", ParamType::TTextureIDPointer, 1);
CaptureUpdateResourceIDs<gl::TextureID>(context, call, textures, callsOut);
break;
}
case gl::EntryPoint::GenTransformFeedbacks:
{
const ParamCapture &xfbs =
call.params.getParam("idsPacked", ParamType::TTransformFeedbackIDPointer, 1);
CaptureUpdateResourceIDs<gl::TransformFeedbackID>(context, call, xfbs, callsOut);
break;
}
case gl::EntryPoint::GenVertexArrays:
case gl::EntryPoint::GenVertexArraysOES:
{
const ParamCapture &vertexArrays =
call.params.getParam("vetexArraysPacked", ParamType::TVertexArrayIDPointer, 1);
CaptureUpdateResourceIDs<gl::VertexArrayID>(context, call, vertexArrays, callsOut);
break;
}
default:
break;
}
}
void CaptureMidExecutionSetup(const gl::Context *context,
std::vector<CallCapture> *setupCalls,
const ShaderSourceMap &cachedShaderSources,
const ProgramSourceMap &cachedProgramSources)
{
const gl::State &glState = context->getState();
// Small helper function to make the code more readable.
auto cap = [setupCalls](CallCapture &&call) { setupCalls->emplace_back(std::move(call)); };
// Currently this code assumes we can use create-on-bind. It does not support 'Gen' usage.
// TODO(jmadill): Use handle mapping for captured objects. http://anglebug.com/3662
// Capture Buffer data.
const gl::BufferManager &buffers = glState.getBufferManagerForCapture();
const gl::BoundBufferMap &boundBuffers = glState.getBoundBuffersForCapture();
gl::BufferID currentArrayBuffer = {0};
for (const auto &bufferIter : buffers)
{
gl::BufferID id = {bufferIter.first};
gl::Buffer *buffer = bufferIter.second;
if (id.value == 0)
{
continue;
}
// 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)
{
continue;
}
ASSERT(!buffer->isMapped());
(void)buffer->mapRange(context, 0, static_cast<GLsizeiptr>(buffer->getSize()),
GL_MAP_READ_BIT);
// Generate binding.
cap(CaptureGenBuffers(context, true, 1, &id));
MaybeCaptureUpdateResourceIDs(context, setupCalls);
// Always use the array buffer binding point to upload data to keep things simple.
cap(CaptureBindBuffer(context, true, gl::BufferBinding::Array, id));
cap(CaptureBufferData(context, true, gl::BufferBinding::Array,
static_cast<GLsizeiptr>(buffer->getSize()), buffer->getMapPointer(),
buffer->getUsage()));
currentArrayBuffer = id;
GLboolean dontCare;
(void)buffer->unmap(context, &dontCare);
}
// Capture Buffer bindings.
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;
if ((isArray && currentArrayBuffer != bufferID) || (!isArray && bufferID.value != 0))
{
cap(CaptureBindBuffer(context, true, binding, bufferID));
}
}
// Capture Texture setup and data.
const gl::TextureManager &textures = glState.getTextureManagerForCapture();
const gl::TextureBindingMap &boundTextures = glState.getBoundTexturesForCapture();
gl::TextureTypeMap<gl::TextureID> currentTextureBindings;
for (const auto &textureIter : textures)
{
gl::TextureID id = {textureIter.first};
const gl::Texture *texture = textureIter.second;
if (id.value == 0)
{
continue;
}
// Gen the Texture.
cap(CaptureGenTextures(context, true, 1, &id));
MaybeCaptureUpdateResourceIDs(context, setupCalls);
cap(CaptureBindTexture(context, true, texture->getType(), id));
currentTextureBindings[texture->getType()] = id;
// 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 = [cap, context, texture](GLenum pname, GLint param) {
cap(CaptureTexParameteri(context, 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());
}
// 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;
// Assume 2D or Cube for now.
// TODO(jmadill): 3D and 2D array textures. http://anglebug.com/4048
ASSERT(index.getType() == gl::TextureType::_2D ||
index.getType() == gl::TextureType::CubeMap);
angle::MemoryBuffer data;
// Use ANGLE_get_image to read back pixel data.
if (context->getExtensions().getImageANGLE)
{
GLenum internalFormat = format.internalFormat;
GLenum getFormat = format.format;
GLenum getType = format.type;
GLsizei pixelBytes = format.pixelBytes;
if (format.compressed)
{
// Determine if we're emulating the compressed format.
GLenum readFormat = texture->getImplementationColorReadFormat(context);
GLenum readType = texture->getImplementationColorReadType(context);
const gl::InternalFormat &nativeFormat =
gl::GetInternalFormatInfo(readFormat, readType);
if (!nativeFormat.compressed)
{
// Emulate with a non-compressed format.
internalFormat = nativeFormat.internalFormat;
getFormat = readFormat;
getType = readType;
pixelBytes = nativeFormat.pixelBytes;
}
else
{
// Will need to add glGetCompressedTexImage support to ANGLE_get_image.
// TODO(jmadill): add glGetCompressedTexImage. http://anglebug.com/3944
UNIMPLEMENTED();
}
}
uint32_t dataSize = pixelBytes * desc.size.width * desc.size.height;
bool result = data.resize(dataSize);
ASSERT(result);
gl::PixelPackState packState;
packState.alignment = 1;
(void)texture->getTexImage(context, packState, nullptr, index.getTarget(),
index.getLevelIndex(), getFormat, getType, data.data());
cap(CaptureTexImage2D(context, true, index.getTarget(), index.getLevelIndex(),
internalFormat, desc.size.width, desc.size.height, 0,
getFormat, getType, data.data()));
}
else
{
cap(CaptureTexImage2D(context, true, index.getTarget(), index.getLevelIndex(),
format.internalFormat, desc.size.width, desc.size.height, 0,
format.format, format.type, nullptr));
}
}
}
// Set Texture bindings.
size_t currentActiveTexture = 0;
for (gl::TextureType textureType : angle::AllEnums<gl::TextureType>())
{
const gl::TextureBindingVector &bindings = boundTextures[textureType];
for (size_t bindingIndex = 0; bindingIndex < bindings.size(); ++bindingIndex)
{
gl::TextureID textureID = bindings[bindingIndex].id();
if (textureID.value != 0)
{
if (currentActiveTexture != bindingIndex)
{
cap(CaptureActiveTexture(context, true,
GL_TEXTURE0 + static_cast<GLenum>(bindingIndex)));
currentActiveTexture = bindingIndex;
}
if (currentTextureBindings[textureType] != textureID)
{
cap(CaptureBindTexture(context, true, textureType, textureID));
currentTextureBindings[textureType] = textureID;
}
}
}
}
// Set active Texture.
size_t stateActiveTexture = glState.getActiveSampler();
if (currentActiveTexture != stateActiveTexture)
{
cap(CaptureActiveTexture(context, true,
GL_TEXTURE0 + static_cast<GLenum>(stateActiveTexture)));
}
// Capture Renderbuffers.
const gl::RenderbufferManager &renderbuffers = glState.getRenderbufferManagerForCapture();
gl::RenderbufferID currentRenderbuffer = {0};
for (const auto &renderbufIter : renderbuffers)
{
gl::RenderbufferID id = {renderbufIter.first};
const gl::Renderbuffer *renderbuffer = renderbufIter.second;
// Generate renderbuffer id.
cap(CaptureGenRenderbuffers(context, true, 1, &id));
MaybeCaptureUpdateResourceIDs(context, setupCalls);
cap(CaptureBindRenderbuffer(context, true, GL_RENDERBUFFER, id));
currentRenderbuffer = id;
GLenum internalformat = renderbuffer->getFormat().info->internalFormat;
if (renderbuffer->getSamples() > 0)
{
// Note: We could also use extensions if available.
cap(CaptureRenderbufferStorageMultisample(
context, true, GL_RENDERBUFFER, renderbuffer->getSamples(), internalformat,
renderbuffer->getWidth(), renderbuffer->getHeight()));
}
else
{
cap(CaptureRenderbufferStorage(context, true, GL_RENDERBUFFER, internalformat,
renderbuffer->getWidth(), renderbuffer->getHeight()));
}
// TODO(jmadill): Capture renderbuffer contents. http://anglebug.com/3662
}
// Set Renderbuffer binding.
if (currentRenderbuffer != glState.getRenderbufferId())
{
cap(CaptureBindRenderbuffer(context, true, GL_RENDERBUFFER, glState.getRenderbufferId()));
}
// Capture Framebuffers.
const gl::FramebufferManager &framebuffers = glState.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;
cap(CaptureGenFramebuffers(context, true, 1, &id));
MaybeCaptureUpdateResourceIDs(context, setupCalls);
cap(CaptureBindFramebuffer(context, true, GL_FRAMEBUFFER, id));
currentDrawFramebuffer = currentReadFramebuffer = id;
// Color Attachments.
for (const gl::FramebufferAttachment &colorAttachment : framebuffer->getColorAttachments())
{
if (!colorAttachment.isAttached())
{
continue;
}
GLuint resourceID = colorAttachment.getResource()->getId();
// TODO(jmadill): Layer attachments. http://anglebug.com/3662
if (colorAttachment.type() == GL_TEXTURE)
{
gl::ImageIndex index = colorAttachment.getTextureImageIndex();
cap(CaptureFramebufferTexture2D(context, true, GL_FRAMEBUFFER,
colorAttachment.getBinding(), index.getTarget(),
{resourceID}, index.getLevelIndex()));
}
else
{
ASSERT(colorAttachment.type() == GL_RENDERBUFFER);
cap(CaptureFramebufferRenderbuffer(context, true, GL_FRAMEBUFFER,
colorAttachment.getBinding(), GL_RENDERBUFFER,
{resourceID}));
}
}
// TODO(jmadill): Draw buffer states. http://anglebug.com/3662
}
// Capture framebuffer bindings.
gl::FramebufferID stateReadFramebuffer = glState.getReadFramebuffer()->id();
gl::FramebufferID stateDrawFramebuffer = glState.getDrawFramebuffer()->id();
if (stateDrawFramebuffer == stateReadFramebuffer)
{
if (currentDrawFramebuffer != stateDrawFramebuffer ||
currentReadFramebuffer != stateReadFramebuffer)
{
cap(CaptureBindFramebuffer(context, true, GL_FRAMEBUFFER, stateDrawFramebuffer));
currentDrawFramebuffer = currentReadFramebuffer = stateDrawFramebuffer;
}
}
else
{
if (currentDrawFramebuffer != stateDrawFramebuffer)
{
cap(CaptureBindFramebuffer(context, true, GL_DRAW_FRAMEBUFFER, currentDrawFramebuffer));
currentDrawFramebuffer = stateDrawFramebuffer;
}
if (currentReadFramebuffer != stateReadFramebuffer)
{
cap(CaptureBindFramebuffer(context, true, GL_READ_FRAMEBUFFER,
glState.getReadFramebuffer()->id()));
currentReadFramebuffer = stateReadFramebuffer;
}
}
// Capture Shaders and Programs.
const gl::ShaderProgramManager &shadersAndPrograms =
glState.getShaderProgramManagerForCapture();
const gl::ResourceMap<gl::Shader, gl::ShaderProgramID> &shaders =
shadersAndPrograms.getShadersForCapture();
const gl::ResourceMap<gl::Program, gl::ShaderProgramID> &programs =
shadersAndPrograms.getProgramsForCapture();
// Capture Program binary state. Use shader ID 1 as a temporary shader ID.
gl::ShaderProgramID tempShaderID = {1};
for (const auto &programIter : programs)
{
gl::ShaderProgramID id = {programIter.first};
gl::Program *program = programIter.second;
// Get last compiled shader source.
const auto &foundSources = cachedProgramSources.find(id);
ASSERT(foundSources != cachedProgramSources.end());
const ProgramSources &linkedSources = foundSources->second;
// Unlinked programs don't have an executable. Thus they don't need to be linked.
if (!program->isLinked())
{
continue;
}
cap(CaptureCreateProgram(context, true, id.value));
// Compile with last linked sources.
for (gl::ShaderType shaderType : program->getState().getLinkedShaderStages())
{
const std::string &sourceString = linkedSources[shaderType];
const char *sourcePointer = sourceString.c_str();
// Compile and attach the temporary shader. Then free it immediately.
cap(CaptureCreateShader(context, true, shaderType, tempShaderID.value));
cap(CaptureShaderSource(context, true, tempShaderID, 1, &sourcePointer, nullptr));
cap(CaptureCompileShader(context, true, tempShaderID));
cap(CaptureAttachShader(context, true, id, tempShaderID));
cap(CaptureDeleteShader(context, true, tempShaderID));
}
cap(CaptureLinkProgram(context, true, id));
}
// Handle shaders.
for (const auto &shaderIter : shaders)
{
gl::ShaderProgramID id = {shaderIter.first};
gl::Shader *shader = shaderIter.second;
cap(CaptureCreateShader(context, true, shader->getType(), id.value));
std::string shaderSource = shader->getSourceString();
const char *sourcePointer = shaderSource.empty() ? nullptr : shaderSource.c_str();
// 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())
{
const auto &foundSources = cachedShaderSources.find(id);
ASSERT(foundSources != cachedShaderSources.end());
const std::string &capturedSource = foundSources->second;
if (capturedSource != shaderSource)
{
ASSERT(!capturedSource.empty());
sourcePointer = capturedSource.c_str();
}
cap(CaptureShaderSource(context, true, id, 1, &sourcePointer, nullptr));
cap(CaptureCompileShader(context, true, id));
}
if (sourcePointer && (!shader->isCompiled() || sourcePointer != shaderSource.c_str()))
{
cap(CaptureShaderSource(context, true, id, 1, &sourcePointer, nullptr));
}
}
// 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 (glState.getProgram())
{
cap(CaptureUseProgram(context, true, glState.getProgram()->id()));
}
// TODO(http://anglebug.com/3662): ES 3.x objects.
// Capture GL Context states.
// TODO(http://anglebug.com/3662): Complete state capture.
const gl::RasterizerState defaultRasterState;
const gl::RasterizerState &currentRasterState = glState.getRasterizerState();
if (currentRasterState.cullFace != defaultRasterState.cullFace)
{
if (currentRasterState.cullFace)
{
cap(CaptureEnable(context, true, GL_CULL_FACE));
}
else
{
cap(CaptureDisable(context, true, GL_CULL_FACE));
}
}
if (currentRasterState.cullMode != defaultRasterState.cullMode)
{
cap(CaptureCullFace(context, true, currentRasterState.cullMode));
}
if (currentRasterState.frontFace != defaultRasterState.frontFace)
{
cap(CaptureFrontFace(context, true, currentRasterState.frontFace));
}
const gl::ColorF &currentClearColor = glState.getColorClearValue();
if (currentClearColor != gl::ColorF())
{
cap(CaptureClearColor(context, true, currentClearColor.red, currentClearColor.green,
currentClearColor.blue, currentClearColor.alpha));
}
}
} // namespace } // namespace
ParamCapture::ParamCapture() : type(ParamType::TGLenum), enumGroup(gl::GLenumGroup::DefaultGroup) {} ParamCapture::ParamCapture() : type(ParamType::TGLenum), enumGroup(gl::GLenumGroup::DefaultGroup) {}
...@@ -823,7 +1502,7 @@ FrameCapture::FrameCapture() ...@@ -823,7 +1502,7 @@ FrameCapture::FrameCapture()
std::string startFromEnv = angle::GetEnvironmentVar(kFrameStartVarName); std::string startFromEnv = angle::GetEnvironmentVar(kFrameStartVarName);
if (!startFromEnv.empty()) if (!startFromEnv.empty())
{ {
WARN() << "Capture frame start is not yet supported. Defaulting to 0."; mFrameStart = atoi(startFromEnv.c_str());
} }
std::string endFromEnv = angle::GetEnvironmentVar(kFrameEndVarName); std::string endFromEnv = angle::GetEnvironmentVar(kFrameEndVarName);
...@@ -846,7 +1525,7 @@ void FrameCapture::maybeCaptureClientData(const gl::Context *context, const Call ...@@ -846,7 +1525,7 @@ void FrameCapture::maybeCaptureClientData(const gl::Context *context, const Call
if (call.params.hasClientArrayData()) if (call.params.hasClientArrayData())
{ {
mClientVertexArrayMap[index] = static_cast<int>(mCalls.size()); mClientVertexArrayMap[index] = static_cast<int>(mFrameCalls.size());
} }
else else
{ {
...@@ -905,6 +1584,28 @@ void FrameCapture::maybeCaptureClientData(const gl::Context *context, const Call ...@@ -905,6 +1584,28 @@ void FrameCapture::maybeCaptureClientData(const gl::Context *context, const Call
break; break;
} }
case gl::EntryPoint::CompileShader:
{
// Refresh the cached shader sources.
gl::ShaderProgramID shaderID =
call.params.getParam("shaderPacked", ParamType::TShaderProgramID, 0)
.value.ShaderProgramIDVal;
const gl::Shader *shader = context->getShader(shaderID);
mCachedShaderSources[shaderID] = shader->getSourceString();
break;
}
case gl::EntryPoint::LinkProgram:
{
// Refresh the cached program sources.
gl::ShaderProgramID programID =
call.params.getParam("programPacked", ParamType::TShaderProgramID, 0)
.value.ShaderProgramIDVal;
const gl::Program *program = context->getProgramResolveLink(programID);
mCachedProgramSources[programID] = GetAttachedProgramSources(program);
break;
}
default: default:
break; break;
} }
...@@ -916,144 +1617,10 @@ void FrameCapture::captureCall(const gl::Context *context, CallCapture &&call) ...@@ -916,144 +1617,10 @@ void FrameCapture::captureCall(const gl::Context *context, CallCapture &&call)
maybeCaptureClientData(context, call); maybeCaptureClientData(context, call);
mReadBufferSize = std::max(mReadBufferSize, call.params.getReadBufferSize()); mReadBufferSize = std::max(mReadBufferSize, call.params.getReadBufferSize());
mCalls.emplace_back(std::move(call)); mFrameCalls.emplace_back(std::move(call));
// Process resource ID updates. // Process resource ID updates.
maybeUpdateResourceIDs(context, mCalls.back()); MaybeCaptureUpdateResourceIDs(context, &mFrameCalls);
}
template <typename IDType>
void FrameCapture::captureUpdateResourceIDs(const gl::Context *context,
const CallCapture &call,
const ParamCapture &param)
{
GLsizei n = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal;
ASSERT(param.data.size() == 1);
const IDType *returnedIDs = reinterpret_cast<const IDType *>(param.data[0].data());
ResourceIDType resourceIDType = GetResourceIDTypeFromParamType(param.type);
ASSERT(resourceIDType != ResourceIDType::InvalidEnum);
const char *resourceName = GetResourceIDTypeName(resourceIDType);
std::stringstream updateFuncNameStr;
updateFuncNameStr << "Update" << resourceName << "ID";
std::string updateFuncName = updateFuncNameStr.str();
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);
mCalls.emplace_back(updateFuncName, std::move(params));
}
}
void FrameCapture::maybeUpdateResourceIDs(const gl::Context *context, const CallCapture &call)
{
switch (call.entryPoint)
{
case gl::EntryPoint::GenBuffers:
{
const ParamCapture &buffers =
call.params.getParam("buffersPacked", ParamType::TBufferIDPointer, 1);
captureUpdateResourceIDs<gl::BufferID>(context, call, buffers);
break;
}
case gl::EntryPoint::GenFencesNV:
{
const ParamCapture &fences =
call.params.getParam("fencesPacked", ParamType::TFenceNVIDPointer, 1);
captureUpdateResourceIDs<gl::FenceNVID>(context, call, fences);
break;
}
case gl::EntryPoint::GenFramebuffers:
case gl::EntryPoint::GenFramebuffersOES:
{
const ParamCapture &framebuffers =
call.params.getParam("framebuffersPacked", ParamType::TFramebufferIDPointer, 1);
captureUpdateResourceIDs<gl::FramebufferID>(context, call, framebuffers);
break;
}
case gl::EntryPoint::GenPathsCHROMIUM:
{
// TODO(jmadill): Handle path IDs. http://anglebug.com/3611
break;
}
case gl::EntryPoint::GenProgramPipelines:
{
const ParamCapture &pipelines =
call.params.getParam("pipelinesPacked", ParamType::TProgramPipelineIDPointer, 1);
captureUpdateResourceIDs<gl::ProgramPipelineID>(context, call, pipelines);
break;
}
case gl::EntryPoint::GenQueries:
case gl::EntryPoint::GenQueriesEXT:
{
const ParamCapture &queries =
call.params.getParam("idsPacked", ParamType::TQueryIDPointer, 1);
captureUpdateResourceIDs<gl::QueryID>(context, call, queries);
break;
}
case gl::EntryPoint::GenRenderbuffers:
case gl::EntryPoint::GenRenderbuffersOES:
{
const ParamCapture &renderbuffers =
call.params.getParam("renderbuffersPacked", ParamType::TRenderbufferIDPointer, 1);
captureUpdateResourceIDs<gl::RenderbufferID>(context, call, renderbuffers);
break;
}
case gl::EntryPoint::GenSamplers:
{
const ParamCapture &samplers =
call.params.getParam("samplersPacked", ParamType::TSamplerIDPointer, 1);
captureUpdateResourceIDs<gl::SamplerID>(context, call, samplers);
break;
}
case gl::EntryPoint::GenSemaphoresEXT:
{
const ParamCapture &semaphores =
call.params.getParam("semaphoresPacked", ParamType::TSemaphoreIDPointer, 1);
captureUpdateResourceIDs<gl::SemaphoreID>(context, call, semaphores);
break;
}
case gl::EntryPoint::GenTextures:
{
const ParamCapture &textures =
call.params.getParam("texturesPacked", ParamType::TTextureIDPointer, 1);
captureUpdateResourceIDs<gl::TextureID>(context, call, textures);
break;
}
case gl::EntryPoint::GenTransformFeedbacks:
{
const ParamCapture &xfbs =
call.params.getParam("idsPacked", ParamType::TTransformFeedbackIDPointer, 1);
captureUpdateResourceIDs<gl::TransformFeedbackID>(context, call, xfbs);
break;
}
case gl::EntryPoint::GenVertexArrays:
case gl::EntryPoint::GenVertexArraysOES:
{
const ParamCapture &vertexArrays =
call.params.getParam("vetexArraysPacked", ParamType::TVertexArrayIDPointer, 1);
captureUpdateResourceIDs<gl::VertexArrayID>(context, call, vertexArrays);
break;
}
default:
break;
}
} }
void FrameCapture::captureClientArraySnapshot(const gl::Context *context, void FrameCapture::captureClientArraySnapshot(const gl::Context *context,
...@@ -1083,7 +1650,7 @@ void FrameCapture::captureClientArraySnapshot(const gl::Context *context, ...@@ -1083,7 +1650,7 @@ void FrameCapture::captureClientArraySnapshot(const gl::Context *context,
// The last capture element doesn't take up the full stride. // The last capture element doesn't take up the full stride.
size_t bytesToCapture = (count - 1) * binding.getStride() + attrib.format->pixelBytes; size_t bytesToCapture = (count - 1) * binding.getStride() + attrib.format->pixelBytes;
CallCapture &call = mCalls[callIndex]; CallCapture &call = mFrameCalls[callIndex];
ParamCapture &param = call.params.getClientArrayPointerParameter(); ParamCapture &param = call.params.getClientArrayPointerParameter();
ASSERT(param.type == ParamType::TvoidConstPointer); ASSERT(param.type == ParamType::TvoidConstPointer);
...@@ -1097,7 +1664,7 @@ void FrameCapture::captureClientArraySnapshot(const gl::Context *context, ...@@ -1097,7 +1664,7 @@ void FrameCapture::captureClientArraySnapshot(const gl::Context *context,
updateParamBuffer.addValueParam<GLuint64>("size", ParamType::TGLuint64, bytesToCapture); updateParamBuffer.addValueParam<GLuint64>("size", ParamType::TGLuint64, bytesToCapture);
mCalls.emplace_back("UpdateClientArrayPointer", std::move(updateParamBuffer)); mFrameCalls.emplace_back("UpdateClientArrayPointer", std::move(updateParamBuffer));
mClientArraySizes[attribIndex] = mClientArraySizes[attribIndex] =
std::max(mClientArraySizes[attribIndex], bytesToCapture); std::max(mClientArraySizes[attribIndex], bytesToCapture);
...@@ -1107,32 +1674,41 @@ void FrameCapture::captureClientArraySnapshot(const gl::Context *context, ...@@ -1107,32 +1674,41 @@ void FrameCapture::captureClientArraySnapshot(const gl::Context *context,
void FrameCapture::onEndFrame(const gl::Context *context) void FrameCapture::onEndFrame(const gl::Context *context)
{ {
if (!mCalls.empty()) // Note that we currently capture before the start frame to collect shader and program sources.
if (!mFrameCalls.empty() && mFrameIndex >= mFrameStart)
{ {
WriteCppReplay(mOutDirectory, context->id(), mFrameIndex, mCalls); WriteCppReplay(mOutDirectory, context->id(), mFrameIndex, mFrameCalls, mSetupCalls);
// Save the index files after the last frame.
if (mFrameIndex == mFrameEnd)
{
WriteCppReplayIndexFiles(mOutDirectory, context->id(), mFrameStart, mFrameEnd,
mReadBufferSize, mClientArraySizes, mHasResourceType);
}
}
// Count resource IDs. // Count resource IDs. This is also done on every frame. It could probably be done by checking
for (const CallCapture &call : mCalls) // the GL state instead of the calls.
for (const CallCapture &call : mFrameCalls)
{
for (const ParamCapture &param : call.params.getParamCaptures())
{ {
for (const ParamCapture &param : call.params.getParamCaptures()) ResourceIDType idType = GetResourceIDTypeFromParamType(param.type);
if (idType != ResourceIDType::InvalidEnum)
{ {
ResourceIDType idType = GetResourceIDTypeFromParamType(param.type); mHasResourceType.set(idType);
if (idType != ResourceIDType::InvalidEnum)
{
mHasResourceType[idType] = true;
}
} }
} }
}
reset(); reset();
mFrameIndex++; mFrameIndex++;
// Save the index files after the last frame. if (enabled() && mFrameIndex == mFrameStart)
if (mFrameIndex == mFrameEnd + 1) {
{ mSetupCalls.clear();
WriteCppReplayIndexFiles(mOutDirectory, context->id(), mFrameStart, mFrameEnd, CaptureMidExecutionSetup(context, &mSetupCalls, mCachedShaderSources,
mReadBufferSize, mClientArraySizes, mHasResourceType); mCachedProgramSources);
}
} }
} }
...@@ -1148,13 +1724,16 @@ int DataCounters::getAndIncrement(gl::EntryPoint entryPoint, const std::string & ...@@ -1148,13 +1724,16 @@ int DataCounters::getAndIncrement(gl::EntryPoint entryPoint, const std::string &
bool FrameCapture::enabled() const bool FrameCapture::enabled() const
{ {
return mEnabled && mFrameIndex >= mFrameStart && mFrameIndex <= mFrameEnd; // 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 <= mFrameEnd;
} }
void FrameCapture::replay(gl::Context *context) void FrameCapture::replay(gl::Context *context)
{ {
ReplayContext replayContext(mReadBufferSize, mClientArraySizes); ReplayContext replayContext(mReadBufferSize, mClientArraySizes);
for (const CallCapture &call : mCalls) for (const CallCapture &call : mFrameCalls)
{ {
INFO() << "frame index: " << mFrameIndex << " " << call.name(); INFO() << "frame index: " << mFrameIndex << " " << call.name();
...@@ -1188,7 +1767,8 @@ void FrameCapture::replay(gl::Context *context) ...@@ -1188,7 +1767,8 @@ void FrameCapture::replay(gl::Context *context)
void FrameCapture::reset() void FrameCapture::reset()
{ {
mCalls.clear(); mFrameCalls.clear();
mSetupCalls.clear();
mClientVertexArrayMap.fill(-1); mClientVertexArrayMap.fill(-1);
// Do not reset replay-specific settings like the maximum read buffer size, client array sizes, // Do not reset replay-specific settings like the maximum read buffer size, client array sizes,
......
...@@ -163,7 +163,14 @@ class DataCounters final : angle::NonCopyable ...@@ -163,7 +163,14 @@ class DataCounters final : angle::NonCopyable
}; };
// Used by the CPP replay to filter out unnecessary code. // Used by the CPP replay to filter out unnecessary code.
using HasResourceTypeMap = angle::PackedEnumMap<ResourceIDType, bool, angle::kParamTypeCount>; using HasResourceTypeMap = angle::PackedEnumBitSet<ResourceIDType>;
// A dictionary of sources indexed by shader type.
using ProgramSources = gl::ShaderMap<std::string>;
// Maps from IDs to sources.
using ShaderSourceMap = std::map<gl::ShaderProgramID, std::string>;
using ProgramSourceMap = std::map<gl::ShaderProgramID, ProgramSources>;
class FrameCapture final : angle::NonCopyable class FrameCapture final : angle::NonCopyable
{ {
...@@ -183,20 +190,17 @@ class FrameCapture final : angle::NonCopyable ...@@ -183,20 +190,17 @@ class FrameCapture final : angle::NonCopyable
void reset(); void reset();
void maybeCaptureClientData(const gl::Context *context, const CallCapture &call); void maybeCaptureClientData(const gl::Context *context, const CallCapture &call);
void maybeUpdateResourceIDs(const gl::Context *context, const CallCapture &call);
template <typename IDType>
void captureUpdateResourceIDs(const gl::Context *context,
const CallCapture &call,
const ParamCapture &param);
static void ReplayCall(gl::Context *context, static void ReplayCall(gl::Context *context,
ReplayContext *replayContext, ReplayContext *replayContext,
const CallCapture &call); const CallCapture &call);
std::vector<CallCapture> mSetupCalls;
std::vector<CallCapture> mFrameCalls;
std::vector<CallCapture> mTearDownCalls;
bool mEnabled; bool mEnabled;
std::string mOutDirectory; std::string mOutDirectory;
std::vector<CallCapture> mCalls;
gl::AttribArray<int> mClientVertexArrayMap; gl::AttribArray<int> mClientVertexArrayMap;
uint32_t mFrameIndex; uint32_t mFrameIndex;
uint32_t mFrameStart; uint32_t mFrameStart;
...@@ -204,6 +208,10 @@ class FrameCapture final : angle::NonCopyable ...@@ -204,6 +208,10 @@ class FrameCapture final : angle::NonCopyable
gl::AttribArray<size_t> mClientArraySizes; gl::AttribArray<size_t> mClientArraySizes;
size_t mReadBufferSize; size_t mReadBufferSize;
HasResourceTypeMap mHasResourceType; HasResourceTypeMap mHasResourceType;
// Cache most recently compiled and linked sources.
ShaderSourceMap mCachedShaderSources;
ProgramSourceMap mCachedProgramSources;
}; };
template <typename CaptureFuncT, typename... ArgsT> template <typename CaptureFuncT, typename... ArgsT>
......
...@@ -219,6 +219,11 @@ class Framebuffer final : public angle::ObserverInterface, ...@@ -219,6 +219,11 @@ class Framebuffer final : public angle::ObserverInterface,
const FramebufferAttachment *getFirstColorAttachment() const; const FramebufferAttachment *getFirstColorAttachment() const;
const FramebufferAttachment *getFirstNonNullAttachment() const; const FramebufferAttachment *getFirstNonNullAttachment() const;
const std::vector<FramebufferAttachment> &getColorAttachments() const
{
return mState.mColorAttachments;
}
const FramebufferAttachment *getAttachment(const Context *context, GLenum attachment) const; const FramebufferAttachment *getAttachment(const Context *context, GLenum attachment) const;
bool isMultiview() const; bool isMultiview() const;
bool readDisallowedByMultiview() const; bool readDisallowedByMultiview() const;
......
...@@ -72,6 +72,12 @@ class TypedResourceManager : public ResourceManagerBase<HandleAllocatorType> ...@@ -72,6 +72,12 @@ class TypedResourceManager : public ResourceManagerBase<HandleAllocatorType>
return GetIDValue(handle) == 0 || mObjectMap.contains(handle); return GetIDValue(handle) == 0 || mObjectMap.contains(handle);
} }
typename ResourceMap<ResourceType, IDType>::Iterator begin() const
{
return mObjectMap.begin();
}
typename ResourceMap<ResourceType, IDType>::Iterator end() const { return mObjectMap.end(); }
protected: protected:
~TypedResourceManager() override; ~TypedResourceManager() override;
...@@ -155,6 +161,10 @@ class ShaderProgramManager : public ResourceManagerBase<HandleAllocator> ...@@ -155,6 +161,10 @@ class ShaderProgramManager : public ResourceManagerBase<HandleAllocator>
return mPrograms.query(handle); return mPrograms.query(handle);
} }
// For capture only.
const ResourceMap<Shader, ShaderProgramID> &getShadersForCapture() const { return mShaders; }
const ResourceMap<Program, ShaderProgramID> &getProgramsForCapture() const { return mPrograms; }
protected: protected:
~ShaderProgramManager() override; ~ShaderProgramManager() override;
......
...@@ -56,6 +56,12 @@ static constexpr Version ES_3_2 = Version(3, 2); ...@@ -56,6 +56,12 @@ static constexpr Version ES_3_2 = Version(3, 2);
using ContextID = uintptr_t; using ContextID = uintptr_t;
template <typename T>
using BufferBindingMap = angle::PackedEnumMap<BufferBinding, T>;
using BoundBufferMap = BufferBindingMap<BindingPointer<Buffer>>;
using TextureBindingVector = std::vector<BindingPointer<Texture>>;
using TextureBindingMap = angle::PackedEnumMap<TextureType, TextureBindingVector>;
class State : angle::NonCopyable class State : angle::NonCopyable
{ {
public: public:
...@@ -687,6 +693,24 @@ class State : angle::NonCopyable ...@@ -687,6 +693,24 @@ class State : angle::NonCopyable
const OverlayType *getOverlay() const { return mOverlay; } const OverlayType *getOverlay() const { return mOverlay; }
// Not for general use.
const BufferManager &getBufferManagerForCapture() const { return *mBufferManager; }
const BoundBufferMap &getBoundBuffersForCapture() const { return mBoundBuffers; }
const TextureManager &getTextureManagerForCapture() const { return *mTextureManager; }
const TextureBindingMap &getBoundTexturesForCapture() const { return mSamplerTextures; }
const RenderbufferManager &getRenderbufferManagerForCapture() const
{
return *mRenderbufferManager;
}
const FramebufferManager &getFramebufferManagerForCapture() const
{
return *mFramebufferManager;
}
const ShaderProgramManager &getShaderProgramManagerForCapture() const
{
return *mShaderProgramManager;
}
private: private:
friend class Context; friend class Context;
...@@ -818,8 +842,6 @@ class State : angle::NonCopyable ...@@ -818,8 +842,6 @@ class State : angle::NonCopyable
// Texture and sampler bindings // Texture and sampler bindings
size_t mActiveSampler; // Active texture unit selector - GL_TEXTURE0 size_t mActiveSampler; // Active texture unit selector - GL_TEXTURE0
using TextureBindingVector = std::vector<BindingPointer<Texture>>;
using TextureBindingMap = angle::PackedEnumMap<TextureType, TextureBindingVector>;
TextureBindingMap mSamplerTextures; TextureBindingMap mSamplerTextures;
// Texture Completeness Caching // Texture Completeness Caching
...@@ -852,7 +874,6 @@ class State : angle::NonCopyable ...@@ -852,7 +874,6 @@ class State : angle::NonCopyable
// Stores the currently bound buffer for each binding point. It has an entry for the element // Stores the currently bound buffer for each binding point. It has an entry for the element
// array buffer but it should not be used. Instead this bind point is owned by the current // array buffer but it should not be used. Instead this bind point is owned by the current
// vertex array object. // vertex array object.
using BoundBufferMap = angle::PackedEnumMap<BufferBinding, BindingPointer<Buffer>>;
BoundBufferMap mBoundBuffers; BoundBufferMap mBoundBuffers;
using BufferVector = std::vector<OffsetBindingPointer<Buffer>>; using BufferVector = std::vector<OffsetBindingPointer<Buffer>>;
......
...@@ -462,7 +462,9 @@ using ContextID = uintptr_t; ...@@ -462,7 +462,9 @@ using ContextID = uintptr_t;
constexpr size_t kCubeFaceCount = 6; constexpr size_t kCubeFaceCount = 6;
using TextureMap = angle::PackedEnumMap<TextureType, BindingPointer<Texture>>; template <typename T>
using TextureTypeMap = angle::PackedEnumMap<TextureType, T>;
using TextureMap = TextureTypeMap<BindingPointer<Texture>>;
// ShaderVector can contain one item per shader. It differs from ShaderMap in that the values are // ShaderVector can contain one item per shader. It differs from ShaderMap in that the values are
// not indexed by ShaderType. // not indexed by ShaderType.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment