Capture/Replay: Reset buffers on replay loop

This CL adds infrastructure for tracking whether resources need to be
reset when looping back to the beginning of the frame sequence.

A new function is generated on the last frame: ResetContext*Replay().
It will contain calls to gen, delete, and restore contents of
resources. This CL only supports Buffer resets.

Bug: b/152512564
Bug: angleproject:3662
Bug: angleproject:4599
Change-Id: I46672dd70dcb997967e3cc0897308144f2582e21
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2168121
Commit-Queue: Cody Northrop <cnorthrop@google.com>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
This commit is contained in:
Cody Northrop 2020-04-27 09:05:54 -06:00 коммит произвёл Commit Bot
Родитель c02cdff744
Коммит 06ce17e039
4 изменённых файлов: 265 добавлений и 5 удалений

Просмотреть файл

@ -27,6 +27,9 @@ std::function<void()> SetupContextReplay = reinterpret_cast<void (*)()>(
std::function<void(int)> ReplayContextFrame = reinterpret_cast<void (*)(int)>(
ANGLE_MACRO_CONCAT(ReplayContext,
ANGLE_MACRO_CONCAT(ANGLE_CAPTURE_REPLAY_SAMPLE_CONTEXT_ID, Frame)));
std::function<void()> ResetContextReplay = reinterpret_cast<void (*)()>(
ANGLE_MACRO_CONCAT(ResetContext,
ANGLE_MACRO_CONCAT(ANGLE_CAPTURE_REPLAY_SAMPLE_CONTEXT_ID, Replay)));
class CaptureReplaySample : public SampleApplication
{
@ -59,12 +62,19 @@ class CaptureReplaySample : public SampleApplication
// Compute the current frame, looping from kReplayFrameStart to kReplayFrameEnd.
uint32_t frame =
kReplayFrameStart + (mCurrentFrame % (kReplayFrameEnd - kReplayFrameStart));
if (mPreviousFrame > frame)
{
ResetContextReplay();
}
ReplayContextFrame(frame);
mPreviousFrame = frame;
mCurrentFrame++;
}
private:
uint32_t mCurrentFrame = 0;
uint32_t mCurrentFrame = 0;
uint32_t mPreviousFrame = 0;
};
int main(int argc, char **argv)

Просмотреть файл

