From 06ce17e039776ef975b85424ffa0d0eb6ee8fb53 Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Mon, 27 Apr 2020 09:05:54 -0600 Subject: [PATCH] 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 Reviewed-by: Jamie Madill --- samples/capture_replay/CaptureReplay.cpp | 12 +- src/libANGLE/FrameCapture.cpp | 215 ++++++++++++++++++++++- src/libANGLE/FrameCapture.h | 41 ++++- src/libANGLE/FrameCapture_mock.cpp | 2 + 4 files changed, 265 insertions(+), 5 deletions(-) diff --git a/samples/capture_replay/CaptureReplay.cpp b/samples/capture_replay/CaptureReplay.cpp index a38c23be8..cd12e4511 100644 --- a/samples/capture_replay/CaptureReplay.cpp +++ b/samples/capture_replay/CaptureReplay.cpp @@ -27,6 +27,9 @@ std::function SetupContextReplay = reinterpret_cast( std::function ReplayContextFrame = reinterpret_cast( ANGLE_MACRO_CONCAT(ReplayContext, ANGLE_MACRO_CONCAT(ANGLE_CAPTURE_REPLAY_SAMPLE_CONTEXT_ID, Frame))); +std::function ResetContextReplay = reinterpret_cast( + 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) diff --git a/src/libANGLE/FrameCapture.cpp b/src/libANGLE/FrameCapture.cpp index 644536b7c..bd0a5138a 100644 --- a/src/libANGLE/FrameCapture.cpp +++ b/src/libANGLE/FrameCapture.cpp @@ -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 *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 &frameCalls, const std::vector &setupCalls, + ResourceTracker *resourceTracker, std::vector *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(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()) + { + 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(contextId) << "Replay();\n"; header << "void ReplayContext" << static_cast(contextId) << "Frame(uint32_t frameIndex);\n"; + header << "void ResetContext" << static_cast(contextId) << "Replay();\n"; header << "\n"; header << "using FramebufferChangeCallback = void(*)(void *userData, GLenum target, GLuint " "framebuffer);\n"; @@ -1752,8 +1846,39 @@ void CaptureTextureContents(std::vector *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(buffer->getSize()), buffer->getMapPointer(), + buffer->getUsage())); +} + void CaptureMidExecutionSetup(const gl::Context *context, std::vector *setupCalls, + ResourceTracker *resourceTracker, const ShaderSourceMap &cachedShaderSources, const ProgramSourceMap &cachedProgramSources, const TextureLevelDataMap &cachedTextureLevelData) @@ -1807,6 +1932,9 @@ void CaptureMidExecutionSetup(const gl::Context *context, static_cast(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 diff --git a/src/libANGLE/FrameCapture.h b/src/libANGLE/FrameCapture.h index 511d24c00..5c6b7b4ea 100644 --- a/src/libANGLE/FrameCapture.h +++ b/src/libANGLE/FrameCapture.h @@ -177,6 +177,44 @@ class DataCounters final : angle::NonCopyable std::map mData; }; +using BufferSet = std::set; +using BufferCalls = std::map>; + +// 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; @@ -225,7 +263,6 @@ class FrameCapture final : angle::NonCopyable std::vector mSetupCalls; std::vector mFrameCalls; - std::vector 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; diff --git a/src/libANGLE/FrameCapture_mock.cpp b/src/libANGLE/FrameCapture_mock.cpp index b0ff3a84e..39f6839e8 100644 --- a/src/libANGLE/FrameCapture_mock.cpp +++ b/src/libANGLE/FrameCapture_mock.cpp @@ -18,6 +18,8 @@ namespace angle CallCapture::~CallCapture() {} ParamBuffer::~ParamBuffer() {} ParamCapture::~ParamCapture() {} +ResourceTracker::ResourceTracker() {} +ResourceTracker::~ResourceTracker() {} FrameCapture::FrameCapture() {} FrameCapture::~FrameCapture() {}