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);
......
...@@ -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