// Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // FrameCapture.h: // ANGLE Frame capture inteface. // #ifndef LIBANGLE_FRAME_CAPTURE_H_ #define LIBANGLE_FRAME_CAPTURE_H_ #include "common/PackedEnums.h" #include "libANGLE/Context.h" #include "libANGLE/angletypes.h" #include "libANGLE/capture/frame_capture_utils_autogen.h" #include "libANGLE/entry_points_utils.h" namespace gl { enum class GLenumGroup; } namespace angle { using ParamData = std::vector>; struct ParamCapture : angle::NonCopyable { ParamCapture(); ParamCapture(const char *nameIn, ParamType typeIn); ~ParamCapture(); ParamCapture(ParamCapture &&other); ParamCapture &operator=(ParamCapture &&other); std::string name; ParamType type; ParamValue value; gl::GLenumGroup enumGroup; // only used for param type GLenum, GLboolean and GLbitfield ParamData data; int arrayClientPointerIndex = -1; size_t readBufferSizeBytes = 0; }; class ParamBuffer final : angle::NonCopyable { public: ParamBuffer(); ~ParamBuffer(); ParamBuffer(ParamBuffer &&other); ParamBuffer &operator=(ParamBuffer &&other); template void addValueParam(const char *paramName, ParamType paramType, T paramValue); template void addEnumParam(const char *paramName, gl::GLenumGroup enumGroup, ParamType paramType, T paramValue); ParamCapture &getParam(const char *paramName, ParamType paramType, int index); const ParamCapture &getParam(const char *paramName, ParamType paramType, int index) const; ParamCapture &getParamFlexName(const char *paramName1, const char *paramName2, ParamType paramType, int index); const ParamCapture &getParamFlexName(const char *paramName1, const char *paramName2, ParamType paramType, int index) const; const ParamCapture &getReturnValue() const { return mReturnValueCapture; } void addParam(ParamCapture &¶m); void addReturnValue(ParamCapture &&returnValue); bool hasClientArrayData() const { return mClientArrayDataParam != -1; } ParamCapture &getClientArrayPointerParameter(); size_t getReadBufferSize() const { return mReadBufferSize; } const std::vector &getParamCaptures() const { return mParamCaptures; } // These helpers allow us to track the ID of the buffer that was active when // MapBufferRange was called. We'll use it during replay to track the // buffer's contents, as they can be modified by the host. void setMappedBufferID(gl::BufferID bufferID) { mMappedBufferID = bufferID; } gl::BufferID getMappedBufferID() const { return mMappedBufferID; } private: std::vector mParamCaptures; ParamCapture mReturnValueCapture; int mClientArrayDataParam = -1; size_t mReadBufferSize = 0; gl::BufferID mMappedBufferID; }; struct CallCapture { CallCapture(EntryPoint entryPointIn, ParamBuffer &¶msIn); CallCapture(const std::string &customFunctionNameIn, ParamBuffer &¶msIn); ~CallCapture(); CallCapture(CallCapture &&other); CallCapture &operator=(CallCapture &&other); const char *name() const; EntryPoint entryPoint; std::string customFunctionName; ParamBuffer params; }; class ReplayContext { public: ReplayContext(size_t readBufferSizebytes, const gl::AttribArray &clientArraysSizebytes); ~ReplayContext(); template T getReadBufferPointer(const ParamCapture ¶m) { ASSERT(param.readBufferSizeBytes > 0); ASSERT(mReadBuffer.size() >= param.readBufferSizeBytes); return reinterpret_cast(mReadBuffer.data()); } template T getAsConstPointer(const ParamCapture ¶m) { if (param.arrayClientPointerIndex != -1) { return reinterpret_cast(mClientArraysBuffer[param.arrayClientPointerIndex].data()); } if (!param.data.empty()) { ASSERT(param.data.size() == 1); return reinterpret_cast(param.data[0].data()); } return nullptr; } template T getAsPointerConstPointer(const ParamCapture ¶m) { static_assert(sizeof(typename std::remove_pointer::type) == sizeof(uint8_t *), "pointer size not match!"); ASSERT(!param.data.empty()); mPointersBuffer.clear(); mPointersBuffer.reserve(param.data.size()); for (const std::vector &data : param.data) { mPointersBuffer.emplace_back(data.data()); } return reinterpret_cast(mPointersBuffer.data()); } gl::AttribArray> &getClientArraysBuffer() { return mClientArraysBuffer; } private: std::vector mReadBuffer; std::vector mPointersBuffer; gl::AttribArray> mClientArraysBuffer; }; // Helper to use unique IDs for each local data variable. class DataCounters final : angle::NonCopyable { public: DataCounters(); ~DataCounters(); int getAndIncrement(EntryPoint entryPoint, const std::string ¶mName); private: // using Counter = std::pair; std::map mData; }; constexpr int kStringsNotFound = -1; class StringCounters final : angle::NonCopyable { public: StringCounters(); ~StringCounters(); int getStringCounter(std::vector &str); void setStringCounter(std::vector &str, int &counter); private: std::map, int> mStringCounterMap; }; class DataTracker final : angle::NonCopyable { public: DataTracker(); ~DataTracker(); DataCounters &getCounters() { return mCounters; } StringCounters &getStringCounters() { return mStringCounters; } private: DataCounters mCounters; StringCounters mStringCounters; }; using BufferSet = std::set; using BufferCalls = std::map>; // true means mapped, false means unmapped using BufferMapStatusMap = std::map; using FenceSyncSet = std::set; using FenceSyncCalls = 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; } BufferCalls &getBufferMapCalls() { return mBufferMapCalls; } BufferCalls &getBufferUnmapCalls() { return mBufferUnmapCalls; } std::vector &getBufferBindingCalls() { return mBufferBindingCalls; } 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); void setBufferMapped(gl::BufferID id); void setBufferUnmapped(gl::BufferID id); const bool &getStartingBuffersMappedCurrent(gl::BufferID id) { return mStartingBuffersMappedCurrent[id]; } const bool &getStartingBuffersMappedInitial(gl::BufferID id) { return mStartingBuffersMappedInitial[id]; } void setStartingBufferMapped(gl::BufferID id, bool mapped) { // Track the current state (which will change throughout the trace) mStartingBuffersMappedCurrent[id] = mapped; // And the initial state, to compare during frame loop reset mStartingBuffersMappedInitial[id] = mapped; } void onShaderProgramAccess(gl::ShaderProgramID shaderProgramID); uint32_t getMaxShaderPrograms() const { return mMaxShaderPrograms; } FenceSyncSet &getStartingFenceSyncs() { return mStartingFenceSyncs; } FenceSyncCalls &getFenceSyncRegenCalls() { return mFenceSyncRegenCalls; } FenceSyncSet &getFenceSyncsToRegen() { return mFenceSyncsToRegen; } void setDeletedFenceSync(GLsync sync); private: // Buffer regen calls will delete and gen a buffer BufferCalls mBufferRegenCalls; // Buffer restore calls will restore the contents of a buffer BufferCalls mBufferRestoreCalls; // Buffer map calls will map a buffer with correct offset, length, and access flags BufferCalls mBufferMapCalls; // Buffer unmap calls will bind and unmap a given buffer BufferCalls mBufferUnmapCalls; // Buffer binding calls to restore bindings recorded during MEC std::vector mBufferBindingCalls; // 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; // Whether a given buffer was mapped at the start of the trace BufferMapStatusMap mStartingBuffersMappedInitial; // The status of buffer mapping throughout the trace, modified with each Map/Unmap call BufferMapStatusMap mStartingBuffersMappedCurrent; // Maximum accessed shader program ID. uint32_t mMaxShaderPrograms = 0; // Fence sync objects created during MEC setup FenceSyncSet mStartingFenceSyncs; // Fence sync regen calls will create a fence sync objects FenceSyncCalls mFenceSyncRegenCalls; // Fence syncs to regen are a list of starting fence sync objects that were deleted and need to // be regen'ed. FenceSyncSet mFenceSyncsToRegen; }; // Used by the CPP replay to filter out unnecessary code. using HasResourceTypeMap = angle::PackedEnumBitSet; // Map of buffer ID to offset and size used when mapped using BufferDataMap = std::map>; // A dictionary of sources indexed by shader type. using ProgramSources = gl::ShaderMap; // Maps from IDs to sources. using ShaderSourceMap = std::map; using ProgramSourceMap = std::map; // Map from textureID to level and data using TextureLevels = std::map>; using TextureLevelDataMap = std::map; // Map from ContextID to surface dimensions using SurfaceDimensions = std::map; class FrameCapture final : angle::NonCopyable { public: FrameCapture(); ~FrameCapture(); void captureCall(const gl::Context *context, CallCapture &&call, bool isCallValid); void checkForCaptureTrigger(); void onEndFrame(const gl::Context *context); void onDestroyContext(const gl::Context *context); void onMakeCurrent(const gl::Context *context, const egl::Surface *drawSurface); bool enabled() const { return mEnabled; } bool isCapturing() const; void replay(gl::Context *context); uint32_t getFrameCount() const; // Returns a frame index starting from "1" as the first frame. uint32_t getReplayFrameIndex() const; void trackBufferMapping(CallCapture *call, gl::BufferID id, GLintptr offset, GLsizeiptr length, bool writable); ResourceTracker &getResouceTracker() { return mResourceTracker; } private: void captureClientArraySnapshot(const gl::Context *context, size_t vertexCount, size_t instanceCount); void captureMappedBufferSnapshot(const gl::Context *context, const CallCapture &call); void copyCompressedTextureData(const gl::Context *context, const CallCapture &call); void captureCompressedTextureData(const gl::Context *context, const CallCapture &call); void reset(); void maybeOverrideEntryPoint(const gl::Context *context, CallCapture &call); void maybeCapturePreCallUpdates(const gl::Context *context, CallCapture &call); void maybeCapturePostCallUpdates(const gl::Context *context); void maybeCaptureDrawArraysClientData(const gl::Context *context, CallCapture &call, size_t instanceCount); void maybeCaptureDrawElementsClientData(const gl::Context *context, CallCapture &call, size_t instanceCount); static void ReplayCall(gl::Context *context, ReplayContext *replayContext, const CallCapture &call); void setCaptureActive() { mCaptureActive = true; } void setCaptureInactive() { mCaptureActive = false; } bool isCaptureActive() { return mCaptureActive; } std::vector mSetupCalls; std::vector mFrameCalls; // We save one large buffer of binary data for the whole CPP replay. // This simplifies a lot of file management. std::vector mBinaryData; bool mEnabled = false; bool mSerializeStateEnabled; std::string mOutDirectory; std::string mCaptureLabel; bool mCompression; gl::AttribArray mClientVertexArrayMap; uint32_t mFrameIndex; uint32_t mCaptureStartFrame; uint32_t mCaptureEndFrame; bool mIsFirstFrame = true; bool mWroteIndexFile = false; SurfaceDimensions mDrawSurfaceDimensions; gl::AttribArray mClientArraySizes; size_t mReadBufferSize; HasResourceTypeMap mHasResourceType; BufferDataMap mBufferDataMap; ResourceTracker mResourceTracker; // If you don't know which frame you want to start capturing at, use the capture trigger. // Initialize it to the number of frames you want to capture, and then clear the value to 0 when // you reach the content you want to capture. Currently only available on Android. uint32_t mCaptureTrigger; bool mCaptureActive = false; }; // Shared class for any items that need to be tracked by FrameCapture across shared contexts class FrameCaptureShared final : angle::NonCopyable { public: FrameCaptureShared(); ~FrameCaptureShared(); const std::string &getShaderSource(gl::ShaderProgramID id) const; void setShaderSource(gl::ShaderProgramID id, std::string sources); const ProgramSources &getProgramSources(gl::ShaderProgramID id) const; void setProgramSources(gl::ShaderProgramID id, ProgramSources sources); // Load data from a previously stored texture level const std::vector &retrieveCachedTextureLevel(gl::TextureID id, gl::TextureTarget target, GLint level); // Create new texture level data and copy the source into it void copyCachedTextureLevel(const gl::Context *context, gl::TextureID srcID, GLint srcLevel, gl::TextureID dstID, GLint dstLevel, const CallCapture &call); // Create the location that should be used to cache texture level data std::vector &getCachedTextureLevelData(gl::Texture *texture, gl::TextureTarget target, GLint level, EntryPoint entryPoint); // Remove any cached texture levels on deletion void deleteCachedTextureLevelData(gl::TextureID id); private: // Cache most recently compiled and linked sources. ShaderSourceMap mCachedShaderSource; ProgramSourceMap mCachedProgramSources; // Cache a shadow copy of texture level data TextureLevels mCachedTextureLevels; TextureLevelDataMap mCachedTextureLevelData; }; template void CaptureCallToFrameCapture(CaptureFuncT captureFunc, bool isCallValid, gl::Context *context, ArgsT... captureParams) { FrameCapture *frameCapture = context->getFrameCapture(); if (!frameCapture->isCapturing()) { return; } CallCapture call = captureFunc(context->getState(), isCallValid, captureParams...); frameCapture->captureCall(context, std::move(call), isCallValid); } template void ParamBuffer::addValueParam(const char *paramName, ParamType paramType, T paramValue) { ParamCapture capture(paramName, paramType); InitParamValue(paramType, paramValue, &capture.value); mParamCaptures.emplace_back(std::move(capture)); } template void ParamBuffer::addEnumParam(const char *paramName, gl::GLenumGroup enumGroup, ParamType paramType, T paramValue) { ParamCapture capture(paramName, paramType); InitParamValue(paramType, paramValue, &capture.value); capture.enumGroup = enumGroup; mParamCaptures.emplace_back(std::move(capture)); } std::ostream &operator<<(std::ostream &os, const ParamCapture &capture); // Pointer capture helpers. void CaptureMemory(const void *source, size_t size, ParamCapture *paramCapture); void CaptureString(const GLchar *str, ParamCapture *paramCapture); void CaptureStringLimit(const GLchar *str, uint32_t limit, ParamCapture *paramCapture); void CaptureVertexPointerGLES1(const gl::State &glState, gl::ClientVertexArrayType type, const void *pointer, ParamCapture *paramCapture); gl::Program *GetProgramForCapture(const gl::State &glState, gl::ShaderProgramID handle); // For GetIntegerv, GetFloatv, etc. void CaptureGetParameter(const gl::State &glState, GLenum pname, size_t typeSize, ParamCapture *paramCapture); void CaptureGetActiveUniformBlockivParameters(const gl::State &glState, gl::ShaderProgramID handle, gl::UniformBlockIndex uniformBlockIndex, GLenum pname, ParamCapture *paramCapture); template void CaptureClearBufferValue(GLenum buffer, const T *value, ParamCapture *paramCapture) { // Per the spec, color buffers have a vec4, the rest a single value uint32_t valueSize = (buffer == GL_COLOR) ? 4 : 1; CaptureMemory(value, valueSize * sizeof(T), paramCapture); } void CaptureGenHandlesImpl(GLsizei n, GLuint *handles, ParamCapture *paramCapture); template void CaptureGenHandles(GLsizei n, T *handles, ParamCapture *paramCapture) { CaptureGenHandlesImpl(n, reinterpret_cast(handles), paramCapture); } void CaptureShaderStrings(GLsizei count, const GLchar *const *strings, const GLint *length, ParamCapture *paramCapture); template void WriteParamValueReplay(std::ostream &os, const CallCapture &call, T value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, GLboolean value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, const void *value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, GLDEBUGPROCKHR value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, GLDEBUGPROC value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::BufferID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::FenceNVID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::FramebufferID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::MemoryObjectID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::ProgramPipelineID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::QueryID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::RenderbufferID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::SamplerID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::SemaphoreID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::ShaderProgramID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::TextureID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::TransformFeedbackID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::VertexArrayID value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::UniformLocation value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, gl::UniformBlockIndex value); template <> void WriteParamValueReplay(std::ostream &os, const CallCapture &call, GLsync value); // General fallback for any unspecific type. template void WriteParamValueReplay(std::ostream &os, const CallCapture &call, T value) { os << value; } } // namespace angle template void CaptureTextureAndSamplerParameter_params(GLenum pname, const T *param, angle::ParamCapture *paramCapture) { if (pname == GL_TEXTURE_BORDER_COLOR) { CaptureMemory(param, sizeof(T) * 4, paramCapture); } else { CaptureMemory(param, sizeof(T), paramCapture); } } #endif // LIBANGLE_FRAME_CAPTURE_H_