| // |
| // 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 interface. |
| // |
| |
| #ifndef LIBANGLE_FRAME_CAPTURE_H_ |
| #define LIBANGLE_FRAME_CAPTURE_H_ |
| |
| #include <fstream> |
| #include "sys/stat.h" |
| |
| #include "common/PackedEnums.h" |
| #include "common/SimpleMutex.h" |
| #include "common/frame_capture_utils.h" |
| #include "common/string_utils.h" |
| #include "common/system_utils.h" |
| #include "libANGLE/Context.h" |
| #include "libANGLE/ShareGroup.h" |
| #include "libANGLE/Thread.h" |
| #include "libANGLE/angletypes.h" |
| #include "libANGLE/entry_points_utils.h" |
| |
| #ifdef ANGLE_ENABLE_CL |
| # include "libANGLE/CLPlatform.h" |
| #endif |
| |
| namespace gl |
| { |
| enum class BigGLEnum; |
| enum class GLESEnum; |
| } // namespace gl |
| |
| namespace angle |
| { |
| // 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); |
| void reset() { mData.clear(); } |
| |
| private: |
| // <CallName, ParamName> |
| using Counter = std::pair<EntryPoint, std::string>; |
| std::map<Counter, int> mData; |
| }; |
| |
| constexpr int kStringsNotFound = -1; |
| class StringCounters final : angle::NonCopyable |
| { |
| public: |
| StringCounters(); |
| ~StringCounters(); |
| |
| int getStringCounter(const std::vector<std::string> &str); |
| void setStringCounter(const std::vector<std::string> &str, int &counter); |
| void reset() { mStringCounterMap.clear(); } |
| |
| private: |
| std::map<std::vector<std::string>, int> mStringCounterMap; |
| }; |
| |
| class DataTracker final : angle::NonCopyable |
| { |
| public: |
| DataTracker(); |
| ~DataTracker(); |
| |
| DataCounters &getCounters() { return mCounters; } |
| StringCounters &getStringCounters() { return mStringCounters; } |
| |
| void reset() |
| { |
| mCounters.reset(); |
| mStringCounters.reset(); |
| } |
| |
| private: |
| DataCounters mCounters; |
| StringCounters mStringCounters; |
| }; |
| |
| enum class CaptureAPI : uint8_t |
| { |
| GL = 0, |
| CL = 1, |
| |
| InvalidEnum = 2, |
| EnumCount = 2, |
| }; |
| |
| class ReplayWriter final : angle::NonCopyable |
| { |
| public: |
| ReplayWriter(); |
| ~ReplayWriter(); |
| |
| void setSourceFileExtension(const char *ext); |
| void setSourceFileSizeThreshold(size_t sourceFileSizeThreshold); |
| void setFilenamePattern(const std::string &pattern); |
| void setSourcePrologue(const std::string &prologue); |
| void setHeaderPrologue(const std::string &prologue); |
| |
| void addPublicFunction(const std::string &functionProto, |
| const std::stringstream &headerStream, |
| const std::stringstream &bodyStream); |
| void addPrivateFunction(const std::string &functionProto, |
| const std::stringstream &headerStream, |
| const std::stringstream &bodyStream); |
| std::string getInlineVariableName(EntryPoint entryPoint, const std::string ¶mName); |
| |
| std::string getInlineStringSetVariableName(EntryPoint entryPoint, |
| const std::string ¶mName, |
| const std::vector<std::string> &strings, |
| bool *isNewEntryOut); |
| |
| void addStaticVariable(const std::string &customVarType, const std::string &customVarName); |
| |
| void saveFrame(); |
| void saveFrameIfFull(); |
| void saveIndexFilesAndHeader(); |
| void saveSetupFile(); |
| |
| std::vector<std::string> getAndResetWrittenFiles(); |
| |
| void reset() |
| { |
| mDataTracker.reset(); |
| mFrameIndex = 1; // This is really the FileIndex |
| } |
| |
| CaptureAPI captureAPI = CaptureAPI::GL; |
| |
| private: |
| static std::string GetVarName(EntryPoint entryPoint, const std::string ¶mName, int counter); |
| |
| void saveHeader(); |
| void writeReplaySource(const std::string &filename); |
| void addWrittenFile(const std::string &filename); |
| size_t getStoredReplaySourceSize() const; |
| |
| std::string mSourceFileExtension; |
| size_t mSourceFileSizeThreshold; |
| size_t mFrameIndex; |
| |
| DataTracker mDataTracker; |
| std::string mFilenamePattern; |
| std::string mSourcePrologue; |
| std::string mHeaderPrologue; |
| |
| std::vector<std::string> mReplayHeaders; |
| std::vector<std::string> mGlobalVariableDeclarations; |
| std::vector<std::string> mStaticVariableDeclarations; |
| |
| std::vector<std::string> mPublicFunctionPrototypes; |
| std::vector<std::string> mPublicFunctions; |
| |
| std::vector<std::string> mPrivateFunctionPrototypes; |
| std::vector<std::string> mPrivateFunctions; |
| |
| std::vector<std::string> mWrittenFiles; |
| }; |
| |
| using BufferCalls = std::map<GLuint, std::vector<CallCapture>>; |
| |
| // true means mapped, false means unmapped |
| using BufferMapStatusMap = std::map<GLuint, bool>; |
| |
| using FenceSyncSet = std::set<gl::SyncID>; |
| using FenceSyncCalls = std::map<gl::SyncID, std::vector<CallCapture>>; |
| |
| // For default uniforms, we need to track which ones are dirty, and the series of calls to reset. |
| // Each program has unique default uniforms, and each uniform has one or more locations in the |
| // default buffer. For reset efficiency, we track only the uniforms dirty by location, per program. |
| |
| // A set of all default uniforms (per program) that were modified during the run |
| using DefaultUniformLocationsSet = std::set<gl::UniformLocation>; |
| using DefaultUniformLocationsPerProgramMap = |
| std::map<gl::ShaderProgramID, DefaultUniformLocationsSet>; |
| |
| // A map of programs which maps to locations and their reset calls |
| using DefaultUniformCallsPerLocationMap = std::map<gl::UniformLocation, std::vector<CallCapture>>; |
| using DefaultUniformCallsPerProgramMap = |
| std::map<gl::ShaderProgramID, DefaultUniformCallsPerLocationMap>; |
| |
| using DefaultUniformBaseLocationMap = |
| std::map<std::pair<gl::ShaderProgramID, gl::UniformLocation>, gl::UniformLocation>; |
| |
| using ResourceSet = std::set<GLuint>; |
| using ResourceCalls = std::map<GLuint, std::vector<CallCapture>>; |
| |
| class TrackedResource final : angle::NonCopyable |
| { |
| public: |
| TrackedResource(); |
| ~TrackedResource(); |
| |
| const ResourceSet &getStartingResources() const { return mStartingResources; } |
| ResourceSet &getStartingResources() { return mStartingResources; } |
| const ResourceSet &getNewResources() const { return mNewResources; } |
| ResourceSet &getNewResources() { return mNewResources; } |
| const ResourceSet &getResourcesToDelete() const { return mResourcesToDelete; } |
| ResourceSet &getResourcesToDelete() { return mResourcesToDelete; } |
| const ResourceSet &getResourcesToRegen() const { return mResourcesToRegen; } |
| ResourceSet &getResourcesToRegen() { return mResourcesToRegen; } |
| const ResourceSet &getResourcesToRestore() const { return mResourcesToRestore; } |
| ResourceSet &getResourcesToRestore() { return mResourcesToRestore; } |
| |
| void setGennedResource(GLuint id); |
| void setDeletedResource(GLuint id); |
| void setModifiedResource(GLuint id); |
| bool resourceIsGenerated(GLuint id); |
| |
| ResourceCalls &getResourceRegenCalls() { return mResourceRegenCalls; } |
| ResourceCalls &getResourceRestoreCalls() { return mResourceRestoreCalls; } |
| |
| void reset() |
| { |
| mResourceRegenCalls.clear(); |
| mResourceRestoreCalls.clear(); |
| mStartingResources.clear(); |
| mNewResources.clear(); |
| mResourcesToDelete.clear(); |
| mResourcesToRegen.clear(); |
| mResourcesToRestore.clear(); |
| } |
| |
| private: |
| // Resource regen calls will gen a resource |
| ResourceCalls mResourceRegenCalls; |
| // Resource restore calls will restore the contents of a resource |
| ResourceCalls mResourceRestoreCalls; |
| |
| // Resources created during startup |
| ResourceSet mStartingResources; |
| |
| // Resources created during the run that need to be deleted |
| ResourceSet mNewResources; |
| // Resources recreated during the run that need to be deleted |
| ResourceSet mResourcesToDelete; |
| // Resources deleted during the run that need to be recreated |
| ResourceSet mResourcesToRegen; |
| // Resources modified during the run that need to be restored |
| ResourceSet mResourcesToRestore; |
| }; |
| |
| using TrackedResourceArray = |
| std::array<TrackedResource, static_cast<uint32_t>(ResourceIDType::EnumCount)>; |
| |
| enum class ShaderProgramType |
| { |
| ShaderType, |
| ProgramType |
| }; |
| |
| // Helper to track resource changes during the capture |
| class ResourceTracker final : angle::NonCopyable |
| { |
| public: |
| ResourceTracker(); |
| ~ResourceTracker(); |
| |
| BufferCalls &getBufferMapCalls() { return mBufferMapCalls; } |
| BufferCalls &getBufferUnmapCalls() { return mBufferUnmapCalls; } |
| |
| std::vector<CallCapture> &getBufferBindingCalls() { return mBufferBindingCalls; } |
| |
| void setBufferMapped(gl::ContextID contextID, GLuint id); |
| void setBufferUnmapped(gl::ContextID contextID, GLuint id); |
| |
| bool getStartingBuffersMappedCurrent(GLuint id) const; |
| bool getStartingBuffersMappedInitial(GLuint id) const; |
| |
| void setStartingBufferMapped(GLuint 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(gl::SyncID sync); |
| |
| DefaultUniformLocationsPerProgramMap &getDefaultUniformsToReset() |
| { |
| return mDefaultUniformsToReset; |
| } |
| DefaultUniformCallsPerLocationMap &getDefaultUniformResetCalls(gl::ShaderProgramID id) |
| { |
| return mDefaultUniformResetCalls[id]; |
| } |
| void setModifiedDefaultUniform(gl::ShaderProgramID programID, gl::UniformLocation location); |
| void setDefaultUniformBaseLocation(gl::ShaderProgramID programID, |
| gl::UniformLocation location, |
| gl::UniformLocation baseLocation); |
| gl::UniformLocation getDefaultUniformBaseLocation(gl::ShaderProgramID programID, |
| gl::UniformLocation location) |
| { |
| ASSERT(mDefaultUniformBaseLocations.find({programID, location}) != |
| mDefaultUniformBaseLocations.end()); |
| return mDefaultUniformBaseLocations[{programID, location}]; |
| } |
| |
| TrackedResource &getTrackedResource(gl::ContextID contextID, ResourceIDType type); |
| |
| void getContextIDs(std::set<gl::ContextID> &idsOut); |
| |
| std::map<EGLImage, egl::AttributeMap> &getImageToAttribTable() { return mMatchImageToAttribs; } |
| |
| std::map<GLuint, egl::ImageID> &getTextureIDToImageTable() { return mMatchTextureIDToImage; } |
| |
| void setShaderProgramType(gl::ShaderProgramID id, angle::ShaderProgramType type) |
| { |
| mShaderProgramType[id] = type; |
| } |
| ShaderProgramType getShaderProgramType(gl::ShaderProgramID id) |
| { |
| ASSERT(mShaderProgramType.find(id) != mShaderProgramType.end()); |
| return mShaderProgramType[id]; |
| } |
| |
| void resetTrackedResourceArray(TrackedResourceArray &trackedResourceArray) |
| { |
| for (auto &trackedResource : trackedResourceArray) |
| { |
| trackedResource.reset(); |
| } |
| } |
| |
| // Some data in FrameCaptureShared tracks resources across all captures while |
| // other data is tracked per-capture. This function is responsible for |
| // resetting the per-capture tracking data in this class. |
| void resetResourceTracking() |
| { |
| resetTrackedResourceArray(mTrackedResourcesShared); |
| for (auto &pair : mTrackedResourcesPerContext) |
| { |
| resetTrackedResourceArray(pair.second); |
| } |
| |
| mBufferMapCalls.clear(); |
| mBufferUnmapCalls.clear(); |
| mBufferBindingCalls.clear(); |
| mStartingBuffersMappedInitial.clear(); |
| mStartingBuffersMappedCurrent.clear(); |
| mMaxShaderPrograms = 0; |
| mStartingFenceSyncs.clear(); |
| mFenceSyncRegenCalls.clear(); |
| mFenceSyncsToRegen.clear(); |
| mDefaultUniformsToReset.clear(); |
| mDefaultUniformResetCalls.clear(); |
| mDefaultUniformBaseLocations.clear(); |
| } |
| |
| private: |
| // These data structures are per-capture and must be reset before beginning a new |
| // capture in the resetResourceTracking() function above |
| |
| // 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<CallCapture> mBufferBindingCalls; |
| // 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; |
| // Default uniforms that were modified during the run |
| DefaultUniformLocationsPerProgramMap mDefaultUniformsToReset; |
| // Calls per default uniform to return to original state |
| DefaultUniformCallsPerProgramMap mDefaultUniformResetCalls; |
| // Base location of arrayed uniforms |
| DefaultUniformBaseLocationMap mDefaultUniformBaseLocations; |
| |
| // These data structures must be preserved across all captures |
| |
| // Tracked resources per context |
| TrackedResourceArray mTrackedResourcesShared; |
| std::map<gl::ContextID, TrackedResourceArray> mTrackedResourcesPerContext; |
| std::map<EGLImage, egl::AttributeMap> mMatchImageToAttribs; |
| std::map<GLuint, egl::ImageID> mMatchTextureIDToImage; |
| std::map<gl::ShaderProgramID, ShaderProgramType> mShaderProgramType; |
| }; |
| |
| // CL specific resource tracker to track resource changes during the capture |
| #ifdef ANGLE_ENABLE_CL |
| struct ResourceTrackerCL final : angle::NonCopyable |
| { |
| ResourceTrackerCL(); |
| ~ResourceTrackerCL(); |
| |
| // To obtain indices of CL arguments in replay |
| std::unordered_map<cl_platform_id, size_t> mCLPlatformIDIndices; |
| std::unordered_map<cl_device_id, size_t> mCLDeviceIDIndices; |
| std::unordered_map<cl_context, size_t> mCLContextIndices; |
| std::unordered_map<cl_event, size_t> mCLEventsIndices; |
| std::unordered_map<cl_command_queue, size_t> mCLCommandQueueIndices; |
| std::unordered_map<cl_mem, size_t> mCLMemIndices; |
| std::unordered_map<cl_sampler, size_t> mCLSamplerIndices; |
| std::unordered_map<cl_program, size_t> mCLProgramIndices; |
| std::unordered_map<cl_kernel, size_t> mCLKernelIndices; |
| |
| std::unordered_map<const void *, size_t> mCLVoidIndices; |
| |
| std::unordered_map<uint32_t, std::vector<size_t>> mCLParamIDToIndexVector; |
| |
| // To account for cl mem or SVM pointers that are potentially dirty |
| // coming into the starting frame or from mapping and unmapping. |
| std::vector<cl_mem> mCLDirtyMem; |
| std::vector<void *> mCLDirtySVM; |
| |
| // Keeps track of the # of times the program is linked, including it's own creation |
| std::unordered_map<cl_program, cl_uint> mCLProgramLinkCounter; |
| |
| // To keep track of the sub buffer and parent replationship |
| std::unordered_map<cl_mem, cl_mem> mCLSubBufferToParent; |
| |
| // To keep track of the linked programs |
| std::unordered_map<cl_program, std::vector<cl_program>> mCLLinkedPrograms; |
| |
| // Program to all the kernels in the program |
| // So that when a program is released, it can also remove all the kernels. |
| std::unordered_map<cl_program, std::vector<cl_kernel>> mCLProgramToKernels; |
| |
| // Kernel to program, to keep track of the program that a cloned kernel |
| // belongs to. Can't use ANGLE's getProgram() because the kernel object |
| // may be deleted by the time it's needed for capture. |
| std::unordered_map<cl_kernel, cl_program> mCLKernelToProgram; |
| |
| // Mapped pointer to the map call |
| std::unordered_map<const void *, CallCapture> mCLMapCall; |
| |
| // Gets the size of the SVM memory |
| std::unordered_map<const void *, size_t> SVMToSize; |
| |
| cl_command_queue mCLCurrentCommandQueue; |
| |
| std::vector<ParamCapture> mCLResetObjs; |
| }; |
| #endif |
| |
| // Used by the CPP replay to filter out unnecessary code. |
| using HasResourceTypeMap = angle::PackedEnumBitSet<ResourceIDType>; |
| |
| // Map of ResourceType to IDs and range of setup calls |
| using ResourceIDToSetupCallsMap = |
| PackedEnumMap<ResourceIDType, std::map<GLuint, gl::Range<size_t>>>; |
| |
| // Map of buffer ID to offset and size used when mapped |
| using BufferDataMap = std::map<gl::BufferID, std::pair<GLintptr, GLsizeiptr>>; |
| |
| // 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>; |
| |
| // Map from textureID to level and data |
| using TextureLevels = std::map<GLint, std::vector<uint8_t>>; |
| using TextureLevelDataMap = std::map<gl::TextureID, TextureLevels>; |
| |
| struct SurfaceParams |
| { |
| gl::Extents extents; |
| egl::ColorSpace colorSpace; |
| }; |
| |
| // Map from ContextID to SurfaceParams |
| using SurfaceParamsMap = std::map<gl::ContextID, SurfaceParams>; |
| |
| using CallVector = std::vector<std::vector<CallCapture> *>; |
| |
| // A map from API entry point to calls |
| using CallResetMap = std::map<angle::EntryPoint, std::vector<CallCapture>>; |
| |
| using TextureBinding = std::pair<size_t, gl::TextureType>; |
| using TextureResetMap = std::map<TextureBinding, gl::TextureID>; |
| |
| using BufferBindingPair = std::pair<gl::BufferBinding, gl::BufferID>; |
| |
| // StateResetHelper provides a simple way to track whether an entry point has been called during the |
| // trace, along with the reset calls to get it back to starting state. This is useful for things |
| // that are one dimensional, like context bindings or context state. |
| class StateResetHelper final : angle::NonCopyable |
| { |
| public: |
| StateResetHelper(); |
| ~StateResetHelper(); |
| |
| const std::set<angle::EntryPoint> &getDirtyEntryPoints() const { return mDirtyEntryPoints; } |
| void setEntryPointDirty(EntryPoint entryPoint) { mDirtyEntryPoints.insert(entryPoint); } |
| |
| CallResetMap &getResetCalls() { return mResetCalls; } |
| const CallResetMap &getResetCalls() const { return mResetCalls; } |
| |
| void setDefaultResetCalls(const gl::Context *context, angle::EntryPoint); |
| |
| const std::set<TextureBinding> &getDirtyTextureBindings() const |
| { |
| return mDirtyTextureBindings; |
| } |
| void setTextureBindingDirty(size_t unit, gl::TextureType target) |
| { |
| mDirtyTextureBindings.emplace(unit, target); |
| } |
| |
| TextureResetMap &getResetTextureBindings() { return mResetTextureBindings; } |
| |
| void setResetActiveTexture(size_t textureID) { mResetActiveTexture = textureID; } |
| size_t getResetActiveTexture() { return mResetActiveTexture; } |
| |
| const std::set<gl::BufferBinding> &getDirtyBufferBindings() const |
| { |
| return mDirtyBufferBindings; |
| } |
| void setBufferBindingDirty(gl::BufferBinding binding) { mDirtyBufferBindings.insert(binding); } |
| |
| const std::set<BufferBindingPair> &getStartingBufferBindings() const |
| { |
| return mStartingBufferBindings; |
| } |
| void setStartingBufferBinding(gl::BufferBinding binding, gl::BufferID bufferID) |
| { |
| mStartingBufferBindings.insert({binding, bufferID}); |
| } |
| |
| void reset() |
| { |
| mDirtyEntryPoints.clear(); |
| mResetCalls.clear(); |
| mDirtyTextureBindings.clear(); |
| mResetTextureBindings.clear(); |
| mResetActiveTexture = 0; |
| mStartingBufferBindings.clear(); |
| mDirtyBufferBindings.clear(); |
| } |
| |
| private: |
| // Dirty state per entry point |
| std::set<angle::EntryPoint> mDirtyEntryPoints; |
| |
| // Reset calls per API entry point |
| CallResetMap mResetCalls; |
| |
| // Dirty state per texture binding |
| std::set<TextureBinding> mDirtyTextureBindings; |
| |
| // Texture bindings and active texture to restore |
| TextureResetMap mResetTextureBindings; |
| size_t mResetActiveTexture = 0; |
| |
| // Starting and dirty buffer bindings |
| std::set<BufferBindingPair> mStartingBufferBindings; |
| std::set<gl::BufferBinding> mDirtyBufferBindings; |
| }; |
| |
| class FrameCapture final : angle::NonCopyable |
| { |
| public: |
| FrameCapture(); |
| ~FrameCapture(); |
| |
| std::vector<CallCapture> &getSetupCalls() { return mSetupCalls; } |
| void clearSetupCalls() { mSetupCalls.clear(); } |
| |
| StateResetHelper &getStateResetHelper() { return mStateResetHelper; } |
| |
| void reset(); |
| |
| private: |
| std::vector<CallCapture> mSetupCalls; |
| |
| StateResetHelper mStateResetHelper; |
| }; |
| |
| // Page range inside a coherent buffer |
| struct PageRange |
| { |
| PageRange(size_t start, size_t end); |
| ~PageRange(); |
| |
| // Relative start page |
| size_t start; |
| |
| // First page after the relative end |
| size_t end; |
| }; |
| |
| // Memory address range defined by start and size |
| struct AddressRange |
| { |
| AddressRange(); |
| AddressRange(uintptr_t start, size_t size); |
| ~AddressRange(); |
| |
| uintptr_t end(); |
| |
| uintptr_t start; |
| size_t size; |
| }; |
| |
| // Used to handle protection of buffers that overlap in pages. |
| enum class PageSharingType |
| { |
| NoneShared, |
| FirstShared, |
| LastShared, |
| FirstAndLastShared |
| }; |
| |
| class CoherentBuffer |
| { |
| public: |
| CoherentBuffer(uintptr_t start, size_t size, size_t pageSize, bool useShadowMemory); |
| ~CoherentBuffer(); |
| |
| // Sets the a range in the buffer clean and protects a selected range |
| void protectPageRange(const PageRange &pageRange); |
| |
| // Sets all pages to clean and enables protection |
| void protectAll(); |
| |
| // Sets a page dirty state and sets it's protection |
| void setDirty(size_t relativePage, bool dirty); |
| |
| // Shadow memory synchronization |
| void updateBufferMemory(); |
| void updateShadowMemory(); |
| |
| // Removes protection |
| void removeProtection(PageSharingType sharingType); |
| |
| bool contains(size_t page, size_t *relativePage); |
| bool isDirty(); |
| |
| // Returns dirty page ranges |
| std::vector<PageRange> getDirtyPageRanges(); |
| |
| // Calculates address range from page range |
| AddressRange getDirtyAddressRange(const PageRange &dirtyPageRange); |
| AddressRange getRange(); |
| |
| void markShadowDirty() { mShadowDirty = true; } |
| bool isShadowDirty() { return mShadowDirty; } |
| |
| private: |
| // Actual buffer start and size |
| AddressRange mRange; |
| |
| // Start and size of page aligned protected area |
| AddressRange mProtectionRange; |
| |
| // Start and end of protection in relative pages, calculated from mProtectionRange. |
| size_t mProtectionStartPage; |
| size_t mProtectionEndPage; |
| |
| size_t mPageCount; |
| size_t mPageSize; |
| |
| // Clean pages are protected |
| std::vector<bool> mDirtyPages; |
| |
| // shadow memory releated fields |
| bool mShadowMemoryEnabled; |
| uintptr_t mBufferStart; |
| void *mShadowMemory; |
| bool mShadowDirty; |
| }; |
| |
| class CoherentBufferTracker final : angle::NonCopyable |
| { |
| public: |
| CoherentBufferTracker(); |
| ~CoherentBufferTracker(); |
| |
| bool isDirty(gl::BufferID id); |
| uintptr_t addBuffer(gl::BufferID id, uintptr_t start, size_t size); |
| void removeBuffer(gl::BufferID id); |
| void disable(); |
| void enable(); |
| void onEndFrame(); |
| bool haveBuffer(gl::BufferID id); |
| bool isShadowMemoryEnabled() { return mShadowMemoryEnabled; } |
| void enableShadowMemory() { mShadowMemoryEnabled = true; } |
| void maybeUpdateShadowMemory(); |
| void markAllShadowDirty(); |
| // Determine whether memory protection can be used directly on graphics memory |
| bool canProtectDirectly(gl::Context *context); |
| |
| private: |
| // Detect overlapping pages when removing protection |
| PageSharingType doesBufferSharePage(gl::BufferID id); |
| |
| // Returns a map to found buffers and the corresponding pages for a given address. |
| // For addresses that are in a page shared by 2 buffers, 2 results are returned. |
| HashMap<std::shared_ptr<CoherentBuffer>, size_t> getBufferPagesForAddress(uintptr_t address); |
| PageFaultHandlerRangeType handleWrite(uintptr_t address); |
| |
| public: |
| angle::SimpleMutex mMutex; |
| HashMap<GLuint, std::shared_ptr<CoherentBuffer>> mBuffers; |
| bool hasBeenReset() { return mHasBeenReset; } |
| |
| private: |
| bool mEnabled; |
| bool mHasBeenReset; |
| std::unique_ptr<PageFaultHandler> mPageFaultHandler; |
| size_t mPageSize; |
| |
| bool mShadowMemoryEnabled; |
| }; |
| |
| // Shared class for any items that need to be tracked by FrameCapture across shared contexts |
| class FrameCaptureShared final : angle::NonCopyable |
| { |
| public: |
| FrameCaptureShared(); |
| ~FrameCaptureShared(); |
| |
| void captureCall(gl::Context *context, CallCapture &&call, bool isCallValid); |
| void checkForCaptureTrigger(); |
| void onEndFrame(gl::Context *context); |
| void onDestroyContext(const gl::Context *context); |
| bool onEndCLCapture(); |
| void onMakeCurrent(const gl::Context *context, const egl::Surface *drawSurface); |
| bool enabled() const { return mEnabled; } |
| |
| bool isCapturing() const; |
| uint32_t getFrameCount() const; |
| |
| // Returns a frame index starting from "1" as the first frame. |
| uint32_t getReplayFrameIndex() const; |
| |
| #ifdef ANGLE_ENABLE_CL |
| void captureCLCall(CallCapture &&call, bool isCallValid); |
| static void onCLProgramEnd(); |
| |
| template <typename T> |
| size_t getIndex(const T *object) |
| { |
| if (getMap<T>().find(*object) == getMap<T>().end()) |
| { |
| return SIZE_MAX; |
| } |
| return getMap<T>()[*object]; |
| } |
| size_t getCLVoidIndex(const void *v); |
| std::vector<size_t> getCLObjVector(const angle::ParamCapture *paramCaptureKey); |
| |
| template <typename T> |
| void setIndex(const T *object) |
| { |
| if (getMap<T>().find(*object) == getMap<T>().end()) |
| { |
| size_t tempSize = getMap<T>().size(); |
| getMap<T>()[*object] = tempSize; |
| } |
| } |
| void setCLPlatformIndices(cl_platform_id *platforms, size_t numPlatforms); |
| void setCLDeviceIndices(cl_device_id *devices, size_t numDevices); |
| void setCLVoidIndex(const void *v); |
| void setOffsetsVector(const void *args, |
| const void **argsLocations, |
| size_t numLocations, |
| const angle::ParamCapture *paramCaptureKey); |
| void setCLVoidVectorIndex(const void *pointers[], |
| size_t numPointers, |
| const angle::ParamCapture *paramCaptureKey); |
| |
| template <typename T> |
| using MemberFuncPtr = size_t (FrameCaptureShared::*)(const T *); |
| |
| template <typename T> |
| void setCLObjVectorMap(const T *objs, |
| size_t numObjs, |
| const angle::ParamCapture *paramCaptureKey, |
| MemberFuncPtr<const T> getCLObjIndexFunc) |
| { |
| mResourceTrackerCL.mCLParamIDToIndexVector[paramCaptureKey->uniqueID] = |
| std::vector<size_t>(); |
| for (size_t i = 0; i < numObjs; ++i) |
| { |
| mResourceTrackerCL.mCLParamIDToIndexVector[paramCaptureKey->uniqueID].push_back( |
| (this->*getCLObjIndexFunc)(&objs[i])); |
| } |
| } |
| |
| template <typename T> |
| std::unordered_map<T, size_t> &getMap(); |
| |
| void addCLResetObj(const angle::ParamCapture ¶m); |
| void removeCLResetObj(const ParamCapture ¶m); |
| void printCLResetObjs(std::stringstream &stream); |
| |
| void trackCLMemUpdate(const cl_mem *mem, bool created); |
| void trackCLProgramUpdate(const cl_program *program, |
| bool referenced, |
| cl_uint numLinkedPrograms, |
| const cl_program *linkedPrograms); |
| void trackCLEvents(const cl_event *event, bool created); |
| void injectMemcpy(void *src, void *dest, size_t size, std::vector<CallCapture> *calls); |
| void captureUpdateCLObjs(std::vector<CallCapture> *calls); |
| void removeCLMemOccurrences(const cl_mem *mem, std::vector<CallCapture> *calls); |
| void removeCLKernelOccurrences(const cl_kernel *kernel, std::vector<CallCapture> *calls); |
| void removeCLProgramOccurrences(const cl_program *program, std::vector<CallCapture> *calls); |
| void removeCLCall(std::vector<CallCapture> *callVector, size_t &callIndex); |
| #endif |
| |
| void trackBufferMapping(const gl::Context *context, |
| CallCapture *call, |
| gl::BufferID id, |
| gl::Buffer *buffer, |
| GLintptr offset, |
| GLsizeiptr length, |
| bool writable, |
| bool coherent); |
| |
| void trackTextureUpdate(const gl::Context *context, const CallCapture &call); |
| void trackImageUpdate(const gl::Context *context, const CallCapture &call); |
| void trackDefaultUniformUpdate(const gl::Context *context, const CallCapture &call); |
| void trackVertexArrayUpdate(const gl::Context *context, const CallCapture &call); |
| |
| 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<uint8_t> &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<uint8_t> &getCachedTextureLevelData(gl::Texture *texture, |
| gl::TextureTarget target, |
| GLint level, |
| EntryPoint entryPoint); |
| |
| // Capture coherent buffer storages |
| void captureCoherentBufferSnapshot(const gl::Context *context, gl::BufferID bufferID); |
| |
| // Remove any cached texture levels on deletion |
| void deleteCachedTextureLevelData(gl::TextureID id); |
| |
| void eraseBufferDataMapEntry(const gl::BufferID bufferId) |
| { |
| const auto &bufferDataInfo = mBufferDataMap.find(bufferId); |
| if (bufferDataInfo != mBufferDataMap.end()) |
| { |
| mBufferDataMap.erase(bufferDataInfo); |
| } |
| } |
| |
| bool hasBufferData(gl::BufferID bufferID) |
| { |
| const auto &bufferDataInfo = mBufferDataMap.find(bufferID); |
| if (bufferDataInfo != mBufferDataMap.end()) |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| std::pair<GLintptr, GLsizeiptr> getBufferDataOffsetAndLength(gl::BufferID bufferID) |
| { |
| const auto &bufferDataInfo = mBufferDataMap.find(bufferID); |
| ASSERT(bufferDataInfo != mBufferDataMap.end()); |
| return bufferDataInfo->second; |
| } |
| |
| void setCaptureActive() { mCaptureActive = true; } |
| void setCaptureInactive() { mCaptureActive = false; } |
| bool isCaptureActive() { return mCaptureActive; } |
| bool usesMidExecutionCapture() { return mCaptureStartFrame > 1; } |
| |
| gl::ContextID getWindowSurfaceContextID() const { return mWindowSurfaceContextID; } |
| |
| void markResourceSetupCallsInactive(std::vector<CallCapture> *setupCalls, |
| ResourceIDType type, |
| GLuint id, |
| gl::Range<size_t> range); |
| |
| void getOutputDirectory(); |
| void updateReadBufferSize(size_t readBufferSize) |
| { |
| mReadBufferSize = std::max(mReadBufferSize, readBufferSize); |
| } |
| |
| template <typename ResourceType> |
| void handleGennedResource(const gl::Context *context, ResourceType resourceID) |
| { |
| if (isCaptureActive()) |
| { |
| ResourceIDType idType = GetResourceIDTypeFromType<ResourceType>::IDType; |
| TrackedResource &tracker = mResourceTracker.getTrackedResource(context->id(), idType); |
| tracker.setGennedResource(resourceID.value); |
| } |
| } |
| |
| template <typename ResourceType> |
| bool resourceIsGenerated(const gl::Context *context, ResourceType resourceID) |
| { |
| ResourceIDType idType = GetResourceIDTypeFromType<ResourceType>::IDType; |
| TrackedResource &tracker = mResourceTracker.getTrackedResource(context->id(), idType); |
| return tracker.resourceIsGenerated(resourceID.value); |
| } |
| |
| template <typename ResourceType> |
| void handleDeletedResource(const gl::Context *context, ResourceType resourceID) |
| { |
| if (isCaptureActive()) |
| { |
| ResourceIDType idType = GetResourceIDTypeFromType<ResourceType>::IDType; |
| TrackedResource &tracker = mResourceTracker.getTrackedResource(context->id(), idType); |
| tracker.setDeletedResource(resourceID.value); |
| } |
| } |
| |
| void *maybeGetShadowMemoryPointer(gl::Buffer *buffer, GLsizeiptr length, GLbitfield access); |
| void determineMemoryProtectionSupport(gl::Context *context); |
| |
| angle::SimpleMutex &getFrameCaptureMutex() { return mFrameCaptureMutex; } |
| |
| void setDeferredLinkProgram(gl::ShaderProgramID programID) |
| { |
| mDeferredLinkPrograms.emplace(programID); |
| } |
| bool isDeferredLinkProgram(gl::ShaderProgramID programID) |
| { |
| return (mDeferredLinkPrograms.find(programID) != mDeferredLinkPrograms.end()); |
| } |
| |
| static bool isRuntimeEnabled(); |
| |
| void resetCaptureStartEndFrames() |
| { |
| // If the trigger has been populated the frame range variables will be calculated |
| // based on the trigger value, so for now reset them to unreasonable values. |
| mCaptureStartFrame = mCaptureEndFrame = std::numeric_limits<uint32_t>::max(); |
| INFO() << "Capture trigger detected, resetting capture start/end frame."; |
| } |
| |
| private: |
| void writeJSON(const gl::Context *context); |
| void writeJSONCL(); |
| void writeJSONCLGetInfo(); |
| void saveCLGetInfo(const CallCapture &call); |
| void writeCppReplayIndexFiles(const gl::Context *context, bool writeResetContextCall); |
| void writeCppReplayIndexFilesCL(); |
| void writeMainContextCppReplay(const gl::Context *context, |
| const std::vector<CallCapture> &setupCalls, |
| StateResetHelper &StateResetHelper); |
| void writeMainContextCppReplayCL(); |
| |
| 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 resetMidExecutionCapture(gl::Context *context); |
| void maybeOverrideEntryPoint(const gl::Context *context, |
| CallCapture &call, |
| std::vector<CallCapture> &newCalls); |
| void maybeCapturePreCallUpdates(const gl::Context *context, |
| CallCapture &call, |
| std::vector<CallCapture> *shareGroupSetupCalls, |
| ResourceIDToSetupCallsMap *resourceIDToSetupCalls); |
| void maybeCapturePreCallUpdatesCL(CallCapture &call); |
| template <typename ParamValueType> |
| void maybeGenResourceOnBind(const gl::Context *context, CallCapture &call); |
| void maybeCapturePostCallUpdates(const gl::Context *context); |
| void maybeCapturePostCallUpdatesCL(); |
| void maybeCaptureDrawArraysClientData(const gl::Context *context, |
| CallCapture &call, |
| size_t instanceCount); |
| void maybeCaptureDrawElementsClientData(const gl::Context *context, |
| CallCapture &call, |
| size_t instanceCount); |
| void maybeCaptureCoherentBuffers(const gl::Context *context); |
| void captureCustomMapBufferFromContext(const gl::Context *context, |
| const char *entryPointName, |
| CallCapture &call, |
| std::vector<CallCapture> &callsOut); |
| void updateCopyImageSubData(CallCapture &call); |
| void overrideProgramBinary(const gl::Context *context, |
| CallCapture &call, |
| std::vector<CallCapture> &outCalls); |
| void updateResourceCountsFromParamCapture(const ParamCapture ¶m, ResourceIDType idType); |
| void updateResourceCountsFromParamCaptureCL(const ParamCapture ¶m, const CallCapture &call); |
| void updateResourceCountsFromCallCapture(const CallCapture &call); |
| void updateResourceCountsFromCallCaptureCL(const CallCapture &call); |
| |
| void runMidExecutionCapture(gl::Context *context); |
| |
| void scanSetupCalls(std::vector<CallCapture> &setupCalls); |
| |
| std::vector<CallCapture> mFrameCalls; |
| |
| // We save one large buffer of binary data for the whole CPP replay. |
| // This simplifies a lot of file management. |
| std::vector<uint8_t> mBinaryData; |
| |
| bool mEnabled; |
| static bool mRuntimeEnabled; |
| static bool mRuntimeInitialized; |
| bool mSerializeStateEnabled; |
| std::string mOutDirectory; |
| std::string mCaptureLabel; |
| bool mCompression; |
| gl::AttribArray<int> mClientVertexArrayMap; |
| uint32_t mFrameIndex; |
| uint32_t mCaptureStartFrame; |
| uint32_t mCaptureEndFrame; |
| bool mIsFirstFrame = true; |
| bool mWroteIndexFile = false; |
| SurfaceParamsMap mDrawSurfaceParams; |
| gl::AttribArray<size_t> mClientArraySizes; |
| size_t mReadBufferSize; |
| size_t mResourceIDBufferSize; |
| HasResourceTypeMap mHasResourceType; |
| ResourceIDToSetupCallsMap mResourceIDToSetupCalls; |
| BufferDataMap mBufferDataMap; |
| bool mValidateSerializedState = false; |
| std::string mValidationExpression; |
| PackedEnumMap<ResourceIDType, uint32_t> mMaxAccessedResourceIDs; |
| std::map<ParamType, uint32_t> mMaxCLParamsSize; |
| CoherentBufferTracker mCoherentBufferTracker; |
| angle::SimpleMutex mFrameCaptureMutex; |
| bool mCallCaptured = false; |
| bool mStartFrameCallCaptured = false; |
| |
| // When true, it removes unnecessary calls going into |
| // replay files that occur before mCaptureStartFrame |
| bool removeUnneededOpenCLCalls = false; |
| |
| #ifdef ANGLE_ENABLE_CL |
| // OpenCL calls considered as "frames" |
| std::unordered_set<EntryPoint> mCLEndFrameCalls = {EntryPoint::CLEnqueueNDRangeKernel, |
| EntryPoint::CLEnqueueNativeKernel, |
| EntryPoint::CLEnqueueTask}; |
| |
| // "Optional" OpenCL calls not important for Capture/Replay |
| std::unordered_set<EntryPoint> mCLOptionalCalls = {EntryPoint::CLGetPlatformInfo, |
| EntryPoint::CLGetDeviceInfo, |
| EntryPoint::CLGetContextInfo, |
| EntryPoint::CLGetCommandQueueInfo, |
| EntryPoint::CLGetProgramInfo, |
| EntryPoint::CLGetProgramBuildInfo, |
| EntryPoint::CLGetKernelInfo, |
| EntryPoint::CLGetKernelArgInfo, |
| EntryPoint::CLGetKernelWorkGroupInfo, |
| EntryPoint::CLGetEventInfo, |
| EntryPoint::CLGetEventProfilingInfo, |
| EntryPoint::CLGetMemObjectInfo, |
| EntryPoint::CLGetImageInfo, |
| EntryPoint::CLGetSamplerInfo, |
| EntryPoint::CLGetSupportedImageFormats}; |
| std::string mCLInfoJson; |
| std::vector<std::string> mExtFuncsAdded; |
| |
| std::vector<CallCapture> mCLSetupCalls; |
| |
| ResourceTrackerCL mResourceTrackerCL; |
| #endif |
| |
| ResourceTracker mResourceTracker; |
| ReplayWriter mReplayWriter; |
| |
| // 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; |
| std::vector<uint32_t> mActiveFrameIndices; |
| |
| // Cache most recently compiled and linked sources. |
| ShaderSourceMap mCachedShaderSource; |
| ProgramSourceMap mCachedProgramSources; |
| |
| // Set of programs which were created but not linked before capture was started |
| std::set<gl::ShaderProgramID> mDeferredLinkPrograms; |
| |
| gl::ContextID mWindowSurfaceContextID; |
| |
| std::vector<CallCapture> mShareGroupSetupCalls; |
| // Track which Contexts were created and made current at least once before MEC, |
| // requiring setup for replay |
| std::unordered_set<GLuint> mActiveContexts; |
| |
| // Invalid call counts per entry point while capture is active and inactive. |
| std::unordered_map<EntryPoint, size_t> mInvalidCallCountsActive; |
| std::unordered_map<EntryPoint, size_t> mInvalidCallCountsInactive; |
| }; |
| |
| template <typename CaptureFuncT, typename... ArgsT> |
| void CaptureGLCallToFrameCapture(CaptureFuncT captureFunc, |
| bool isCallValid, |
| gl::Context *context, |
| ArgsT... captureParams) |
| { |
| if (!FrameCaptureShared::isRuntimeEnabled()) |
| { |
| // Return immediately to reduce overhead of compile-time flag |
| return; |
| } |
| FrameCaptureShared *frameCaptureShared = context->getShareGroup()->getFrameCaptureShared(); |
| |
| // EGL calls are protected by the global context mutex but only a subset of GL calls |
| // are so protected. Ensure FrameCaptureShared access thread safety by using a |
| // frame-capture only mutex. |
| std::lock_guard<angle::SimpleMutex> lock(frameCaptureShared->getFrameCaptureMutex()); |
| |
| if (!frameCaptureShared->isCapturing()) |
| { |
| return; |
| } |
| |
| CallCapture call = captureFunc(context->getState(), isCallValid, captureParams...); |
| frameCaptureShared->captureCall(context, std::move(call), isCallValid); |
| } |
| |
| template <typename FirstT, typename... OthersT> |
| egl::Display *GetEGLDisplayArg(FirstT display, OthersT... others) |
| { |
| if constexpr (std::is_same<egl::Display *, FirstT>::value) |
| { |
| return display; |
| } |
| return nullptr; |
| } |
| |
| template <typename CaptureFuncT, typename... ArgsT> |
| void CaptureEGLCallToFrameCapture(CaptureFuncT captureFunc, |
| bool isCallValid, |
| egl::Thread *thread, |
| ArgsT... captureParams) |
| { |
| if (!FrameCaptureShared::isRuntimeEnabled()) |
| { |
| // Return immediately to reduce overhead of compile-time flag |
| return; |
| } |
| gl::Context *context = thread->getContext(); |
| if (!context) |
| { |
| // Get a valid context from the display argument if no context is associated with this |
| // thread |
| egl::Display *display = GetEGLDisplayArg(captureParams...); |
| if (display) |
| { |
| for (const auto &contextIter : display->getState().contextMap) |
| { |
| context = contextIter.second; |
| break; |
| } |
| } |
| if (!context) |
| { |
| return; |
| } |
| } |
| std::lock_guard<egl::ContextMutex> lock(context->getContextMutex()); |
| |
| angle::FrameCaptureShared *frameCaptureShared = |
| context->getShareGroup()->getFrameCaptureShared(); |
| if (!frameCaptureShared->isCapturing()) |
| { |
| return; |
| } |
| |
| angle::CallCapture call = captureFunc(thread, isCallValid, captureParams...); |
| frameCaptureShared->captureCall(context, std::move(call), true); |
| } |
| |
| #ifdef ANGLE_ENABLE_CL |
| template <typename CaptureFuncT, typename... ArgsT> |
| void CaptureCLCallToFrameCapture(CaptureFuncT captureFunc, bool isCallValid, ArgsT... captureParams) |
| { |
| if (!FrameCaptureShared::isRuntimeEnabled()) |
| { |
| // Return immediately to reduce overhead of compile-time flag |
| return; |
| } |
| angle::FrameCaptureShared *frameCaptureShared = |
| cl::Platform::GetDefault()->getFrameCaptureShared(); |
| std::lock_guard<angle::SimpleMutex> lock(frameCaptureShared->getFrameCaptureMutex()); |
| if (!frameCaptureShared || !frameCaptureShared->isCapturing()) |
| { |
| return; |
| } |
| angle::CallCapture call = captureFunc(isCallValid, captureParams...); |
| frameCaptureShared->captureCLCall(std::move(call), isCallValid); |
| } |
| #endif |
| |
| // 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 <typename T> |
| 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 <typename T> |
| void CaptureGenHandles(GLsizei n, T *handles, ParamCapture *paramCapture) |
| { |
| paramCapture->dataNElements = n; |
| CaptureGenHandlesImpl(n, reinterpret_cast<GLuint *>(handles), paramCapture); |
| } |
| |
| template <typename T> |
| void CaptureArray(T *elements, GLsizei n, ParamCapture *paramCapture) |
| { |
| paramCapture->dataNElements = n; |
| CaptureMemory(elements, n * sizeof(T), paramCapture); |
| } |
| |
| void CaptureShaderStrings(GLsizei count, |
| const GLchar *const *strings, |
| const GLint *length, |
| ParamCapture *paramCapture); |
| |
| bool IsTrackedPerContext(ResourceIDType type); |
| |
| // Function declarations & data types for both |
| // capturing OpenGL and OpenCL |
| |
| std::string EscapeString(const std::string &string); |
| |
| // Used to indicate that "shared" should be used to identify the files. |
| constexpr gl::ContextID kSharedContextId = {0}; |
| // Used to indicate no context ID should be output. |
| constexpr gl::ContextID kNoContextId = {std::numeric_limits<uint32_t>::max()}; |
| |
| constexpr uint32_t kNoPartId = std::numeric_limits<uint32_t>::max(); |
| |
| std::ostream &operator<<(std::ostream &os, gl::ContextID contextId); |
| |
| struct FmtCapturePrefix |
| { |
| FmtCapturePrefix(gl::ContextID contextIdIn, const std::string &captureLabelIn) |
| : contextId(contextIdIn), captureLabel(captureLabelIn) |
| {} |
| gl::ContextID contextId; |
| const std::string &captureLabel; |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, const FmtCapturePrefix &fmt); |
| |
| // In C, when you declare or define a function that takes no parameters, you must explicitly say the |
| // function takes "void" parameters. When you're calling the function you omit this void. It's |
| // therefore necessary to know how we're using a function to know if we should emi the "void". |
| enum FuncUsage |
| { |
| Prototype, |
| Definition, |
| Call, |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, FuncUsage usage); |
| |
| struct FmtReplayFunction |
| { |
| FmtReplayFunction(gl::ContextID contextIdIn, |
| FuncUsage usageIn, |
| uint32_t frameIndexIn, |
| uint32_t partIdIn = kNoPartId) |
| : contextId(contextIdIn), usage(usageIn), frameIndex(frameIndexIn), partId(partIdIn) |
| {} |
| gl::ContextID contextId; |
| FuncUsage usage; |
| uint32_t frameIndex; |
| uint32_t partId; |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, const FmtReplayFunction &fmt); |
| |
| enum class ReplayFunc |
| { |
| Replay, |
| Setup, |
| SetupInactive, |
| Reset, |
| SetupFirstFrame, |
| }; |
| |
| struct FmtFunction |
| { |
| FmtFunction(ReplayFunc funcTypeIn, |
| gl::ContextID contextIdIn, |
| FuncUsage usageIn, |
| uint32_t frameIndexIn, |
| uint32_t partIdIn) |
| : funcType(funcTypeIn), |
| contextId(contextIdIn), |
| usage(usageIn), |
| frameIndex(frameIndexIn), |
| partId(partIdIn) |
| {} |
| |
| ReplayFunc funcType; |
| gl::ContextID contextId; |
| FuncUsage usage; |
| uint32_t frameIndex; |
| uint32_t partId; |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, gl::ContextID contextId); |
| |
| std::ostream &operator<<(std::ostream &os, const FmtFunction &fmt); |
| |
| struct FmtSetupFunction |
| { |
| FmtSetupFunction(uint32_t partIdIn, gl::ContextID contextIdIn, FuncUsage usageIn) |
| : partId(partIdIn), contextId(contextIdIn), usage(usageIn) |
| {} |
| |
| uint32_t partId; |
| gl::ContextID contextId; |
| FuncUsage usage; |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, const FmtSetupFunction &fmt); |
| |
| struct FmtSetupFirstFrameFunction |
| { |
| FmtSetupFirstFrameFunction(uint32_t partIdIn = kNoPartId) : partId(partIdIn) {} |
| |
| uint32_t partId; |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, const FmtSetupFirstFrameFunction &fmt); |
| |
| struct FmtSetupInactiveFunction |
| { |
| FmtSetupInactiveFunction(uint32_t partIdIn, gl::ContextID contextIdIn, FuncUsage usageIn) |
| : partId(partIdIn), contextId(contextIdIn), usage(usageIn) |
| {} |
| |
| uint32_t partId; |
| gl::ContextID contextId; |
| FuncUsage usage; |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, const FmtSetupInactiveFunction &fmt); |
| |
| struct FmtResetFunction |
| { |
| FmtResetFunction(uint32_t partIdIn, gl::ContextID contextIdIn, FuncUsage usageIn) |
| : partId(partIdIn), contextId(contextIdIn), usage(usageIn) |
| {} |
| |
| uint32_t partId; |
| gl::ContextID contextId; |
| FuncUsage usage; |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, const FmtResetFunction &fmt); |
| |
| // For compatibility with C, which does not have multi-line string literals, we break strings up |
| // into multiple lines like: |
| // |
| // const char *str[] = { |
| // "multiple\n" |
| // "line\n" |
| // "strings may have \"quotes\"\n" |
| // "and \\slashes\\\n", |
| // }; |
| // |
| // Note we need to emit extra escapes to ensure quotes and other special characters are preserved. |
| struct FmtMultiLineString |
| { |
| FmtMultiLineString(const std::string &str) : strings() |
| { |
| std::string str2; |
| |
| // Strip any carriage returns before splitting, for consistency |
| if (str.find("\r") != std::string::npos) |
| { |
| // str is const, so have to make a copy of it first |
| str2 = str; |
| ReplaceAllSubstrings(&str2, "\r", ""); |
| } |
| |
| strings = |
| angle::SplitString(str2.empty() ? str : str2, "\n", WhitespaceHandling::KEEP_WHITESPACE, |
| SplitResult::SPLIT_WANT_ALL); |
| } |
| |
| std::vector<std::string> strings; |
| }; |
| |
| std::ostream &operator<<(std::ostream &ostr, const FmtMultiLineString &fmt); |
| |
| struct SaveFileHelper |
| { |
| public: |
| // We always use ios::binary to avoid inconsistent line endings when captured on Linux vs Win. |
| SaveFileHelper(const std::string &filePathIn) |
| : mOfs(filePathIn, std::ios::binary | std::ios::out), mFilePath(filePathIn) |
| { |
| if (!mOfs.is_open()) |
| { |
| FATAL() << "Could not open " << filePathIn; |
| } |
| } |
| ~SaveFileHelper() { printf("Saved '%s'.\n", mFilePath.c_str()); } |
| |
| template <typename T> |
| SaveFileHelper &operator<<(const T &value) |
| { |
| mOfs << value; |
| if (mOfs.bad()) |
| { |
| FATAL() << "Error writing to " << mFilePath; |
| } |
| return *this; |
| } |
| |
| void write(const uint8_t *data, size_t size) |
| { |
| mOfs.write(reinterpret_cast<const char *>(data), size); |
| } |
| |
| private: |
| void checkError(); |
| |
| std::ofstream mOfs; |
| std::string mFilePath; |
| }; |
| |
| // TODO: Consolidate to C output and remove option. http://anglebug.com/42266223 |
| |
| constexpr char kEnabledVarName[] = "ANGLE_CAPTURE_ENABLED"; |
| constexpr char kOutDirectoryVarName[] = "ANGLE_CAPTURE_OUT_DIR"; |
| constexpr char kFrameStartVarName[] = "ANGLE_CAPTURE_FRAME_START"; |
| constexpr char kFrameEndVarName[] = "ANGLE_CAPTURE_FRAME_END"; |
| constexpr char kTriggerVarName[] = "ANGLE_CAPTURE_TRIGGER"; |
| constexpr char kCaptureLabelVarName[] = "ANGLE_CAPTURE_LABEL"; |
| constexpr char kCompressionVarName[] = "ANGLE_CAPTURE_COMPRESSION"; |
| constexpr char kSerializeStateVarName[] = "ANGLE_CAPTURE_SERIALIZE_STATE"; |
| constexpr char kValidationVarName[] = "ANGLE_CAPTURE_VALIDATION"; |
| constexpr char kValidationExprVarName[] = "ANGLE_CAPTURE_VALIDATION_EXPR"; |
| constexpr char kSourceExtVarName[] = "ANGLE_CAPTURE_SOURCE_EXT"; |
| constexpr char kSourceSizeVarName[] = "ANGLE_CAPTURE_SOURCE_SIZE"; |
| constexpr char kForceShadowVarName[] = "ANGLE_CAPTURE_FORCE_SHADOW"; |
| |
| constexpr size_t kBinaryAlignment = 16; |
| constexpr size_t kFunctionSizeLimit = 5000; |
| |
| // Limit based on MSVC Compiler Error C2026 |
| constexpr size_t kStringLengthLimit = 16380; |
| |
| // Default limit to number of bytes in a capture source files. |
| constexpr char kDefaultSourceFileExt[] = "cpp"; |
| constexpr size_t kDefaultSourceFileSizeThreshold = 400000; |
| |
| // Android debug properties that correspond to the above environment variables |
| constexpr char kAndroidEnabled[] = "debug.angle.capture.enabled"; |
| constexpr char kAndroidOutDir[] = "debug.angle.capture.out_dir"; |
| constexpr char kAndroidFrameStart[] = "debug.angle.capture.frame_start"; |
| constexpr char kAndroidFrameEnd[] = "debug.angle.capture.frame_end"; |
| constexpr char kAndroidTrigger[] = "debug.angle.capture.trigger"; |
| constexpr char kAndroidCaptureLabel[] = "debug.angle.capture.label"; |
| constexpr char kAndroidCompression[] = "debug.angle.capture.compression"; |
| constexpr char kAndroidValidation[] = "debug.angle.capture.validation"; |
| constexpr char kAndroidValidationExpr[] = "debug.angle.capture.validation_expr"; |
| constexpr char kAndroidSourceExt[] = "debug.angle.capture.source_ext"; |
| constexpr char kAndroidSourceSize[] = "debug.angle.capture.source_size"; |
| constexpr char kAndroidForceShadow[] = "debug.angle.capture.force_shadow"; |
| |
| void WriteCppReplayForCall(const CallCapture &call, |
| ReplayWriter &replayWriter, |
| std::ostream &out, |
| std::ostream &header, |
| std::vector<uint8_t> *binaryData, |
| size_t *maxResourceIDBufferSize); |
| |
| void WriteCppReplayForCallCL(const CallCapture &call, |
| ReplayWriter &replayWriter, |
| std::ostream &out, |
| std::ostream &header, |
| std::vector<uint8_t> *binaryData); |
| |
| void WriteBinaryParamReplay(ReplayWriter &replayWriter, |
| std::ostream &out, |
| std::ostream &header, |
| const CallCapture &call, |
| const ParamCapture ¶m, |
| std::vector<uint8_t> *binaryData); |
| |
| std::string GetBinaryDataFilePath(bool compression, const std::string &captureLabel); |
| |
| void SaveBinaryData(bool compression, |
| const std::string &outDir, |
| gl::ContextID contextId, |
| const std::string &captureLabel, |
| const std::vector<uint8_t> &binaryData); |
| |
| void WriteStringPointerParamReplay(ReplayWriter &replayWriter, |
| std::ostream &out, |
| std::ostream &header, |
| const CallCapture &call, |
| const ParamCapture ¶m); |
| |
| void WriteCppReplayFunctionWithParts(const gl::ContextID contextID, |
| ReplayFunc replayFunc, |
| ReplayWriter &replayWriter, |
| uint32_t frameIndex, |
| std::vector<uint8_t> *binaryData, |
| const std::vector<CallCapture> &calls, |
| std::stringstream &header, |
| std::stringstream &out, |
| size_t *maxResourceIDBufferSize); |
| |
| void WriteComment(std::ostream &out, const CallCapture &call); |
| |
| template <typename T, typename CastT = T> |
| void WriteInlineData(const std::vector<uint8_t> &vec, std::ostream &out) |
| { |
| const T *data = reinterpret_cast<const T *>(vec.data()); |
| size_t count = vec.size() / sizeof(T); |
| |
| if (data == nullptr) |
| { |
| return; |
| } |
| |
| out << static_cast<CastT>(data[0]); |
| |
| for (size_t dataIndex = 1; dataIndex < count; ++dataIndex) |
| { |
| out << ", " << static_cast<CastT>(data[dataIndex]); |
| } |
| } |
| |
| template <> |
| void WriteInlineData<GLchar>(const std::vector<uint8_t> &vec, std::ostream &out); |
| |
| void AddComment(std::vector<CallCapture> *outCalls, const std::string &comment); |
| |
| } // namespace angle |
| |
| template <typename T> |
| void CaptureTextureAndSamplerParameter_params(GLenum pname, |
| const T *param, |
| angle::ParamCapture *paramCapture) |
| { |
| if (pname == GL_TEXTURE_BORDER_COLOR || pname == GL_TEXTURE_CROP_RECT_OES) |
| { |
| CaptureMemory(param, sizeof(T) * 4, paramCapture); |
| } |
| else |
| { |
| CaptureMemory(param, sizeof(T), paramCapture); |
| } |
| } |
| |
| namespace egl |
| { |
| angle::ParamCapture CaptureAttributeMap(const egl::AttributeMap &attribMap); |
| } // namespace egl |
| |
| #endif // LIBANGLE_FRAME_CAPTURE_H_ |