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.
Limitations:
* Many OpenGL ES functions are not yet implemented.
* GLES capture has many unimplemented functions.
* EGL capture and replay is not yet supported.
* Mid-execution is not yet implemented.
* Capture only tested on desktop platforms currently.
* Replays currently are implemented as CPP files.
* Mid-execution capture is supported with the Vulkan back-end.
* Mid-execution capture has many unimplemented features.
* Capture and replay is currently only tested on desktop platforms.
* Binary replay is unimplemented. CPP replay is supported.
## Capturing and replaying an application
......@@ -18,23 +19,28 @@ To build ANGLE with capture and replay enabled update your GN args:
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
`angle_capture_context{id}_frame{n}.cpp`. ANGLE will additionally write out data binary blobs for
Texture or Buffer contexts to `angle_capture_context{id}_frame{n}.angledata`.
`angle_capture_context{id}_frame{n}.cpp`. Each GL Context currently has its own replay sources.
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
Some simple environment variables control frame capture:
* `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>`:
Can specify an alternate replay output directory than the CWD.
Example: `ANGLE_CAPTURE_OUT_DIR=samples/capture_replay`
* Can specify an alternate replay output directory.
* 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>`:
By default ANGLE will capture the first ten frames. This variable can override the default.
Example: `ANGLE_CAPTURE_FRAME_END=4`
* By default ANGLE will capture the first ten frames. This variable can override the default.
* 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
template. For example:
......@@ -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
[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 = [
"capture_replay/angle_capture_context1_frame000.cpp",
"capture_replay/angle_capture_context1_frame001.cpp",
......@@ -64,6 +71,8 @@ capture_replay("my_sample") {
Then build and run your replay sample:
```
$ autoninja -C out/Debug my_sample
$ ANGLE_CAPTURE_ENABLED=0 out/Debug/my_sample
$ autoninja -C out/Debug capture_replay
$ 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") {
]
}
template("capture_replay") {
template("angle_capture_replay_sample") {
angle_sample(target_name) {
sources = invoker.sources + [
"capture_replay/CaptureReplay.cpp",
......
......@@ -149,6 +149,9 @@ class PackedEnumMap
T *data() 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:
Storage mPrivateData;
};
......@@ -500,6 +503,13 @@ typename std::enable_if<IsResourceIDType<T>::value, bool>::type operator!=(const
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.
template <typename ResourceIDType>
GLuint GetIDValue(ResourceIDType id);
......
......@@ -163,7 +163,14 @@ class DataCounters final : angle::NonCopyable
};
// 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
{
......@@ -183,20 +190,17 @@ class FrameCapture final : angle::NonCopyable
void reset();
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,
ReplayContext *replayContext,
const CallCapture &call);
std::vector<CallCapture> mSetupCalls;
std::vector<CallCapture> mFrameCalls;
std::vector<CallCapture> mTearDownCalls;
bool mEnabled;
std::string mOutDirectory;
std::vector<CallCapture> mCalls;
gl::AttribArray<int> mClientVertexArrayMap;
uint32_t mFrameIndex;
uint32_t mFrameStart;
......@@ -204,6 +208,10 @@ class FrameCapture final : angle::NonCopyable
gl::AttribArray<size_t> mClientArraySizes;
size_t mReadBufferSize;
HasResourceTypeMap mHasResourceType;
// Cache most recently compiled and linked sources.
ShaderSourceMap mCachedShaderSources;
ProgramSourceMap mCachedProgramSources;
};
template <typename CaptureFuncT, typename... ArgsT>
......
......@@ -219,6 +219,11 @@ class Framebuffer final : public angle::ObserverInterface,
const FramebufferAttachment *getFirstColorAttachment() const;
const FramebufferAttachment *getFirstNonNullAttachment() const;
const std::vector<FramebufferAttachment> &getColorAttachments() const
{
return mState.mColorAttachments;
}
const FramebufferAttachment *getAttachment(const Context *context, GLenum attachment) const;
bool isMultiview() const;
bool readDisallowedByMultiview() const;
......
......@@ -72,6 +72,12 @@ class TypedResourceManager : public ResourceManagerBase<HandleAllocatorType>
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:
~TypedResourceManager() override;
......@@ -155,6 +161,10 @@ class ShaderProgramManager : public ResourceManagerBase<HandleAllocator>
return mPrograms.query(handle);
}
// For capture only.
const ResourceMap<Shader, ShaderProgramID> &getShadersForCapture() const { return mShaders; }
const ResourceMap<Program, ShaderProgramID> &getProgramsForCapture() const { return mPrograms; }
protected:
~ShaderProgramManager() override;
......
......@@ -56,6 +56,12 @@ static constexpr Version ES_3_2 = Version(3, 2);
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
{
public:
......@@ -687,6 +693,24 @@ class State : angle::NonCopyable
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:
friend class Context;
......@@ -818,8 +842,6 @@ class State : angle::NonCopyable
// Texture and sampler bindings
size_t mActiveSampler; // Active texture unit selector - GL_TEXTURE0
using TextureBindingVector = std::vector<BindingPointer<Texture>>;
using TextureBindingMap = angle::PackedEnumMap<TextureType, TextureBindingVector>;
TextureBindingMap mSamplerTextures;
// Texture Completeness Caching
......@@ -852,7 +874,6 @@ class State : angle::NonCopyable
// 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
// vertex array object.
using BoundBufferMap = angle::PackedEnumMap<BufferBinding, BindingPointer<Buffer>>;
BoundBufferMap mBoundBuffers;
using BufferVector = std::vector<OffsetBindingPointer<Buffer>>;
......
......@@ -462,7 +462,9 @@ using ContextID = uintptr_t;
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
// 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