@ -734,13 +734,84 @@ void WriteLoadBinaryDataCall(bool compression,
out << " LoadBinaryData(\"" << binaryDataFileName << "\");\n";
}
void MaybeResetResources(std::stringstream &out,
ResourceIDType resourceIDType,
DataCounters *counters,
std::stringstream &header,
ResourceTracker *resourceTracker,
std::vector<uint8_t> *binaryData)
{
switch (resourceIDType)
{
case ResourceIDType::Buffer:
{
BufferSet &newBuffers = resourceTracker->getNewBuffers();
BufferCalls &bufferRegenCalls = resourceTracker->getBufferRegenCalls();
BufferCalls &bufferRestoreCalls = resourceTracker->getBufferRestoreCalls();
// If we have any new buffers generated and not deleted during the run, delete them now
if (!newBuffers.empty())
{
out << " const GLuint deleteBuffers[] = {";
BufferSet::iterator bufferIter = newBuffers.begin();
for (size_t i = 0; bufferIter != newBuffers.end(); ++i, ++bufferIter)
{
if (i > 0)
{
out << ", ";
}
if ((i % 4) == 0)
{
out << "\n ";
}
out << "gBufferMap[" << (*bufferIter).value << "]";
}
out << "};\n";
out << " glDeleteBuffers(" << newBuffers.size() << ", deleteBuffers);\n";
}
// If any of our starting buffers were deleted during the run, recreate them
BufferSet &buffersToRegen = resourceTracker->getBuffersToRegen();
for (const gl::BufferID id : buffersToRegen)
{
// Emit their regen calls
for (CallCapture &call : bufferRegenCalls[id])
{
out << " ";
WriteCppReplayForCall(call, counters, out, header, binaryData);
out << ";\n";
}
}
// If any of our starting buffers were modified during the run, restore their contents
BufferSet &buffersToRestore = resourceTracker->getBuffersToRestore();
for (const gl::BufferID id : buffersToRestore)
{
// Emit their restore calls
for (CallCapture &call : bufferRestoreCalls[id])
{
out << " ";
WriteCppReplayForCall(call, counters, out, header, binaryData);
out << ";\n";
}
}
break;
}
default:
// TODO (http://anglebug.com/4599): Reset more than just buffers
break;
}
}
void WriteCppReplay(bool compression,
const std::string &outDir,
gl::ContextID contextId,
const std::string &captureLabel,
uint32_t frameIndex,
uint32_t frameEnd,
const std::vector<CallCapture> &frameCalls,
const std::vector<CallCapture> &setupCalls,
ResourceTracker *resourceTracker,
std::vector<uint8_t> *binaryData)
{
DataCounters counters;
@ -782,6 +853,28 @@ void WriteCppReplay(bool compression,
out << "\n";
}
if (frameIndex == frameEnd)
{
// Emit code to reset back to starting state
out << "void ResetContext" << Str(static_cast<int>(contextId)) << "Replay()\n";
out << "{\n";
std::stringstream restoreCallStream;
// For now, we only reset buffer states
// TODO (http://anglebug.com/4599): Reset more state on frame loop
for (ResourceIDType resourceType : AllEnums<ResourceIDType>())
{
MaybeResetResources(restoreCallStream, resourceType, &counters, header, resourceTracker,
binaryData);
}
out << restoreCallStream.str();
out << "}\n";
out << "\n";
}
out << "void " << FmtReplayFunction(contextId, frameIndex) << "\n";
out << "{\n";
@ -869,6 +962,7 @@ void WriteCppReplayIndexFiles(bool compression,
header << "void SetupContext" << static_cast<int>(contextId) << "Replay();\n";
header << "void ReplayContext" << static_cast<int>(contextId)
<< "Frame(uint32_t frameIndex);\n";
header << "void ResetContext" << static_cast<int>(contextId) << "Replay();\n";
header << "\n";
header << "using FramebufferChangeCallback = void(*)(void *userData, GLenum target, GLuint "
"framebuffer);\n";
@ -1752,8 +1846,39 @@ void CaptureTextureContents(std::vector<CallCapture> *setupCalls,
}
}
// 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::State &replayState,
ResourceTracker *resourceTracker,
gl::BufferID *id,
const gl::Buffer *buffer)
{
// Track this as a starting resource that may need to be restored.
BufferSet &startingBuffers = resourceTracker->getStartingBuffers();
startingBuffers.insert(*id);
// Track calls to regenerate a given buffer
BufferCalls &bufferRegenCalls = resourceTracker->getBufferRegenCalls();
Capture(&bufferRegenCalls[*id], CaptureDeleteBuffers(replayState, true, 1, id));
Capture(&bufferRegenCalls[*id], CaptureGenBuffers(replayState, true, 1, id));
MaybeCaptureUpdateResourceIDs(&bufferRegenCalls[*id]);
// Track calls to restore a given buffer's contents
BufferCalls &bufferRestoreCalls = resourceTracker->getBufferRestoreCalls();
Capture(&bufferRestoreCalls[*id],
CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, *id));
Capture(&bufferRestoreCalls[*id],
CaptureBufferData(replayState, true, gl::BufferBinding::Array,
static_cast<GLsizeiptr>(buffer->getSize()), buffer->getMapPointer(),
buffer->getUsage()));
}
void CaptureMidExecutionSetup(const gl::Context *context,
std::vector<CallCapture> *setupCalls,
ResourceTracker *resourceTracker,
const ShaderSourceMap &cachedShaderSources,
const ProgramSourceMap &cachedProgramSources,
const TextureLevelDataMap &cachedTextureLevelData)
@ -1807,6 +1932,9 @@ void CaptureMidExecutionSetup(const gl::Context *context,
static_cast<GLsizeiptr>(buffer->getSize()), buffer->getMapPointer(),
buffer->getUsage()));
// Generate the calls needed to restore this buffer to original state for frame looping
CaptureBufferResetCalls(replayState, resourceTracker, &id, buffer);
GLboolean dontCare;
(void)buffer->unmap(context, &dontCare);
}
@ -3229,6 +3357,28 @@ void FrameCapture::maybeCaptureClientData(const gl::Context *context, CallCaptur
{
mBufferDataMap.erase(bufferDataInfo);
}
// If we're capturing, track what new buffers have been genned
if (mFrameIndex >= mFrameStart)
{
mResourceTracker.setDeletedBuffer(bufferIDs[i]);
}
}
break;
}
case gl::EntryPoint::GenBuffers:
{
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++)
{
// If we're capturing, track what new buffers have been genned
if (mFrameIndex >= mFrameStart)
{
mResourceTracker.setGennedBuffer(bufferIDs[i]);
}
}
break;
}
@ -3391,6 +3541,9 @@ void FrameCapture::maybeCaptureClientData(const gl::Context *context, CallCaptur
// Track the bufferID that was just mapped
call.params.setMappedBufferID(buffer->id());
// Remember that it was mapped writable, for use during state reset
mResourceTracker.setBufferModified(buffer->id());
}
break;
}
@ -3403,6 +3556,20 @@ void FrameCapture::maybeCaptureClientData(const gl::Context *context, CallCaptur
break;
}
case gl::EntryPoint::BufferData:
case gl::EntryPoint::BufferSubData:
{
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.setBufferModified(buffer->id());
break;
}
default:
break;
}
@ -3574,7 +3741,7 @@ void FrameCapture::onEndFrame(const gl::Context *context)
if (!mFrameCalls.empty() && mFrameIndex >= mFrameStart)
{
WriteCppReplay(mCompression, mOutDirectory, context->id(), mCaptureLabel, mFrameIndex,
mFrameCalls, mSetupCalls, &mBinaryData);
mFrameEnd, mFrameCalls, mSetupCalls, &mResourceTracker, &mBinaryData);
// Save the index files after the last frame.
if (mFrameIndex == mFrameEnd)
@ -3612,8 +3779,8 @@ void FrameCapture::onEndFrame(const gl::Context *context)
if (enabled() && mFrameIndex == mFrameStart)
{
mSetupCalls.clear();
CaptureMidExecutionSetup(context, &mSetupCalls, mCachedShaderSources, mCachedProgramSources,
mCachedTextureLevelData);
CaptureMidExecutionSetup(context, &mSetupCalls, &mResourceTracker, mCachedShaderSources,
mCachedProgramSources, mCachedTextureLevelData);
}
}
@ -3627,6 +3794,48 @@ int DataCounters::getAndIncrement(gl::EntryPoint entryPoint, const std::string &
return mData[counterKey]++;
}
ResourceTracker::ResourceTracker() = default;
ResourceTracker::~ResourceTracker() = default;
void ResourceTracker::setDeletedBuffer(gl::BufferID id)
{
if (mNewBuffers.find(id) != mNewBuffers.end())
{
// This is a buffer genned after MEC was initialized, just clear it, since there will be no
// actions required for it to return to starting state.
mNewBuffers.erase(id);
return;
}
// Ensure this buffer was in our starting set
// It's possible this could fire if the app deletes buffers that were never generated
ASSERT(mStartingBuffers.find(id) != mStartingBuffers.end());
// In this case, the app is deleting a buffer we started with, we need to regen on loop
mBuffersToRegen.insert(id);
mBuffersToRestore.insert(id);
}
void ResourceTracker::setGennedBuffer(gl::BufferID id)
{
if (mStartingBuffers.find(id) == mStartingBuffers.end())
{
// This is a buffer genned after MEC was initialized, track it
mNewBuffers.insert(id);
return;
}
}
void ResourceTracker::setBufferModified(gl::BufferID id)
{
// If this was a starting buffer, we need to track it for restore
if (mStartingBuffers.find(id) != mStartingBuffers.end())
{
mBuffersToRestore.insert(id);
}
}
bool FrameCapture::isCapturing() const
{
// Currently we will always do a capture up until the last frame. In the future we could improve

Просмотреть файл

@ -177,6 +177,44 @@ class DataCounters final : angle::NonCopyable
std::map<Counter, int> mData;
};
using BufferSet = std::set<gl::BufferID>;
using BufferCalls = std::map<gl::BufferID, std::vector<CallCapture>>;
// Helper to track resource changes during the capture
class ResourceTracker final : angle::NonCopyable
{
public:
ResourceTracker();
~ResourceTracker();
BufferCalls &getBufferRegenCalls() { return mBufferRegenCalls; }
BufferCalls &getBufferRestoreCalls() { return mBufferRestoreCalls; }
BufferSet &getStartingBuffers() { return mStartingBuffers; }
BufferSet &getNewBuffers() { return mNewBuffers; }
BufferSet &getBuffersToRegen() { return mBuffersToRegen; }
BufferSet &getBuffersToRestore() { return mBuffersToRestore; }
void setGennedBuffer(gl::BufferID id);
void setDeletedBuffer(gl::BufferID id);
void setBufferModified(gl::BufferID id);
private:
// Buffer regen calls will delete and gen a buffer
BufferCalls mBufferRegenCalls;
// Buffer restore calls will restore the contents of a buffer
BufferCalls mBufferRestoreCalls;
// Starting buffers include all the buffers created during setup for MEC
BufferSet mStartingBuffers;
// New buffers are those generated while capturing
BufferSet mNewBuffers;
// Buffers to regen are a list of starting buffers that need to be deleted and genned
BufferSet mBuffersToRegen;
// Buffers to restore include any starting buffers with contents modified during the run
BufferSet mBuffersToRestore;
};
// Used by the CPP replay to filter out unnecessary code.
using HasResourceTypeMap = angle::PackedEnumBitSet<ResourceIDType>;
@ -225,7 +263,6 @@ class FrameCapture final : angle::NonCopyable
std::vector<CallCapture> mSetupCalls;
std::vector<CallCapture> mFrameCalls;
std::vector<CallCapture> mTearDownCalls;
// We save one large buffer of binary data for the whole CPP replay.
// This simplifies a lot of file management.
@ -244,6 +281,8 @@ class FrameCapture final : angle::NonCopyable
HasResourceTypeMap mHasResourceType;
BufferDataMap mBufferDataMap;
ResourceTracker mResourceTracker;
// Cache most recently compiled and linked sources.
ShaderSourceMap mCachedShaderSources;
ProgramSourceMap mCachedProgramSources;

Просмотреть файл

@ -18,6 +18,8 @@ namespace angle
CallCapture::~CallCapture() {}
ParamBuffer::~ParamBuffer() {}
ParamCapture::~ParamCapture() {}
ResourceTracker::ResourceTracker() {}
ResourceTracker::~ResourceTracker() {}
FrameCapture::FrameCapture() {}
FrameCapture::~FrameCapture() {}