| // |
| // 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.cpp: |
| // ANGLE Frame capture GL implementation. |
| // |
| |
| #include "libANGLE/capture/FrameCapture.h" |
| |
| #include <cerrno> |
| #include <cstring> |
| #include <fstream> |
| #include <queue> |
| #include <string> |
| |
| #include "common/aligned_memory.h" |
| #include "common/angle_version_info.h" |
| #include "common/frame_capture_utils.h" |
| #include "common/gl_enum_utils.h" |
| #include "common/mathutil.h" |
| #include "common/serializer/JsonSerializer.h" |
| #include "common/string_utils.h" |
| #include "common/system_utils.h" |
| #include "gpu_info_util/SystemInfo.h" |
| #include "image_util/storeimage.h" |
| #include "libANGLE/Config.h" |
| #include "libANGLE/Context.h" |
| #include "libANGLE/Context.inl.h" |
| #include "libANGLE/Display.h" |
| #include "libANGLE/EGLSync.h" |
| #include "libANGLE/Fence.h" |
| #include "libANGLE/Framebuffer.h" |
| #include "libANGLE/GLES1Renderer.h" |
| #include "libANGLE/Query.h" |
| #include "libANGLE/ResourceMap.h" |
| #include "libANGLE/Shader.h" |
| #include "libANGLE/Surface.h" |
| #include "libANGLE/VertexArray.h" |
| #include "libANGLE/capture/capture_egl_autogen.h" |
| #include "libANGLE/capture/capture_gles_1_0_autogen.h" |
| #include "libANGLE/capture/capture_gles_2_0_autogen.h" |
| #include "libANGLE/capture/capture_gles_3_0_autogen.h" |
| #include "libANGLE/capture/capture_gles_3_1_autogen.h" |
| #include "libANGLE/capture/capture_gles_3_2_autogen.h" |
| #include "libANGLE/capture/capture_gles_ext_autogen.h" |
| #include "libANGLE/capture/serialize.h" |
| #include "libANGLE/entry_points_utils.h" |
| #include "libANGLE/queryconversions.h" |
| #include "libANGLE/queryutils.h" |
| #include "libANGLE/renderer/driver_utils.h" |
| #include "libANGLE/validationEGL.h" |
| #include "third_party/ceval/ceval.h" |
| |
| #define USE_SYSTEM_ZLIB |
| #include "compression_utils_portable.h" |
| |
| #if !ANGLE_CAPTURE_ENABLED |
| # error Frame capture must be enabled to include this file. |
| #endif // !ANGLE_CAPTURE_ENABLED |
| |
| namespace angle |
| { |
| |
| struct FramebufferCaptureFuncs |
| { |
| FramebufferCaptureFuncs(bool isGLES1) |
| { |
| if (isGLES1) |
| { |
| // From GL_OES_framebuffer_object |
| framebufferTexture2D = &gl::CaptureFramebufferTexture2DOES; |
| framebufferRenderbuffer = &gl::CaptureFramebufferRenderbufferOES; |
| bindFramebuffer = &gl::CaptureBindFramebufferOES; |
| genFramebuffers = &gl::CaptureGenFramebuffersOES; |
| bindRenderbuffer = &gl::CaptureBindRenderbufferOES; |
| genRenderbuffers = &gl::CaptureGenRenderbuffersOES; |
| renderbufferStorage = &gl::CaptureRenderbufferStorageOES; |
| } |
| else |
| { |
| framebufferTexture2D = &gl::CaptureFramebufferTexture2D; |
| framebufferRenderbuffer = &gl::CaptureFramebufferRenderbuffer; |
| bindFramebuffer = &gl::CaptureBindFramebuffer; |
| genFramebuffers = &gl::CaptureGenFramebuffers; |
| bindRenderbuffer = &gl::CaptureBindRenderbuffer; |
| genRenderbuffers = &gl::CaptureGenRenderbuffers; |
| renderbufferStorage = &gl::CaptureRenderbufferStorage; |
| } |
| } |
| |
| decltype(&gl::CaptureFramebufferTexture2D) framebufferTexture2D; |
| decltype(&gl::CaptureFramebufferRenderbuffer) framebufferRenderbuffer; |
| decltype(&gl::CaptureBindFramebuffer) bindFramebuffer; |
| decltype(&gl::CaptureGenFramebuffers) genFramebuffers; |
| decltype(&gl::CaptureBindRenderbuffer) bindRenderbuffer; |
| decltype(&gl::CaptureGenRenderbuffers) genRenderbuffers; |
| decltype(&gl::CaptureRenderbufferStorage) renderbufferStorage; |
| }; |
| |
| struct VertexArrayCaptureFuncs |
| { |
| VertexArrayCaptureFuncs(bool isGLES1) |
| { |
| if (isGLES1) |
| { |
| // From GL_OES_vertex_array_object |
| bindVertexArray = &gl::CaptureBindVertexArrayOES; |
| deleteVertexArrays = &gl::CaptureDeleteVertexArraysOES; |
| genVertexArrays = &gl::CaptureGenVertexArraysOES; |
| isVertexArray = &gl::CaptureIsVertexArrayOES; |
| } |
| else |
| { |
| bindVertexArray = &gl::CaptureBindVertexArray; |
| deleteVertexArrays = &gl::CaptureDeleteVertexArrays; |
| genVertexArrays = &gl::CaptureGenVertexArrays; |
| isVertexArray = &gl::CaptureIsVertexArray; |
| } |
| } |
| |
| decltype(&gl::CaptureBindVertexArray) bindVertexArray; |
| decltype(&gl::CaptureDeleteVertexArrays) deleteVertexArrays; |
| decltype(&gl::CaptureGenVertexArrays) genVertexArrays; |
| decltype(&gl::CaptureIsVertexArray) isVertexArray; |
| }; |
| |
| std::string GetCaptureTrigger() |
| { |
| // Use the GetAndSet variant to improve future lookup times |
| return GetAndSetEnvironmentVarOrUnCachedAndroidProperty(kTriggerVarName, kAndroidTrigger); |
| } |
| |
| struct FmtGetSerializedContextStateFunction |
| { |
| FmtGetSerializedContextStateFunction(gl::ContextID contextIdIn, |
| FuncUsage usageIn, |
| uint32_t frameIndexIn) |
| : contextId(contextIdIn), usage(usageIn), frameIndex(frameIndexIn) |
| {} |
| gl::ContextID contextId; |
| FuncUsage usage; |
| uint32_t frameIndex; |
| }; |
| |
| std::ostream &operator<<(std::ostream &os, const FmtGetSerializedContextStateFunction &fmt) |
| { |
| os << "GetSerializedContext" << fmt.contextId << "StateFrame" << fmt.frameIndex << "Data" |
| << fmt.usage; |
| return os; |
| } |
| |
| void WriteGLFloatValue(std::ostream &out, GLfloat value) |
| { |
| // Check for non-representable values |
| ASSERT(std::numeric_limits<float>::has_infinity); |
| ASSERT(std::numeric_limits<float>::has_quiet_NaN); |
| |
| if (std::isinf(value)) |
| { |
| float negativeInf = -std::numeric_limits<float>::infinity(); |
| if (value == negativeInf) |
| { |
| out << "-"; |
| } |
| out << "INFINITY"; |
| } |
| else if (std::isnan(value)) |
| { |
| out << "NAN"; |
| } |
| else |
| { |
| // Write a decimal point to preserve the zero sign on replay |
| out << (value == 0.0 ? std::showpoint : std::noshowpoint); |
| out << std::setprecision(16); |
| out << value; |
| } |
| } |
| |
| void WriteStringParamReplay(ReplayWriter &replayWriter, |
| std::ostream &out, |
| std::ostream &header, |
| const CallCapture &call, |
| const ParamCapture ¶m, |
| std::vector<uint8_t> *binaryData) |
| { |
| const std::vector<uint8_t> &data = param.data[0]; |
| // null terminate C style string |
| ASSERT(data.size() > 0 && data.back() == '\0'); |
| std::string str(data.begin(), data.end() - 1); |
| |
| constexpr size_t kMaxInlineStringLength = 20000; |
| if (str.size() > kMaxInlineStringLength) |
| { |
| // Store in binary file if the string is too long. |
| // Round up to 16-byte boundary for cross ABI safety. |
| size_t offset = rx::roundUpPow2(binaryData->size(), kBinaryAlignment); |
| binaryData->resize(offset + str.size() + 1); |
| memcpy(binaryData->data() + offset, str.data(), str.size() + 1); |
| out << "(const char *)&gBinaryData[" << offset << "]"; |
| } |
| else if (str.find('\n') != std::string::npos) |
| { |
| std::string varName = replayWriter.getInlineVariableName(call.entryPoint, param.name); |
| header << "const char " << varName << "[] = \n" << FmtMultiLineString(str) << ";"; |
| out << varName; |
| } |
| else |
| { |
| out << "\"" << str << "\""; |
| } |
| } |
| |
| enum class Indent |
| { |
| Indent, |
| NoIdent, |
| }; |
| |
| void UpdateResourceIDBuffer(std::ostream &out, |
| Indent indent, |
| size_t bufferIndex, |
| ResourceIDType resourceIDType, |
| gl::ContextID contextID, |
| GLuint resourceID) |
| { |
| if (indent == Indent::Indent) |
| { |
| out << " "; |
| } |
| out << "UpdateResourceIDBuffer(" << bufferIndex << ", g" |
| << GetResourceIDTypeName(resourceIDType) << "Map"; |
| if (IsTrackedPerContext(resourceIDType)) |
| { |
| out << "PerContext[" << contextID.value << "]"; |
| } |
| out << "[" << resourceID << "]);\n"; |
| } |
| |
| template <typename ParamT> |
| void WriteResourceIDPointerParamReplay(ReplayWriter &replayWriter, |
| std::ostream &out, |
| std::ostream &header, |
| const CallCapture &call, |
| const ParamCapture ¶m, |
| size_t *maxResourceIDBufferSize) |
| { |
| const ResourceIDType resourceIDType = GetResourceIDTypeFromParamType(param.type); |
| ASSERT(resourceIDType != ResourceIDType::InvalidEnum); |
| |
| if (param.dataNElements > 0) |
| { |
| ASSERT(param.data.size() == 1); |
| |
| const ParamT *returnedIDs = reinterpret_cast<const ParamT *>(param.data[0].data()); |
| for (GLsizei resIndex = 0; resIndex < param.dataNElements; ++resIndex) |
| { |
| ParamT id = returnedIDs[resIndex]; |
| UpdateResourceIDBuffer(header, Indent::NoIdent, resIndex, resourceIDType, |
| call.contextID, id.value); |
| } |
| |
| *maxResourceIDBufferSize = std::max<size_t>(*maxResourceIDBufferSize, param.dataNElements); |
| } |
| |
| out << "gResourceIDBuffer"; |
| } |
| |
| void WriteCppReplayForCall(const CallCapture &call, |
| ReplayWriter &replayWriter, |
| std::ostream &out, |
| std::ostream &header, |
| std::vector<uint8_t> *binaryData, |
| size_t *maxResourceIDBufferSize) |
| { |
| if (call.customFunctionName == "Comment") |
| { |
| // Just write it directly to the file and move on |
| WriteComment(out, call); |
| return; |
| } |
| |
| std::ostringstream callOut; |
| |
| callOut << call.name() << "("; |
| |
| bool first = true; |
| for (const ParamCapture ¶m : call.params.getParamCaptures()) |
| { |
| if (!first) |
| { |
| callOut << ", "; |
| } |
| |
| if (param.arrayClientPointerIndex != -1 && param.value.voidConstPointerVal != nullptr) |
| { |
| callOut << "gClientArrays[" << param.arrayClientPointerIndex << "]"; |
| } |
| else if (param.readBufferSizeBytes > 0) |
| { |
| callOut << "(" << ParamTypeToString(param.type) << ")gReadBuffer"; |
| } |
| else if (param.data.empty()) |
| { |
| if (param.type == ParamType::TGLenum) |
| { |
| OutputGLenumString(callOut, param.enumGroup, param.value.GLenumVal); |
| } |
| else if (param.type == ParamType::TGLbitfield) |
| { |
| OutputGLbitfieldString(callOut, param.enumGroup, param.value.GLbitfieldVal); |
| } |
| else if (param.type == ParamType::TGLfloat) |
| { |
| WriteGLFloatValue(callOut, param.value.GLfloatVal); |
| } |
| else if (param.type == ParamType::TGLsync) |
| { |
| callOut << "gSyncMap[" << FmtPointerIndex(param.value.GLsyncVal) << "]"; |
| } |
| else if (param.type == ParamType::TGLuint64 && param.name == "timeout") |
| { |
| if (param.value.GLuint64Val == GL_TIMEOUT_IGNORED) |
| { |
| callOut << "GL_TIMEOUT_IGNORED"; |
| } |
| else |
| { |
| WriteParamCaptureReplay(callOut, call, param); |
| } |
| } |
| else |
| { |
| WriteParamCaptureReplay(callOut, call, param); |
| } |
| } |
| else |
| { |
| switch (param.type) |
| { |
| case ParamType::TGLcharConstPointer: |
| WriteStringParamReplay(replayWriter, callOut, header, call, param, binaryData); |
| break; |
| case ParamType::TGLcharConstPointerPointer: |
| WriteStringPointerParamReplay(replayWriter, callOut, header, call, param); |
| break; |
| case ParamType::TBufferIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::BufferID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TFenceNVIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::FenceNVID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TFramebufferIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::FramebufferID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TMemoryObjectIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::MemoryObjectID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TProgramPipelineIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::ProgramPipelineID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TQueryIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::QueryID>(replayWriter, callOut, out, call, |
| param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TRenderbufferIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::RenderbufferID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TSamplerIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::SamplerID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TSemaphoreIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::SemaphoreID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TTextureIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::TextureID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TTransformFeedbackIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::TransformFeedbackID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| case ParamType::TVertexArrayIDConstPointer: |
| WriteResourceIDPointerParamReplay<gl::VertexArrayID>( |
| replayWriter, callOut, out, call, param, maxResourceIDBufferSize); |
| break; |
| default: |
| WriteBinaryParamReplay(replayWriter, callOut, header, call, param, binaryData); |
| break; |
| } |
| } |
| |
| first = false; |
| } |
| |
| callOut << ")"; |
| |
| out << callOut.str(); |
| } |
| |
| size_t MaxClientArraySize(const gl::AttribArray<size_t> &clientArraySizes) |
| { |
| size_t found = 0; |
| for (size_t size : clientArraySizes) |
| { |
| if (size > found) |
| { |
| found = size; |
| } |
| } |
| |
| return found; |
| } |
| |
| void WriteInitReplayCall(bool compression, |
| std::ostream &out, |
| gl::ContextID contextID, |
| const std::string &captureLabel, |
| size_t maxClientArraySize, |
| size_t readBufferSize, |
| size_t resourceIDBufferSize, |
| const PackedEnumMap<ResourceIDType, uint32_t> &maxIDs) |
| { |
| std::string binaryDataFileName = GetBinaryDataFilePath(compression, captureLabel); |
| |
| out << " // binaryDataFileName = " << binaryDataFileName << "\n"; |
| out << " // maxClientArraySize = " << maxClientArraySize << "\n"; |
| out << " // readBufferSize = " << readBufferSize << "\n"; |
| out << " // resourceIDBufferSize = " << resourceIDBufferSize << "\n"; |
| out << " // contextID = " << contextID << "\n"; |
| for (ResourceIDType resourceID : AllEnums<ResourceIDType>()) |
| { |
| const char *name = GetResourceIDTypeName(resourceID); |
| out << " // max" << name << " = " << maxIDs[resourceID] << "\n"; |
| } |
| out << " InitializeReplay4(\"" << binaryDataFileName << "\", " << maxClientArraySize << ", " |
| << readBufferSize << ", " << resourceIDBufferSize << ", " << contextID; |
| |
| for (ResourceIDType resourceID : AllEnums<ResourceIDType>()) |
| { |
| // Sanity check for catching e.g. uninitialized memory reads like b/380296979 |
| ASSERT(maxIDs[resourceID] < 1000000); |
| out << ", " << maxIDs[resourceID]; |
| } |
| |
| out << ");\n"; |
| } |
| |
| void DeleteResourcesInReset(std::stringstream &out, |
| const gl::ContextID contextID, |
| const ResourceSet &newResources, |
| const ResourceSet &resourcesToDelete, |
| const ResourceIDType resourceIDType, |
| size_t *maxResourceIDBufferSize) |
| { |
| if (!newResources.empty() || !resourcesToDelete.empty()) |
| { |
| size_t count = 0; |
| |
| for (GLuint oldResource : resourcesToDelete) |
| { |
| UpdateResourceIDBuffer(out, Indent::Indent, count++, resourceIDType, contextID, |
| oldResource); |
| } |
| |
| for (GLuint newResource : newResources) |
| { |
| UpdateResourceIDBuffer(out, Indent::Indent, count++, resourceIDType, contextID, |
| newResource); |
| } |
| |
| // Delete all the new and old buffers at once |
| out << " glDelete" << GetResourceIDTypeName(resourceIDType) << "s(" << count |
| << ", gResourceIDBuffer);\n"; |
| |
| *maxResourceIDBufferSize = std::max(*maxResourceIDBufferSize, count); |
| } |
| } |
| |
| // TODO (http://anglebug.com/42263204): Reset more state on frame loop |
| void MaybeResetResources(egl::Display *display, |
| gl::ContextID contextID, |
| ResourceIDType resourceIDType, |
| ReplayWriter &replayWriter, |
| std::stringstream &out, |
| std::stringstream &header, |
| ResourceTracker *resourceTracker, |
| std::vector<uint8_t> *binaryData, |
| bool &anyResourceReset, |
| size_t *maxResourceIDBufferSize) |
| { |
| // Track the initial output position so we can detect if it has moved |
| std::streampos initialOutPos = out.tellp(); |
| |
| switch (resourceIDType) |
| { |
| case ResourceIDType::Buffer: |
| { |
| TrackedResource &trackedBuffers = |
| resourceTracker->getTrackedResource(contextID, ResourceIDType::Buffer); |
| ResourceSet &newBuffers = trackedBuffers.getNewResources(); |
| ResourceSet &buffersToDelete = trackedBuffers.getResourcesToDelete(); |
| ResourceSet &buffersToRegen = trackedBuffers.getResourcesToRegen(); |
| ResourceCalls &bufferRegenCalls = trackedBuffers.getResourceRegenCalls(); |
| ResourceCalls &bufferRestoreCalls = trackedBuffers.getResourceRestoreCalls(); |
| |
| BufferCalls &bufferMapCalls = resourceTracker->getBufferMapCalls(); |
| BufferCalls &bufferUnmapCalls = resourceTracker->getBufferUnmapCalls(); |
| |
| DeleteResourcesInReset(out, contextID, newBuffers, buffersToDelete, resourceIDType, |
| maxResourceIDBufferSize); |
| |
| // If any of our starting buffers were deleted during the run, recreate them |
| for (GLuint id : buffersToRegen) |
| { |
| // Emit their regen calls |
| for (CallCapture &call : bufferRegenCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| // If any of our starting buffers were modified during the run, restore their contents |
| ResourceSet &buffersToRestore = trackedBuffers.getResourcesToRestore(); |
| for (GLuint id : buffersToRestore) |
| { |
| if (resourceTracker->getStartingBuffersMappedCurrent(id)) |
| { |
| // Some drivers require the buffer to be unmapped before you can update data, |
| // which violates the spec. See gl::Buffer::bufferDataImpl(). |
| for (CallCapture &call : bufferUnmapCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| // Emit their restore calls |
| for (CallCapture &call : bufferRestoreCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| |
| // Also note that this buffer has been implicitly unmapped by this call |
| resourceTracker->setBufferUnmapped(contextID, id); |
| } |
| } |
| |
| // Update the map/unmap of buffers to match the starting state |
| ResourceSet startingBuffers = trackedBuffers.getStartingResources(); |
| for (GLuint id : startingBuffers) |
| { |
| // If the buffer was mapped at the start, but is not mapped now, we need to map |
| if (resourceTracker->getStartingBuffersMappedInitial(id) && |
| !resourceTracker->getStartingBuffersMappedCurrent(id)) |
| { |
| // Emit their map calls |
| for (CallCapture &call : bufferMapCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| // If the buffer was unmapped at the start, but is mapped now, we need to unmap |
| if (!resourceTracker->getStartingBuffersMappedInitial(id) && |
| resourceTracker->getStartingBuffersMappedCurrent(id)) |
| { |
| // Emit their unmap calls |
| for (CallCapture &call : bufferUnmapCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| } |
| break; |
| } |
| case ResourceIDType::Framebuffer: |
| { |
| TrackedResource &trackedFramebuffers = |
| resourceTracker->getTrackedResource(contextID, ResourceIDType::Framebuffer); |
| ResourceSet &newFramebuffers = trackedFramebuffers.getNewResources(); |
| ResourceSet &framebuffersToDelete = trackedFramebuffers.getResourcesToDelete(); |
| ResourceSet &framebuffersToRegen = trackedFramebuffers.getResourcesToRegen(); |
| ResourceCalls &framebufferRegenCalls = trackedFramebuffers.getResourceRegenCalls(); |
| ResourceCalls &framebufferRestoreCalls = trackedFramebuffers.getResourceRestoreCalls(); |
| |
| DeleteResourcesInReset(out, contextID, newFramebuffers, framebuffersToDelete, |
| resourceIDType, maxResourceIDBufferSize); |
| |
| for (GLuint id : framebuffersToRegen) |
| { |
| // Emit their regen calls |
| for (CallCapture &call : framebufferRegenCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| // If any of our starting framebuffers were modified during the run, restore their |
| // contents |
| ResourceSet &framebuffersToRestore = trackedFramebuffers.getResourcesToRestore(); |
| for (GLuint id : framebuffersToRestore) |
| { |
| // Emit their restore calls |
| for (CallCapture &call : framebufferRestoreCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| break; |
| } |
| case ResourceIDType::Renderbuffer: |
| { |
| TrackedResource &trackedRenderbuffers = |
| resourceTracker->getTrackedResource(contextID, ResourceIDType::Renderbuffer); |
| ResourceSet &newRenderbuffers = trackedRenderbuffers.getNewResources(); |
| ResourceSet &renderbuffersToDelete = trackedRenderbuffers.getResourcesToDelete(); |
| ResourceSet &renderbuffersToRegen = trackedRenderbuffers.getResourcesToRegen(); |
| ResourceCalls &renderbufferRegenCalls = trackedRenderbuffers.getResourceRegenCalls(); |
| ResourceCalls &renderbufferRestoreCalls = |
| trackedRenderbuffers.getResourceRestoreCalls(); |
| |
| DeleteResourcesInReset(out, contextID, newRenderbuffers, renderbuffersToDelete, |
| resourceIDType, maxResourceIDBufferSize); |
| |
| for (GLuint id : renderbuffersToRegen) |
| { |
| // Emit their regen calls |
| for (CallCapture &call : renderbufferRegenCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| // If any of our starting renderbuffers were modified during the run, restore their |
| // contents |
| ResourceSet &renderbuffersToRestore = trackedRenderbuffers.getResourcesToRestore(); |
| for (GLuint id : renderbuffersToRestore) |
| { |
| // Emit their restore calls |
| for (CallCapture &call : renderbufferRestoreCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| break; |
| } |
| case ResourceIDType::ShaderProgram: |
| { |
| TrackedResource &trackedShaderPrograms = |
| resourceTracker->getTrackedResource(contextID, ResourceIDType::ShaderProgram); |
| ResourceSet &newShaderPrograms = trackedShaderPrograms.getNewResources(); |
| ResourceSet &shaderProgramsToDelete = trackedShaderPrograms.getResourcesToDelete(); |
| ResourceSet &shaderProgramsToRegen = trackedShaderPrograms.getResourcesToRegen(); |
| ResourceSet &shaderProgramsToRestore = trackedShaderPrograms.getResourcesToRestore(); |
| ResourceCalls &shaderProgramRegenCalls = trackedShaderPrograms.getResourceRegenCalls(); |
| ResourceCalls &shaderProgramRestoreCalls = |
| trackedShaderPrograms.getResourceRestoreCalls(); |
| |
| // If we have any new shaders or programs created and not deleted during the run, delete |
| // them now |
| for (const GLuint &newShaderProgram : newShaderPrograms) |
| { |
| if (resourceTracker->getShaderProgramType({newShaderProgram}) == |
| ShaderProgramType::ShaderType) |
| { |
| out << " glDeleteShader(gShaderProgramMap[" << newShaderProgram << "]);\n"; |
| } |
| else |
| { |
| ASSERT(resourceTracker->getShaderProgramType({newShaderProgram}) == |
| ShaderProgramType::ProgramType); |
| out << " glDeleteProgram(gShaderProgramMap[" << newShaderProgram << "]);\n"; |
| } |
| } |
| |
| // Do the same for shaders/programs to be deleted |
| for (const GLuint &shaderProgramToDelete : shaderProgramsToDelete) |
| { |
| if (resourceTracker->getShaderProgramType({shaderProgramToDelete}) == |
| ShaderProgramType::ShaderType) |
| { |
| out << " glDeleteShader(gShaderProgramMap[" << shaderProgramToDelete |
| << "]);\n"; |
| } |
| else |
| { |
| ASSERT(resourceTracker->getShaderProgramType({shaderProgramToDelete}) == |
| ShaderProgramType::ProgramType); |
| out << " glDeleteProgram(gShaderProgramMap[" << shaderProgramToDelete |
| << "]);\n"; |
| } |
| } |
| |
| for (const GLuint id : shaderProgramsToRegen) |
| { |
| // Emit their regen calls |
| for (CallCapture &call : shaderProgramRegenCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| for (const GLuint id : shaderProgramsToRestore) |
| { |
| // Emit their restore calls |
| for (CallCapture &call : shaderProgramRestoreCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| break; |
| } |
| case ResourceIDType::Texture: |
| { |
| TrackedResource &trackedTextures = |
| resourceTracker->getTrackedResource(contextID, ResourceIDType::Texture); |
| ResourceSet &newTextures = trackedTextures.getNewResources(); |
| ResourceSet &texturesToDelete = trackedTextures.getResourcesToDelete(); |
| ResourceSet &texturesToRegen = trackedTextures.getResourcesToRegen(); |
| ResourceCalls &textureRegenCalls = trackedTextures.getResourceRegenCalls(); |
| ResourceCalls &textureRestoreCalls = trackedTextures.getResourceRestoreCalls(); |
| |
| DeleteResourcesInReset(out, contextID, newTextures, texturesToDelete, resourceIDType, |
| maxResourceIDBufferSize); |
| |
| // If any of our starting textures were deleted, regen them |
| for (GLuint id : texturesToRegen) |
| { |
| // Emit their regen calls |
| for (CallCapture &call : textureRegenCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| // If any of our starting textures were modified during the run, restore their contents |
| ResourceSet &texturesToRestore = trackedTextures.getResourcesToRestore(); |
| |
| // Do some setup if we have any textures to restore |
| if (texturesToRestore.size() != 0) |
| { |
| // We need to unbind PIXEL_UNPACK_BUFFER before restoring textures |
| // The correct binding will be restored in context state reset |
| gl::Context *context = display->getContext(contextID); |
| if (context->getState().getTargetBuffer(gl::BufferBinding::PixelUnpack)) |
| { |
| out << " // Clearing PIXEL_UNPACK_BUFFER binding for texture restore\n"; |
| out << " "; |
| WriteCppReplayForCall(CaptureBindBuffer(context->getState(), true, |
| gl::BufferBinding::PixelUnpack, {0}), |
| replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| for (GLuint id : texturesToRestore) |
| { |
| // Emit their restore calls |
| for (CallCapture &call : textureRestoreCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| break; |
| } |
| case ResourceIDType::VertexArray: |
| { |
| TrackedResource &trackedVertexArrays = |
| resourceTracker->getTrackedResource(contextID, ResourceIDType::VertexArray); |
| ResourceSet &newVertexArrays = trackedVertexArrays.getNewResources(); |
| ResourceSet &vertexArraysToDelete = trackedVertexArrays.getResourcesToDelete(); |
| ResourceSet &vertexArraysToRegen = trackedVertexArrays.getResourcesToRegen(); |
| ResourceSet &vertexArraysToRestore = trackedVertexArrays.getResourcesToRestore(); |
| ResourceCalls &vertexArrayRegenCalls = trackedVertexArrays.getResourceRegenCalls(); |
| ResourceCalls &vertexArrayRestoreCalls = trackedVertexArrays.getResourceRestoreCalls(); |
| |
| DeleteResourcesInReset(out, contextID, newVertexArrays, vertexArraysToDelete, |
| resourceIDType, maxResourceIDBufferSize); |
| |
| // If any of our starting vertex arrays were deleted during the run, recreate them |
| for (GLuint id : vertexArraysToRegen) |
| { |
| // Emit their regen calls |
| for (CallCapture &call : vertexArrayRegenCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| // If any of our starting vertex arrays were modified during the run, restore their |
| // contents |
| for (GLuint id : vertexArraysToRestore) |
| { |
| // Emit their restore calls |
| for (CallCapture &call : vertexArrayRestoreCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| break; |
| } |
| case ResourceIDType::egl_Sync: |
| { |
| TrackedResource &trackedEGLSyncs = |
| resourceTracker->getTrackedResource(contextID, ResourceIDType::egl_Sync); |
| ResourceSet &newEGLSyncs = trackedEGLSyncs.getNewResources(); |
| ResourceSet &eglSyncsToDelete = trackedEGLSyncs.getResourcesToDelete(); |
| ResourceSet &eglSyncsToRegen = trackedEGLSyncs.getResourcesToRegen(); |
| ResourceCalls &eglSyncRegenCalls = trackedEGLSyncs.getResourceRegenCalls(); |
| |
| if (!newEGLSyncs.empty() || !eglSyncsToDelete.empty()) |
| { |
| for (GLuint oldResource : eglSyncsToDelete) |
| { |
| out << " eglDestroySyncKHR(gEGLDisplay, gEGLSyncMap[" << oldResource |
| << "]);\n"; |
| } |
| |
| for (GLuint newResource : newEGLSyncs) |
| { |
| out << " eglDestroySyncKHR(gEGLDisplay, gEGLSyncMap[" << newResource |
| << "]);\n"; |
| } |
| } |
| |
| // If any of our starting EGLsyncs were deleted during the run, recreate them |
| for (GLuint id : eglSyncsToRegen) |
| { |
| // Emit their regen calls |
| for (CallCapture &call : eglSyncRegenCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| break; |
| } |
| case ResourceIDType::Image: |
| { |
| TrackedResource &trackedEGLImages = |
| resourceTracker->getTrackedResource(contextID, ResourceIDType::Image); |
| ResourceSet &newEGLImages = trackedEGLImages.getNewResources(); |
| ResourceSet &eglImagesToDelete = trackedEGLImages.getResourcesToDelete(); |
| ResourceSet &eglImagesToRegen = trackedEGLImages.getResourcesToRegen(); |
| ResourceCalls &eglImageRegenCalls = trackedEGLImages.getResourceRegenCalls(); |
| |
| if (!newEGLImages.empty() || !eglImagesToDelete.empty()) |
| { |
| for (GLuint oldResource : eglImagesToDelete) |
| { |
| out << " DestroyEGLImageKHR(gEGLDisplay, gEGLImageMap2[" << oldResource |
| << "], " << oldResource << ");\n"; |
| } |
| |
| for (GLuint newResource : newEGLImages) |
| { |
| out << " DestroyEGLImageKHR(gEGLDisplay, gEGLImageMap2[" << newResource |
| << "], " << newResource << ");\n"; |
| } |
| } |
| // If any of our starting EGLImages were deleted during the run, recreate them |
| for (GLuint id : eglImagesToRegen) |
| { |
| // Emit their regen calls |
| for (CallCapture &call : eglImageRegenCalls[id]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| break; |
| } |
| default: |
| // TODO (http://anglebug.com/42263204): Reset more resource types |
| break; |
| } |
| |
| // If the output position has moved, we Reset something |
| anyResourceReset = (initialOutPos != out.tellp()); |
| } |
| |
| void MaybeResetFenceSyncObjects(std::stringstream &out, |
| ReplayWriter &replayWriter, |
| std::stringstream &header, |
| ResourceTracker *resourceTracker, |
| std::vector<uint8_t> *binaryData, |
| size_t *maxResourceIDBufferSize) |
| { |
| FenceSyncCalls &fenceSyncRegenCalls = resourceTracker->getFenceSyncRegenCalls(); |
| |
| // If any of our starting fence sync objects were deleted during the run, recreate them |
| FenceSyncSet &fenceSyncsToRegen = resourceTracker->getFenceSyncsToRegen(); |
| for (const gl::SyncID syncID : fenceSyncsToRegen) |
| { |
| // Emit their regen calls |
| for (CallCapture &call : fenceSyncRegenCalls[syncID]) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| } |
| |
| void Capture(std::vector<CallCapture> *setupCalls, CallCapture &&call) |
| { |
| setupCalls->emplace_back(std::move(call)); |
| } |
| |
| void CaptureUpdateCurrentProgram(const CallCapture &call, |
| int programParamPos, |
| std::vector<CallCapture> *callsOut) |
| { |
| const ParamCapture ¶m = |
| call.params.getParam("programPacked", ParamType::TShaderProgramID, programParamPos); |
| gl::ShaderProgramID programID = param.value.ShaderProgramIDVal; |
| |
| ParamBuffer paramBuffer; |
| paramBuffer.addValueParam("program", ParamType::TGLuint, programID.value); |
| |
| callsOut->emplace_back("UpdateCurrentProgram", std::move(paramBuffer)); |
| } |
| |
| bool ProgramNeedsReset(const gl::Context *context, |
| ResourceTracker *resourceTracker, |
| gl::ShaderProgramID programID) |
| { |
| // Check whether the program is listed in programs to regen or restore |
| TrackedResource &trackedShaderPrograms = |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::ShaderProgram); |
| |
| ResourceSet &shaderProgramsToRegen = trackedShaderPrograms.getResourcesToRegen(); |
| if (shaderProgramsToRegen.count(programID.value) != 0) |
| { |
| return true; |
| } |
| |
| ResourceSet &shaderProgramsToRestore = trackedShaderPrograms.getResourcesToRestore(); |
| if (shaderProgramsToRestore.count(programID.value) != 0) |
| { |
| return true; |
| } |
| |
| // Deferred linked programs will also update their own uniforms |
| FrameCaptureShared *frameCaptureShared = context->getShareGroup()->getFrameCaptureShared(); |
| if (frameCaptureShared->isDeferredLinkProgram(programID)) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void MaybeResetDefaultUniforms(std::stringstream &out, |
| ReplayWriter &replayWriter, |
| std::stringstream &header, |
| const gl::Context *context, |
| ResourceTracker *resourceTracker, |
| std::vector<uint8_t> *binaryData, |
| size_t *maxResourceIDBufferSize) |
| { |
| DefaultUniformLocationsPerProgramMap &defaultUniformsToReset = |
| resourceTracker->getDefaultUniformsToReset(); |
| |
| for (const auto &uniformIter : defaultUniformsToReset) |
| { |
| gl::ShaderProgramID programID = uniformIter.first; |
| const DefaultUniformLocationsSet &locations = uniformIter.second; |
| |
| if (ProgramNeedsReset(context, resourceTracker, programID)) |
| { |
| // Skip programs marked for reset as they will update their own uniforms |
| return; |
| } |
| |
| // Bind the program to update its uniforms |
| std::vector<CallCapture> bindCalls; |
| Capture(&bindCalls, CaptureUseProgram(context->getState(), true, programID)); |
| CaptureUpdateCurrentProgram((&bindCalls)->back(), 0, &bindCalls); |
| for (CallCapture &call : bindCalls) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| |
| DefaultUniformCallsPerLocationMap &defaultUniformResetCalls = |
| resourceTracker->getDefaultUniformResetCalls(programID); |
| |
| // Uniform arrays might have been modified in the middle (i.e. location 5 out of 10) |
| // We only have Reset calls for the entire array, so emit them once for the entire array |
| std::set<gl::UniformLocation> alreadyReset; |
| |
| // Emit the reset calls per modified location |
| for (const gl::UniformLocation &location : locations) |
| { |
| gl::UniformLocation baseLocation = |
| resourceTracker->getDefaultUniformBaseLocation(programID, location); |
| if (alreadyReset.find(baseLocation) != alreadyReset.end()) |
| { |
| // We've already Reset this array |
| continue; |
| } |
| alreadyReset.insert(baseLocation); |
| |
| ASSERT(defaultUniformResetCalls.find(baseLocation) != defaultUniformResetCalls.end()); |
| std::vector<CallCapture> &callsPerLocation = defaultUniformResetCalls[baseLocation]; |
| |
| for (CallCapture &call : callsPerLocation) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| } |
| } |
| |
| void MaybeResetOpaqueTypeObjects(ReplayWriter &replayWriter, |
| std::stringstream &out, |
| std::stringstream &header, |
| const gl::Context *context, |
| ResourceTracker *resourceTracker, |
| std::vector<uint8_t> *binaryData, |
| size_t *maxResourceIDBufferSize) |
| { |
| MaybeResetFenceSyncObjects(out, replayWriter, header, resourceTracker, binaryData, |
| maxResourceIDBufferSize); |
| |
| MaybeResetDefaultUniforms(out, replayWriter, header, context, resourceTracker, binaryData, |
| maxResourceIDBufferSize); |
| } |
| |
| void MaybeResetContextState(ReplayWriter &replayWriter, |
| std::stringstream &out, |
| std::stringstream &header, |
| ResourceTracker *resourceTracker, |
| const gl::Context *context, |
| std::vector<uint8_t> *binaryData, |
| StateResetHelper &stateResetHelper, |
| size_t *maxResourceIDBufferSize) |
| { |
| // Check dirty states per entrypoint |
| for (const EntryPoint &entryPoint : stateResetHelper.getDirtyEntryPoints()) |
| { |
| const CallResetMap *resetCalls = &stateResetHelper.getResetCalls(); |
| |
| // Create the default reset call for this entrypoint |
| if (resetCalls->find(entryPoint) == resetCalls->end()) |
| { |
| // If we don't have any reset calls for these entrypoints, that means we started capture |
| // from the beginning, amd mid-execution capture was not invoked. |
| stateResetHelper.setDefaultResetCalls(context, entryPoint); |
| } |
| |
| // Emit the calls, if we added any |
| if (resetCalls->find(entryPoint) != resetCalls->end()) |
| { |
| for (const auto &call : resetCalls->at(entryPoint)) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| } |
| |
| // Reset buffer bindings that weren't bound at the beginning |
| for (const gl::BufferBinding &dirtyBufferBinding : stateResetHelper.getDirtyBufferBindings()) |
| { |
| // Check to see if dirty binding was part of starting set |
| bool dirtyStartingBinding = false; |
| for (const BufferBindingPair &startingBufferBinding : |
| stateResetHelper.getStartingBufferBindings()) |
| { |
| gl::BufferBinding startingBinding = startingBufferBinding.first; |
| if (startingBinding == dirtyBufferBinding) |
| { |
| dirtyStartingBinding = true; |
| } |
| } |
| |
| // If the dirty binding was not part of starting bindings, clear it |
| if (!dirtyStartingBinding) |
| { |
| out << " "; |
| WriteCppReplayForCall( |
| CaptureBindBuffer(context->getState(), true, dirtyBufferBinding, {0}), replayWriter, |
| out, header, binaryData, maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| // Restore starting buffer bindings to initial state |
| std::vector<CallCapture> &bufferBindingCalls = resourceTracker->getBufferBindingCalls(); |
| for (CallCapture &call : bufferBindingCalls) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| |
| // Restore texture bindings to initial state |
| size_t activeTexture = context->getState().getActiveSampler(); |
| const TextureResetMap &resetBindings = stateResetHelper.getResetTextureBindings(); |
| for (const auto &textureBinding : stateResetHelper.getDirtyTextureBindings()) |
| { |
| TextureResetMap::const_iterator id = resetBindings.find(textureBinding); |
| if (id != resetBindings.end()) |
| { |
| const auto &[unit, target] = textureBinding; |
| |
| // Set active texture unit if necessary |
| if (unit != activeTexture) |
| { |
| out << " "; |
| WriteCppReplayForCall(CaptureActiveTexture(context->getState(), true, |
| GL_TEXTURE0 + static_cast<GLenum>(unit)), |
| replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| activeTexture = unit; |
| } |
| |
| // Bind texture for this target |
| out << " "; |
| WriteCppReplayForCall(CaptureBindTexture(context->getState(), true, target, id->second), |
| replayWriter, out, header, binaryData, maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| // Restore active texture unit to initial state if necessary |
| if (activeTexture != stateResetHelper.getResetActiveTexture()) |
| { |
| out << " "; |
| WriteCppReplayForCall( |
| CaptureActiveTexture( |
| context->getState(), true, |
| GL_TEXTURE0 + static_cast<GLenum>(stateResetHelper.getResetActiveTexture())), |
| replayWriter, out, header, binaryData, maxResourceIDBufferSize); |
| out << ";\n"; |
| } |
| } |
| |
| void MarkResourceIDActive(ResourceIDType resourceType, |
| GLuint id, |
| std::vector<CallCapture> *setupCalls, |
| const ResourceIDToSetupCallsMap *resourceIDToSetupCallsMap) |
| { |
| const std::map<GLuint, gl::Range<size_t>> &resourceSetupCalls = |
| (*resourceIDToSetupCallsMap)[resourceType]; |
| const auto iter = resourceSetupCalls.find(id); |
| if (iter == resourceSetupCalls.end()) |
| { |
| return; |
| } |
| |
| // Mark all of the calls that were used to initialize this resource as ACTIVE |
| const gl::Range<size_t> &calls = iter->second; |
| for (size_t index : calls) |
| { |
| (*setupCalls)[index].isActive = true; |
| } |
| } |
| |
| // Some replay functions can get quite large. If over a certain size, this method breaks up the |
| // function into parts to avoid overflowing the stack and causing slow compilation. |
| 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) |
| { |
| int callCount = 0; |
| int partCount = 0; |
| |
| if (calls.size() > kFunctionSizeLimit) |
| { |
| out << "void " |
| << FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, ++partCount) |
| << "\n"; |
| } |
| else |
| { |
| out << "void " |
| << FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, kNoPartId) |
| << "\n"; |
| } |
| |
| out << "{\n"; |
| |
| for (const CallCapture &call : calls) |
| { |
| // Process active calls for Setup and inactive calls for SetupInactive |
| if ((call.isActive && replayFunc != ReplayFunc::SetupInactive) || |
| (!call.isActive && replayFunc == ReplayFunc::SetupInactive)) |
| { |
| out << " "; |
| WriteCppReplayForCall(call, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| |
| if (partCount > 0 && ++callCount % kFunctionSizeLimit == 0) |
| { |
| out << "}\n"; |
| out << "\n"; |
| out << "void " |
| << FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, |
| ++partCount) |
| << "\n"; |
| out << "{\n"; |
| } |
| } |
| } |
| out << "}\n"; |
| |
| if (partCount > 0) |
| { |
| out << "\n"; |
| out << "void " |
| << FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, kNoPartId) |
| << "\n"; |
| out << "{\n"; |
| |
| // Write out the main call which calls all the parts. |
| for (int i = 1; i <= partCount; i++) |
| { |
| out << " " << FmtFunction(replayFunc, contextID, FuncUsage::Call, frameIndex, i) |
| << ";\n"; |
| } |
| |
| out << "}\n"; |
| } |
| } |
| |
| // Performance can be gained by reordering traced calls and grouping them by context. |
| // Side context calls (as opposed to main context) can be grouped together paying attention |
| // to synchronization points in the original call stream. |
| void WriteCppReplayFunctionWithPartsMultiContext(const gl::ContextID contextID, |
| ReplayFunc replayFunc, |
| ReplayWriter &replayWriter, |
| uint32_t frameIndex, |
| std::vector<uint8_t> *binaryData, |
| std::vector<CallCapture> &calls, |
| std::stringstream &header, |
| std::stringstream &out, |
| size_t *maxResourceIDBufferSize) |
| { |
| int callCount = 0; |
| int partCount = 0; |
| |
| if (calls.size() > kFunctionSizeLimit) |
| { |
| out << "void " |
| << FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, ++partCount) |
| << "\n"; |
| } |
| else |
| { |
| out << "void " |
| << FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, kNoPartId) |
| << "\n"; |
| } |
| |
| out << "{\n"; |
| |
| std::map<gl::ContextID, std::queue<int>> sideContextCallIndices; |
| |
| // Helper lambda to write a context change command to the call stream |
| auto writeMakeCurrentCall = [&](gl::ContextID cID) { |
| CallCapture makeCurrentCall = |
| egl::CaptureMakeCurrent(nullptr, true, nullptr, {0}, {0}, cID, EGL_TRUE); |
| out << " "; |
| WriteCppReplayForCall(makeCurrentCall, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| callCount++; |
| }; |
| |
| // Helper lambda to write a call to the call stream |
| auto writeCall = [&](CallCapture &outCall, gl::ContextID cID) { |
| out << " "; |
| WriteCppReplayForCall(outCall, replayWriter, out, header, binaryData, |
| maxResourceIDBufferSize); |
| out << ";\n"; |
| if (cID != contextID) |
| { |
| sideContextCallIndices[cID].pop(); |
| } |
| callCount++; |
| }; |
| |
| int callIndex = 0; |
| // Iterate through calls saving side context call indices in a per-side-context queue |
| for (CallCapture &call : calls) |
| { |
| if (call.contextID != contextID) |
| { |
| sideContextCallIndices[call.contextID].push(callIndex); |
| } |
| callIndex++; |
| } |
| |
| // At the beginning of the frame, output all side context calls occuring before a sync point. |
| // If no sync points are present, all calls in that side context are written at this time |
| for (auto const &sideContext : sideContextCallIndices) |
| { |
| gl::ContextID sideContextID = sideContext.first; |
| |
| // Make sidecontext current if there are commands before the first syncpoint |
| if (!calls[sideContextCallIndices[sideContextID].front()].isSyncPoint) |
| { |
| writeMakeCurrentCall(sideContextID); |
| } |
| // Output all commands in sidecontext until a syncpoint is reached |
| while (!sideContextCallIndices[sideContextID].empty() && |
| !calls[sideContextCallIndices[sideContextID].front()].isSyncPoint) |
| { |
| writeCall(calls[sideContextCallIndices[sideContextID].front()], sideContextID); |
| } |
| } |
| |
| // Make mainContext current |
| writeMakeCurrentCall(contextID); |
| |
| // Iterate through calls writing out main context calls. When a sync point is reached, write the |
| // next queued sequence of side context calls until another sync point is reached. |
| for (CallCapture &call : calls) |
| { |
| if (call.contextID == contextID) |
| { |
| writeCall(call, call.contextID); |
| } |
| else |
| { |
| if (call.isSyncPoint) |
| { |
| // Make sideContext current |
| writeMakeCurrentCall(call.contextID); |
| |
| do |
| { |
| writeCall(calls[sideContextCallIndices[call.contextID].front()], |
| call.contextID); |
| } while (!sideContextCallIndices[call.contextID].empty() && |
| !calls[sideContextCallIndices[call.contextID].front()].isSyncPoint); |
| |
| // Make mainContext current |
| writeMakeCurrentCall(contextID); |
| |
| if (partCount > 0 && ++callCount % kFunctionSizeLimit == 0) |
| { |
| out << "}\n"; |
| out << "\n"; |
| out << "void " |
| << FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, |
| ++partCount) |
| << "\n"; |
| out << "{\n"; |
| } |
| } |
| } |
| } |
| out << "}\n"; |
| |
| if (partCount > 0) |
| { |
| out << "\n"; |
| out << "void " |
| << FmtFunction(replayFunc, contextID, FuncUsage::Definition, frameIndex, kNoPartId) |
| << "\n"; |
| out << "{\n"; |
| |
| // Write out the main call which calls all the parts. |
| for (int i = 1; i <= partCount; i++) |
| { |
| out << " " << FmtFunction(replayFunc, contextID, FuncUsage::Call, frameIndex, i) |
| << ";\n"; |
| } |
| |
| out << "}\n"; |
| } |
| } |
| |
| // Auxiliary contexts are other contexts in the share group that aren't the context calling |
| // eglSwapBuffers(). |
| void WriteAuxiliaryContextCppSetupReplay(ReplayWriter &replayWriter, |
| bool compression, |
| const std::string &outDir, |
| const gl::Context *context, |
| const std::string &captureLabel, |
| uint32_t frameIndex, |
| const std::vector<CallCapture> &setupCalls, |
| std::vector<uint8_t> *binaryData, |
| bool serializeStateEnabled, |
| const FrameCaptureShared &frameCaptureShared, |
| size_t *maxResourceIDBufferSize) |
| { |
| ASSERT(frameCaptureShared.getWindowSurfaceContextID() != context->id()); |
| |
| { |
| std::stringstream filenameStream; |
| filenameStream << outDir << FmtCapturePrefix(context->id(), captureLabel); |
| std::string filenamePattern = filenameStream.str(); |
| replayWriter.setFilenamePattern(filenamePattern); |
| } |
| |
| { |
| std::stringstream include; |
| include << "#include \"" |
| << FmtCapturePrefix(frameCaptureShared.getWindowSurfaceContextID(), captureLabel) |
| << ".h\"\n"; |
| include << "#include \"angle_trace_gl.h\"\n"; |
| |
| std::string frameIncludes = include.str(); |
| replayWriter.setSourcePrologue(frameIncludes); |
| replayWriter.setHeaderPrologue(frameIncludes); |
| } |
| |
| { |
| std::stringstream protoStream; |
| std::stringstream headerStream; |
| std::stringstream bodyStream; |
| |
| protoStream << "void " << FmtSetupFunction(kNoPartId, context->id(), FuncUsage::Prototype); |
| std::string proto = protoStream.str(); |
| |
| WriteCppReplayFunctionWithParts(context->id(), ReplayFunc::Setup, replayWriter, frameIndex, |
| binaryData, setupCalls, headerStream, bodyStream, |
| maxResourceIDBufferSize); |
| |
| replayWriter.addPrivateFunction(proto, headerStream, bodyStream); |
| } |
| |
| replayWriter.saveFrame(); |
| } |
| |
| void WriteShareGroupCppSetupReplay(ReplayWriter &replayWriter, |
| bool compression, |
| const std::string &outDir, |
| const std::string &captureLabel, |
| uint32_t frameIndex, |
| uint32_t frameCount, |
| const std::vector<CallCapture> &setupCalls, |
| ResourceTracker *resourceTracker, |
| std::vector<uint8_t> *binaryData, |
| bool serializeStateEnabled, |
| gl::ContextID windowSurfaceContextID, |
| size_t *maxResourceIDBufferSize) |
| { |
| { |
| |
| std::stringstream include; |
| |
| include << "#include \"angle_trace_gl.h\"\n"; |
| include << "#include \"" << FmtCapturePrefix(windowSurfaceContextID, captureLabel) |
| << ".h\"\n"; |
| |
| std::string includeString = include.str(); |
| |
| replayWriter.setSourcePrologue(includeString); |
| } |
| |
| { |
| std::stringstream protoStream; |
| std::stringstream headerStream; |
| std::stringstream bodyStream; |
| |
| protoStream << "void " |
| << FmtSetupFunction(kNoPartId, kSharedContextId, FuncUsage::Prototype); |
| std::string proto = protoStream.str(); |
| |
| WriteCppReplayFunctionWithParts(kSharedContextId, ReplayFunc::Setup, replayWriter, |
| frameIndex, binaryData, setupCalls, headerStream, |
| bodyStream, maxResourceIDBufferSize); |
| |
| replayWriter.addPrivateFunction(proto, headerStream, bodyStream); |
| |
| protoStream.str(""); |
| headerStream.str(""); |
| bodyStream.str(""); |
| protoStream << "void " |
| << FmtSetupInactiveFunction(kNoPartId, kSharedContextId, FuncUsage::Prototype); |
| proto = protoStream.str(); |
| |
| WriteCppReplayFunctionWithParts(kSharedContextId, ReplayFunc::SetupInactive, replayWriter, |
| frameIndex, binaryData, setupCalls, headerStream, |
| bodyStream, maxResourceIDBufferSize); |
| replayWriter.addPrivateFunction(proto, headerStream, bodyStream); |
| } |
| |
| { |
| std::stringstream filenameStream; |
| filenameStream << outDir << FmtCapturePrefix(kSharedContextId, captureLabel); |
| |
| std::string filenamePattern = filenameStream.str(); |
| |
| replayWriter.setFilenamePattern(filenamePattern); |
| } |
| |
| replayWriter.saveSetupFile(); |
| } |
| |
| ProgramSources GetAttachedProgramSources(const gl::Context *context, const gl::Program *program) |
| { |
| ProgramSources sources; |
| for (gl::ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| const gl::Shader *shader = program->getAttachedShader(shaderType); |
| if (shader) |
| { |
| sources[shaderType] = shader->getSourceString(); |
| } |
| } |
| return sources; |
| } |
| |
| template <typename IDType> |
| void CaptureUpdateResourceIDs(const gl::Context *context, |
| const CallCapture &call, |
| const ParamCapture ¶m, |
| ResourceTracker *resourceTracker, |
| std::vector<CallCapture> *callsOut) |
| { |
| GLsizei n = call.params.getParamFlexName("n", "count", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| ASSERT(param.data.size() == 1); |
| ResourceIDType resourceIDType = GetResourceIDTypeFromParamType(param.type); |
| ASSERT(resourceIDType != ResourceIDType::InvalidEnum && |
| resourceIDType != ResourceIDType::ShaderProgram); |
| const char *resourceName = GetResourceIDTypeName(resourceIDType); |
| |
| std::stringstream updateFuncNameStr; |
| updateFuncNameStr << "Update" << resourceName << "ID"; |
| bool trackedPerContext = IsTrackedPerContext(resourceIDType); |
| if (trackedPerContext) |
| { |
| // TODO (https://issuetracker.google.com/169868803) The '2' version can be removed after all |
| // context-local objects are tracked per-context |
| updateFuncNameStr << "2"; |
| } |
| std::string updateFuncName = updateFuncNameStr.str(); |
| |
| const IDType *returnedIDs = reinterpret_cast<const IDType *>(param.data[0].data()); |
| |
| ResourceSet &startingSet = |
| resourceTracker->getTrackedResource(context->id(), resourceIDType).getStartingResources(); |
| |
| for (GLsizei idIndex = 0; idIndex < n; ++idIndex) |
| { |
| IDType id = returnedIDs[idIndex]; |
| GLsizei readBufferOffset = idIndex * sizeof(gl::RenderbufferID); |
| ParamBuffer params; |
| if (trackedPerContext) |
| { |
| params.addValueParam("contextId", ParamType::TGLuint, context->id().value); |
| } |
| params.addValueParam("id", ParamType::TGLuint, id.value); |
| params.addValueParam("readBufferOffset", ParamType::TGLsizei, readBufferOffset); |
| callsOut->emplace_back(updateFuncName, std::move(params)); |
| |
| // Add only if not in starting resources. |
| if (startingSet.find(id.value) == startingSet.end()) |
| { |
| resourceTracker->getTrackedResource(context->id(), resourceIDType) |
| .getNewResources() |
| .insert(id.value); |
| } |
| } |
| } |
| |
| void CaptureUpdateUniformLocations(const gl::Program *program, std::vector<CallCapture> *callsOut) |
| { |
| const gl::ProgramExecutable &executable = program->getExecutable(); |
| const std::vector<gl::LinkedUniform> &uniforms = executable.getUniforms(); |
| const std::vector<gl::VariableLocation> &locations = executable.getUniformLocations(); |
| |
| for (GLint location = 0; location < static_cast<GLint>(locations.size()); ++location) |
| { |
| const gl::VariableLocation &locationVar = locations[location]; |
| |
| // This handles the case where the application calls glBindUniformLocationCHROMIUM |
| // on an unused uniform. We must still store a -1 into gUniformLocations in case the |
| // application attempts to call a glUniform* call. To do this we'll pass in a blank name to |
| // force glGetUniformLocation to return -1. |
| std::string name; |
| int count = 1; |
| ParamBuffer params; |
| params.addValueParam("program", ParamType::TGLuint, program->id().value); |
| |
| if (locationVar.index >= uniforms.size()) |
| { |
| name = ""; |
| } |
| else |
| { |
| const gl::LinkedUniform &uniform = uniforms[locationVar.index]; |
| |
| name = executable.getUniformNameByIndex(locationVar.index); |
| |
| if (uniform.isArray()) |
| { |
| if (locationVar.arrayIndex > 0) |
| { |
| // Non-sequential array uniform locations are not currently handled. |
| // In practice array locations shouldn't ever be non-sequential. |
| ASSERT(uniform.getLocation() == -1 || |
| location == |
| uniform.getLocation() + static_cast<int>(locationVar.arrayIndex)); |
| continue; |
| } |
| |
| name = gl::StripLastArrayIndex(name); |
| count = uniform.getBasicTypeElementCount(); |
| } |
| } |
| |
| ParamCapture nameParam("name", ParamType::TGLcharConstPointer); |
| CaptureString(name.c_str(), &nameParam); |
| params.addParam(std::move(nameParam)); |
| params.addValueParam("location", ParamType::TGLint, location); |
| params.addValueParam("count", ParamType::TGLint, static_cast<GLint>(count)); |
| callsOut->emplace_back("UpdateUniformLocation", std::move(params)); |
| } |
| } |
| |
| void CaptureValidateSerializedState(const gl::Context *context, std::vector<CallCapture> *callsOut) |
| { |
| INFO() << "Capturing validation checkpoint at position " << callsOut->size(); |
| |
| context->finishImmutable(); |
| |
| std::string serializedState; |
| angle::Result result = angle::SerializeContextToString(context, &serializedState); |
| if (result != angle::Result::Continue) |
| { |
| ERR() << "Internal error serializing context state."; |
| return; |
| } |
| ParamCapture serializedStateParam("serializedState", ParamType::TGLcharConstPointer); |
| CaptureString(serializedState.c_str(), &serializedStateParam); |
| |
| ParamBuffer params; |
| params.addParam(std::move(serializedStateParam)); |
| |
| callsOut->emplace_back("VALIDATE_CHECKPOINT", std::move(params)); |
| } |
| |
| void CaptureUpdateUniformBlockIndexes(const gl::Program *program, |
| std::vector<CallCapture> *callsOut) |
| { |
| const std::vector<gl::InterfaceBlock> &uniformBlocks = |
| program->getExecutable().getUniformBlocks(); |
| |
| for (GLuint index = 0; index < uniformBlocks.size(); ++index) |
| { |
| ParamBuffer params; |
| |
| std::string name; |
| params.addValueParam("program", ParamType::TShaderProgramID, program->id()); |
| |
| const std::string fullName = uniformBlocks[index].nameWithArrayIndex(); |
| ParamCapture nameParam("name", ParamType::TGLcharConstPointer); |
| CaptureString(fullName.c_str(), &nameParam); |
| params.addParam(std::move(nameParam)); |
| |
| params.addValueParam("index", ParamType::TGLuint, index); |
| callsOut->emplace_back("UpdateUniformBlockIndex", std::move(params)); |
| } |
| } |
| |
| void CaptureDeleteUniformLocations(gl::ShaderProgramID program, std::vector<CallCapture> *callsOut) |
| { |
| ParamBuffer params; |
| params.addValueParam("program", ParamType::TShaderProgramID, program); |
| callsOut->emplace_back("DeleteUniformLocations", std::move(params)); |
| } |
| |
| void MaybeCaptureUpdateResourceIDs(const gl::Context *context, |
| ResourceTracker *resourceTracker, |
| std::vector<CallCapture> *callsOut) |
| { |
| const CallCapture &call = callsOut->back(); |
| |
| switch (call.entryPoint) |
| { |
| case EntryPoint::GLGenBuffers: |
| { |
| const ParamCapture &buffers = |
| call.params.getParam("buffersPacked", ParamType::TBufferIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::BufferID>(context, call, buffers, resourceTracker, |
| callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenFencesNV: |
| { |
| const ParamCapture &fences = |
| call.params.getParam("fencesPacked", ParamType::TFenceNVIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::FenceNVID>(context, call, fences, resourceTracker, |
| callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenFramebuffers: |
| case EntryPoint::GLGenFramebuffersOES: |
| { |
| const ParamCapture &framebuffers = |
| call.params.getParam("framebuffersPacked", ParamType::TFramebufferIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::FramebufferID>(context, call, framebuffers, |
| resourceTracker, callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenProgramPipelines: |
| { |
| const ParamCapture &pipelines = |
| call.params.getParam("pipelinesPacked", ParamType::TProgramPipelineIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::ProgramPipelineID>(context, call, pipelines, |
| resourceTracker, callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenQueries: |
| case EntryPoint::GLGenQueriesEXT: |
| { |
| const ParamCapture &queries = |
| call.params.getParam("idsPacked", ParamType::TQueryIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::QueryID>(context, call, queries, resourceTracker, |
| callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenRenderbuffers: |
| case EntryPoint::GLGenRenderbuffersOES: |
| { |
| const ParamCapture &renderbuffers = |
| call.params.getParam("renderbuffersPacked", ParamType::TRenderbufferIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::RenderbufferID>(context, call, renderbuffers, |
| resourceTracker, callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenSamplers: |
| { |
| const ParamCapture &samplers = |
| call.params.getParam("samplersPacked", ParamType::TSamplerIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::SamplerID>(context, call, samplers, resourceTracker, |
| callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenSemaphoresEXT: |
| { |
| const ParamCapture &semaphores = |
| call.params.getParam("semaphoresPacked", ParamType::TSemaphoreIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::SemaphoreID>(context, call, semaphores, resourceTracker, |
| callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenTextures: |
| { |
| const ParamCapture &textures = |
| call.params.getParam("texturesPacked", ParamType::TTextureIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::TextureID>(context, call, textures, resourceTracker, |
| callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenTransformFeedbacks: |
| { |
| const ParamCapture &xfbs = |
| call.params.getParam("idsPacked", ParamType::TTransformFeedbackIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::TransformFeedbackID>(context, call, xfbs, resourceTracker, |
| callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLGenVertexArrays: |
| case EntryPoint::GLGenVertexArraysOES: |
| { |
| const ParamCapture &vertexArrays = |
| call.params.getParam("arraysPacked", ParamType::TVertexArrayIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::VertexArrayID>(context, call, vertexArrays, |
| resourceTracker, callsOut); |
| break; |
| } |
| |
| case EntryPoint::GLCreateMemoryObjectsEXT: |
| { |
| const ParamCapture &memoryObjects = |
| call.params.getParam("memoryObjectsPacked", ParamType::TMemoryObjectIDPointer, 1); |
| CaptureUpdateResourceIDs<gl::MemoryObjectID>(context, call, memoryObjects, |
| resourceTracker, callsOut); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| bool IsDefaultCurrentValue(const gl::VertexAttribCurrentValueData ¤tValue) |
| { |
| if (currentValue.Type != gl::VertexAttribType::Float) |
| return false; |
| |
| return currentValue.Values.FloatValues[0] == 0.0f && |
| currentValue.Values.FloatValues[1] == 0.0f && |
| currentValue.Values.FloatValues[2] == 0.0f && currentValue.Values.FloatValues[3] == 1.0f; |
| } |
| |
| bool IsQueryActive(const gl::State &glState, gl::QueryID &queryID) |
| { |
| const gl::ActiveQueryMap &activeQueries = glState.getActiveQueriesForCapture(); |
| for (const auto &activeQueryIter : activeQueries) |
| { |
| const gl::Query *activeQuery = activeQueryIter.get(); |
| if (activeQuery && activeQuery->id() == queryID) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool IsTextureUpdate(CallCapture &call) |
| { |
| switch (call.entryPoint) |
| { |
| case EntryPoint::GLCompressedCopyTextureCHROMIUM: |
| case EntryPoint::GLCompressedTexImage2D: |
| case EntryPoint::GLCompressedTexImage2DRobustANGLE: |
| case EntryPoint::GLCompressedTexImage3D: |
| case EntryPoint::GLCompressedTexImage3DOES: |
| case EntryPoint::GLCompressedTexImage3DRobustANGLE: |
| case EntryPoint::GLCompressedTexSubImage2D: |
| case EntryPoint::GLCompressedTexSubImage2DRobustANGLE: |
| case EntryPoint::GLCompressedTexSubImage3D: |
| case EntryPoint::GLCompressedTexSubImage3DOES: |
| case EntryPoint::GLCompressedTexSubImage3DRobustANGLE: |
| case EntryPoint::GLCopyTexImage2D: |
| case EntryPoint::GLCopyTexSubImage2D: |
| case EntryPoint::GLCopyTexSubImage3D: |
| case EntryPoint::GLCopyTexSubImage3DOES: |
| case EntryPoint::GLCopyTexture3DANGLE: |
| case EntryPoint::GLCopyTextureCHROMIUM: |
| case EntryPoint::GLTexImage2D: |
| case EntryPoint::GLTexImage2DExternalANGLE: |
| case EntryPoint::GLTexImage2DRobustANGLE: |
| case EntryPoint::GLTexImage3D: |
| case EntryPoint::GLTexImage3DOES: |
| case EntryPoint::GLTexImage3DRobustANGLE: |
| case EntryPoint::GLTexSubImage2D: |
| case EntryPoint::GLTexSubImage2DRobustANGLE: |
| case EntryPoint::GLTexSubImage3D: |
| case EntryPoint::GLTexSubImage3DOES: |
| case EntryPoint::GLTexSubImage3DRobustANGLE: |
| case EntryPoint::GLCopyImageSubData: |
| case EntryPoint::GLCopyImageSubDataEXT: |
| case EntryPoint::GLCopyImageSubDataOES: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool IsImageUpdate(CallCapture &call) |
| { |
| switch (call.entryPoint) |
| { |
| case EntryPoint::GLDispatchCompute: |
| case EntryPoint::GLDispatchComputeIndirect: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool IsVertexArrayUpdate(CallCapture &call) |
| { |
| switch (call.entryPoint) |
| { |
| case EntryPoint::GLVertexAttribFormat: |
| case EntryPoint::GLVertexAttribIFormat: |
| case EntryPoint::GLBindVertexBuffer: |
| case EntryPoint::GLVertexAttribBinding: |
| case EntryPoint::GLVertexAttribPointer: |
| case EntryPoint::GLVertexAttribIPointer: |
| case EntryPoint::GLEnableVertexAttribArray: |
| case EntryPoint::GLDisableVertexAttribArray: |
| case EntryPoint::GLVertexBindingDivisor: |
| case EntryPoint::GLVertexAttribDivisor: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool IsSharedObjectResource(ResourceIDType type) |
| { |
| // This helper function informs us which objects are shared vs. per context |
| // |
| // OpenGL ES Version 3.2 (October 22, 2019) |
| // Chapter 5 Shared Objects and Multiple Contexts: |
| // |
| // - Objects that can be shared between contexts include buffer objects, program |
| // and shader objects, renderbuffer objects, sampler objects, sync objects, and texture |
| // objects (except for the texture objects named zero). |
| // - Objects which contain references to other objects include framebuffer, program |
| // pipeline, transform feedback, and vertex array objects. Such objects are called |
| // container objects and are not shared. |
| // |
| // Notably absent from this list are Sync objects, which are not ResourceIDType, are handled |
| // elsewhere, and are shared: |
| // - 2.6.13 Sync Objects: Sync objects may be shared. |
| |
| switch (type) |
| { |
| case ResourceIDType::Buffer: |
| // 2.6.2 Buffer Objects: Buffer objects may be shared. |
| return true; |
| |
| case ResourceIDType::Framebuffer: |
| // 2.6.9 Framebuffer Objects: Framebuffer objects are container objects including |
| // references to renderbuffer and / or texture objects, and are not shared. |
| return false; |
| |
| case ResourceIDType::ProgramPipeline: |
| // 2.6.5 Program Pipeline Objects: Program pipeline objects are container objects |
| // including references to program objects, and are not shared. |
| return false; |
| |
| case ResourceIDType::TransformFeedback: |
| // 2.6.11 Transform Feedback Objects: Transform feedback objects are container objects |
| // including references to buffer objects, and are not shared |
| return false; |
| |
| case ResourceIDType::VertexArray: |
| // 2.6.10 Vertex Array Objects: Vertex array objects are container objects including |
| // references to buffer objects, and are not shared |
| return false; |
| |
| case ResourceIDType::FenceNV: |
| // From https://registry.khronos.org/OpenGL/extensions/NV/NV_fence.txt |
| // Are the fences sharable between multiple contexts? |
| // RESOLUTION: No. |
| return false; |
| |
| case ResourceIDType::Renderbuffer: |
| // 2.6.8 Renderbuffer Objects: Renderbuffer objects may be shared. |
| return true; |
| |
| case ResourceIDType::ShaderProgram: |
| // 2.6.3 Shader Objects: Shader objects may be shared. |
| // 2.6.4 Program Objects: Program objects may be shared. |
| return true; |
| |
| case ResourceIDType::Sampler: |
| // 2.6.7 Sampler Objects: Sampler objects may be shared |
| return true; |
| |
| case ResourceIDType::Sync: |
| // 2.6.13 Sync Objects: Sync objects may be shared. |
| return true; |
| |
| case ResourceIDType::Texture: |
| // 2.6.6 Texture Objects: Texture objects may be shared |
| return true; |
| |
| case ResourceIDType::Query: |
| // 2.6.12 Query Objects: Query objects are not shared |
| return false; |
| |
| case ResourceIDType::Semaphore: |
| // From https://registry.khronos.org/OpenGL/extensions/EXT/EXT_external_objects.txt |
| // 2.6.14 Semaphore Objects: Semaphore objects may be shared. |
| return true; |
| |
| case ResourceIDType::MemoryObject: |
| // From https://registry.khronos.org/OpenGL/extensions/EXT/EXT_external_objects.txt |
| // 2.6.15 Memory Objects: Memory objects may be shared. |
| return true; |
| |
| case ResourceIDType::Context: |
| case ResourceIDType::Image: |
| case ResourceIDType::Surface: |
| case ResourceIDType::egl_Sync: |
| // EGL types are associated with a display and not bound to a context |
| // For the way this function is used, we can treat them as shared. |
| return true; |
| |
| case ResourceIDType::EnumCount: |
| default: |
| ERR() << "Unhandled ResourceIDType= " << static_cast<int>(type); |
| UNREACHABLE(); |
| return false; |
| } |
| } |
| |
| enum class DefaultUniformType |
| { |
| None, |
| CurrentProgram, |
| SpecifiedProgram, |
| }; |
| |
| DefaultUniformType GetDefaultUniformType(const CallCapture &call) |
| { |
| switch (call.entryPoint) |
| { |
| case EntryPoint::GLProgramUniform1f: |
| case EntryPoint::GLProgramUniform1fEXT: |
| case EntryPoint::GLProgramUniform1fv: |
| case EntryPoint::GLProgramUniform1fvEXT: |
| case EntryPoint::GLProgramUniform1i: |
| case EntryPoint::GLProgramUniform1iEXT: |
| case EntryPoint::GLProgramUniform1iv: |
| case EntryPoint::GLProgramUniform1ivEXT: |
| case EntryPoint::GLProgramUniform1ui: |
| case EntryPoint::GLProgramUniform1uiEXT: |
| case EntryPoint::GLProgramUniform1uiv: |
| case EntryPoint::GLProgramUniform1uivEXT: |
| case EntryPoint::GLProgramUniform2f: |
| case EntryPoint::GLProgramUniform2fEXT: |
| case EntryPoint::GLProgramUniform2fv: |
| case EntryPoint::GLProgramUniform2fvEXT: |
| case EntryPoint::GLProgramUniform2i: |
| case EntryPoint::GLProgramUniform2iEXT: |
| case EntryPoint::GLProgramUniform2iv: |
| case EntryPoint::GLProgramUniform2ivEXT: |
| case EntryPoint::GLProgramUniform2ui: |
| case EntryPoint::GLProgramUniform2uiEXT: |
| case EntryPoint::GLProgramUniform2uiv: |
| case EntryPoint::GLProgramUniform2uivEXT: |
| case EntryPoint::GLProgramUniform3f: |
| case EntryPoint::GLProgramUniform3fEXT: |
| case EntryPoint::GLProgramUniform3fv: |
| case EntryPoint::GLProgramUniform3fvEXT: |
| case EntryPoint::GLProgramUniform3i: |
| case EntryPoint::GLProgramUniform3iEXT: |
| case EntryPoint::GLProgramUniform3iv: |
| case EntryPoint::GLProgramUniform3ivEXT: |
| case EntryPoint::GLProgramUniform3ui: |
| case EntryPoint::GLProgramUniform3uiEXT: |
| case EntryPoint::GLProgramUniform3uiv: |
| case EntryPoint::GLProgramUniform3uivEXT: |
| case EntryPoint::GLProgramUniform4f: |
| case EntryPoint::GLProgramUniform4fEXT: |
| case EntryPoint::GLProgramUniform4fv: |
| case EntryPoint::GLProgramUniform4fvEXT: |
| case EntryPoint::GLProgramUniform4i: |
| case EntryPoint::GLProgramUniform4iEXT: |
| case EntryPoint::GLProgramUniform4iv: |
| case EntryPoint::GLProgramUniform4ivEXT: |
| case EntryPoint::GLProgramUniform4ui: |
| case EntryPoint::GLProgramUniform4uiEXT: |
| case EntryPoint::GLProgramUniform4uiv: |
| case EntryPoint::GLProgramUniform4uivEXT: |
| case EntryPoint::GLProgramUniformMatrix2fv: |
| case EntryPoint::GLProgramUniformMatrix2fvEXT: |
| case EntryPoint::GLProgramUniformMatrix2x3fv: |
| case EntryPoint::GLProgramUniformMatrix2x3fvEXT: |
| case EntryPoint::GLProgramUniformMatrix2x4fv: |
| case EntryPoint::GLProgramUniformMatrix2x4fvEXT: |
| case EntryPoint::GLProgramUniformMatrix3fv: |
| case EntryPoint::GLProgramUniformMatrix3fvEXT: |
| case EntryPoint::GLProgramUniformMatrix3x2fv: |
| case EntryPoint::GLProgramUniformMatrix3x2fvEXT: |
| case EntryPoint::GLProgramUniformMatrix3x4fv: |
| case EntryPoint::GLProgramUniformMatrix3x4fvEXT: |
| case EntryPoint::GLProgramUniformMatrix4fv: |
| case EntryPoint::GLProgramUniformMatrix4fvEXT: |
| case EntryPoint::GLProgramUniformMatrix4x2fv: |
| case EntryPoint::GLProgramUniformMatrix4x2fvEXT: |
| case EntryPoint::GLProgramUniformMatrix4x3fv: |
| case EntryPoint::GLProgramUniformMatrix4x3fvEXT: |
| return DefaultUniformType::SpecifiedProgram; |
| |
| case EntryPoint::GLUniform1f: |
| case EntryPoint::GLUniform1fv: |
| case EntryPoint::GLUniform1i: |
| case EntryPoint::GLUniform1iv: |
| case EntryPoint::GLUniform1ui: |
| case EntryPoint::GLUniform1uiv: |
| case EntryPoint::GLUniform2f: |
| case EntryPoint::GLUniform2fv: |
| case EntryPoint::GLUniform2i: |
| case EntryPoint::GLUniform2iv: |
| case EntryPoint::GLUniform2ui: |
| case EntryPoint::GLUniform2uiv: |
| case EntryPoint::GLUniform3f: |
| case EntryPoint::GLUniform3fv: |
| case EntryPoint::GLUniform3i: |
| case EntryPoint::GLUniform3iv: |
| case EntryPoint::GLUniform3ui: |
| case EntryPoint::GLUniform3uiv: |
| case EntryPoint::GLUniform4f: |
| case EntryPoint::GLUniform4fv: |
| case EntryPoint::GLUniform4i: |
| case EntryPoint::GLUniform4iv: |
| case EntryPoint::GLUniform4ui: |
| case EntryPoint::GLUniform4uiv: |
| case EntryPoint::GLUniformMatrix2fv: |
| case EntryPoint::GLUniformMatrix2x3fv: |
| case EntryPoint::GLUniformMatrix2x4fv: |
| case EntryPoint::GLUniformMatrix3fv: |
| case EntryPoint::GLUniformMatrix3x2fv: |
| case EntryPoint::GLUniformMatrix3x4fv: |
| case EntryPoint::GLUniformMatrix4fv: |
| case EntryPoint::GLUniformMatrix4x2fv: |
| case EntryPoint::GLUniformMatrix4x3fv: |
| return DefaultUniformType::CurrentProgram; |
| |
| default: |
| return DefaultUniformType::None; |
| } |
| } |
| |
| void CaptureFramebufferAttachment(std::vector<CallCapture> *setupCalls, |
| const gl::State &replayState, |
| const FramebufferCaptureFuncs &framebufferFuncs, |
| const gl::FramebufferAttachment &attachment, |
| std::vector<CallCapture> *shareGroupSetupCalls, |
| ResourceIDToSetupCallsMap *resourceIDToSetupCalls) |
| { |
| GLuint resourceID = attachment.getResource()->getId(); |
| |
| if (attachment.type() == GL_TEXTURE) |
| { |
| gl::ImageIndex index = attachment.getTextureImageIndex(); |
| |
| if (index.usesTex3D()) |
| { |
| Capture(setupCalls, CaptureFramebufferTextureLayer( |
| replayState, true, GL_FRAMEBUFFER, attachment.getBinding(), |
| {resourceID}, index.getLevelIndex(), index.getLayerIndex())); |
| } |
| else |
| { |
| Capture(setupCalls, |
| framebufferFuncs.framebufferTexture2D( |
| replayState, true, GL_FRAMEBUFFER, attachment.getBinding(), |
| index.getTargetOrFirstCubeFace(), {resourceID}, index.getLevelIndex())); |
| } |
| |
| std::vector<gl::TextureID> textureIDs; |
| const CallCapture &call = setupCalls->back(); |
| if (FindResourceIDsInCall<gl::TextureID>(call, textureIDs)) |
| { |
| // We skip the is active check on the assumption this call is made during MEC |
| for (gl::TextureID textureID : textureIDs) |
| { |
| // Track that this call referenced a Texture, setting it active for Setup |
| MarkResourceIDActive(ResourceIDType::Texture, textureID.value, shareGroupSetupCalls, |
| resourceIDToSetupCalls); |
| } |
| } |
| } |
| else |
| { |
| ASSERT(attachment.type() == GL_RENDERBUFFER); |
| Capture(setupCalls, framebufferFuncs.framebufferRenderbuffer( |
| replayState, true, GL_FRAMEBUFFER, attachment.getBinding(), |
| GL_RENDERBUFFER, {resourceID})); |
| } |
| } |
| |
| void CaptureUpdateUniformValues(const gl::State &replayState, |
| const gl::Context *context, |
| gl::Program *program, |
| ResourceTracker *resourceTracker, |
| std::vector<CallCapture> *callsOut) |
| { |
| if (!program->isLinked()) |
| { |
| // We can't populate uniforms if the program hasn't been linked |
| return; |
| } |
| |
| // We need to bind the program and update its uniforms |
| if (!replayState.getProgram() || replayState.getProgram()->id() != program->id()) |
| { |
| Capture(callsOut, CaptureUseProgram(replayState, true, program->id())); |
| CaptureUpdateCurrentProgram(callsOut->back(), 0, callsOut); |
| } |
| |
| const gl::ProgramExecutable &executable = program->getExecutable(); |
| |
| for (GLuint uniformIndex = 0; |
| uniformIndex < static_cast<GLuint>(executable.getUniforms().size()); uniformIndex++) |
| { |
| std::string uniformName = executable.getUniformNameByIndex(uniformIndex); |
| const gl::LinkedUniform &uniform = executable.getUniformByIndex(uniformIndex); |
| |
| int uniformCount = 1; |
| if (uniform.isArray()) |
| { |
| uniformCount = uniform.getBasicTypeElementCount(); |
| uniformName = gl::StripLastArrayIndex(uniformName); |
| } |
| |
| gl::UniformLocation uniformLoc = executable.getUniformLocation(uniformName); |
| const gl::UniformTypeInfo &typeInfo = gl::GetUniformTypeInfo(uniform.getType()); |
| int componentCount = typeInfo.componentCount; |
| int uniformSize = uniformCount * componentCount; |
| |
| // For arrayed uniforms, we'll need to increment a read location |
| gl::UniformLocation readLoc = uniformLoc; |
| |
| // If the uniform is unused, just continue |
| if (readLoc.value == -1) |
| { |
| continue; |
| } |
| |
| // Image uniforms are special and cannot be set this way |
| if (typeInfo.isImageType) |
| { |
| continue; |
| } |
| |
| DefaultUniformCallsPerLocationMap &resetCalls = |
| resourceTracker->getDefaultUniformResetCalls(program->id()); |
| |
| // Create two lists of calls for uniforms, one for Setup, one for Reset |
| CallVector defaultUniformCalls({callsOut, &resetCalls[uniformLoc]}); |
| |
| // Samplers should be populated with GL_INT, regardless of return type |
| if (typeInfo.isSampler) |
| { |
| std::vector<GLint> uniformBuffer(uniformSize); |
| for (int index = 0; index < uniformCount; index++, readLoc.value++) |
| { |
| executable.getUniformiv(context, readLoc, |
| uniformBuffer.data() + index * componentCount); |
| resourceTracker->setDefaultUniformBaseLocation(program->id(), readLoc, uniformLoc); |
| } |
| |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform1iv(replayState, true, uniformLoc, uniformCount, |
| uniformBuffer.data())); |
| } |
| |
| continue; |
| } |
| |
| switch (typeInfo.componentType) |
| { |
| case GL_FLOAT: |
| { |
| std::vector<GLfloat> uniformBuffer(uniformSize); |
| for (int index = 0; index < uniformCount; index++, readLoc.value++) |
| { |
| executable.getUniformfv(context, readLoc, |
| uniformBuffer.data() + index * componentCount); |
| resourceTracker->setDefaultUniformBaseLocation(program->id(), readLoc, |
| uniformLoc); |
| } |
| switch (typeInfo.type) |
| { |
| // Note: All matrix uniforms are populated without transpose |
| case GL_FLOAT_MAT4x3: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniformMatrix4x3fv(replayState, true, uniformLoc, |
| uniformCount, false, |
| uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_MAT4x2: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniformMatrix4x2fv(replayState, true, uniformLoc, |
| uniformCount, false, |
| uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_MAT4: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniformMatrix4fv(replayState, true, uniformLoc, |
| uniformCount, false, |
| uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_MAT3x4: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniformMatrix3x4fv(replayState, true, uniformLoc, |
| uniformCount, false, |
| uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_MAT3x2: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniformMatrix3x2fv(replayState, true, uniformLoc, |
| uniformCount, false, |
| uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_MAT3: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniformMatrix3fv(replayState, true, uniformLoc, |
| uniformCount, false, |
| uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_MAT2x4: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniformMatrix2x4fv(replayState, true, uniformLoc, |
| uniformCount, false, |
| uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_MAT2x3: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniformMatrix2x3fv(replayState, true, uniformLoc, |
| uniformCount, false, |
| uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_MAT2: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniformMatrix2fv(replayState, true, uniformLoc, |
| uniformCount, false, |
| uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_VEC4: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform4fv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_VEC3: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform3fv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT_VEC2: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform2fv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| case GL_FLOAT: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform1fv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| default: |
| UNIMPLEMENTED(); |
| break; |
| } |
| break; |
| } |
| case GL_INT: |
| { |
| std::vector<GLint> uniformBuffer(uniformSize); |
| for (int index = 0; index < uniformCount; index++, readLoc.value++) |
| { |
| executable.getUniformiv(context, readLoc, |
| uniformBuffer.data() + index * componentCount); |
| resourceTracker->setDefaultUniformBaseLocation(program->id(), readLoc, |
| uniformLoc); |
| } |
| switch (componentCount) |
| { |
| case 4: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform4iv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| case 3: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform3iv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| case 2: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform2iv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| case 1: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform1iv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| default: |
| UNIMPLEMENTED(); |
| break; |
| } |
| break; |
| } |
| case GL_BOOL: |
| case GL_UNSIGNED_INT: |
| { |
| std::vector<GLuint> uniformBuffer(uniformSize); |
| for (int index = 0; index < uniformCount; index++, readLoc.value++) |
| { |
| executable.getUniformuiv(context, readLoc, |
| uniformBuffer.data() + index * componentCount); |
| resourceTracker->setDefaultUniformBaseLocation(program->id(), readLoc, |
| uniformLoc); |
| } |
| switch (componentCount) |
| { |
| case 4: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform4uiv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| case 3: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform3uiv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| case 2: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform2uiv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| case 1: |
| for (std::vector<CallCapture> *calls : defaultUniformCalls) |
| { |
| Capture(calls, CaptureUniform1uiv(replayState, true, uniformLoc, |
| uniformCount, uniformBuffer.data())); |
| } |
| break; |
| default: |
| UNIMPLEMENTED(); |
| break; |
| } |
| break; |
| } |
| default: |
| UNIMPLEMENTED(); |
| break; |
| } |
| } |
| } |
| |
| void CaptureVertexPointerES1(std::vector<CallCapture> *setupCalls, |
| gl::State *replayState, |
| GLuint attribIndex, |
| const gl::VertexAttribute &attrib, |
| const gl::VertexBinding &binding) |
| { |
| switch (gl::GLES1Renderer::VertexArrayType(attribIndex)) |
| { |
| case gl::ClientVertexArrayType::Vertex: |
| Capture(setupCalls, |
| CaptureVertexPointer(*replayState, true, attrib.format->channelCount, |
| attrib.format->vertexAttribType, binding.getStride(), |
| attrib.pointer)); |
| break; |
| case gl::ClientVertexArrayType::Normal: |
| Capture(setupCalls, |
| CaptureNormalPointer(*replayState, true, attrib.format->vertexAttribType, |
| binding.getStride(), attrib.pointer)); |
| break; |
| case gl::ClientVertexArrayType::Color: |
| Capture(setupCalls, CaptureColorPointer(*replayState, true, attrib.format->channelCount, |
| attrib.format->vertexAttribType, |
| binding.getStride(), attrib.pointer)); |
| break; |
| case gl::ClientVertexArrayType::PointSize: |
| Capture(setupCalls, |
| CapturePointSizePointerOES(*replayState, true, attrib.format->vertexAttribType, |
| binding.getStride(), attrib.pointer)); |
| break; |
| case gl::ClientVertexArrayType::TextureCoord: |
| Capture(setupCalls, |
| CaptureTexCoordPointer(*replayState, true, attrib.format->channelCount, |
| attrib.format->vertexAttribType, binding.getStride(), |
| attrib.pointer)); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void CaptureTextureEnvironmentState(std::vector<CallCapture> *setupCalls, |
| gl::State *replayState, |
| const gl::State *apiState, |
| unsigned int unit) |
| { |
| const gl::TextureEnvironmentParameters ¤tEnv = apiState->gles1().textureEnvironment(unit); |
| const gl::TextureEnvironmentParameters &defaultEnv = |
| replayState->gles1().textureEnvironment(unit); |
| |
| if (currentEnv == defaultEnv) |
| { |
| return; |
| } |
| |
| auto capIfNe = [setupCalls](auto currentState, auto defaultState, CallCapture &&call) { |
| if (currentState != defaultState) |
| { |
| setupCalls->emplace_back(std::move(call)); |
| } |
| }; |
| |
| // When the texture env state differs on a non-default sampler unit, emit an ActiveTexture call. |
| // The default sampler unit is GL_TEXTURE0. |
| GLenum currentUnit = GL_TEXTURE0 + static_cast<GLenum>(unit); |
| GLenum defaultUnit = GL_TEXTURE0 + static_cast<GLenum>(replayState->getActiveSampler()); |
| capIfNe(currentUnit, defaultUnit, CaptureActiveTexture(*replayState, true, currentUnit)); |
| |
| auto capEnum = [capIfNe, replayState](gl::TextureEnvParameter pname, auto currentState, |
| auto defaultState) { |
| capIfNe(currentState, defaultState, |
| CaptureTexEnvi(*replayState, true, gl::TextureEnvTarget::Env, pname, |
| ToGLenum(currentState))); |
| }; |
| |
| capEnum(gl::TextureEnvParameter::Mode, currentEnv.mode, defaultEnv.mode); |
| |
| capEnum(gl::TextureEnvParameter::CombineRgb, currentEnv.combineRgb, defaultEnv.combineRgb); |
| capEnum(gl::TextureEnvParameter::CombineAlpha, currentEnv.combineAlpha, |
| defaultEnv.combineAlpha); |
| |
| capEnum(gl::TextureEnvParameter::Src0Rgb, currentEnv.src0Rgb, defaultEnv.src0Rgb); |
| capEnum(gl::TextureEnvParameter::Src1Rgb, currentEnv.src1Rgb, defaultEnv.src1Rgb); |
| capEnum(gl::TextureEnvParameter::Src2Rgb, currentEnv.src2Rgb, defaultEnv.src2Rgb); |
| |
| capEnum(gl::TextureEnvParameter::Src0Alpha, currentEnv.src0Alpha, defaultEnv.src0Alpha); |
| capEnum(gl::TextureEnvParameter::Src1Alpha, currentEnv.src1Alpha, defaultEnv.src1Alpha); |
| capEnum(gl::TextureEnvParameter::Src2Alpha, currentEnv.src2Alpha, defaultEnv.src2Alpha); |
| |
| capEnum(gl::TextureEnvParameter::Op0Rgb, currentEnv.op0Rgb, defaultEnv.op0Rgb); |
| capEnum(gl::TextureEnvParameter::Op1Rgb, currentEnv.op1Rgb, defaultEnv.op1Rgb); |
| capEnum(gl::TextureEnvParameter::Op2Rgb, currentEnv.op2Rgb, defaultEnv.op2Rgb); |
| |
| capEnum(gl::TextureEnvParameter::Op0Alpha, currentEnv.op0Alpha, defaultEnv.op0Alpha); |
| capEnum(gl::TextureEnvParameter::Op1Alpha, currentEnv.op1Alpha, defaultEnv.op1Alpha); |
| capEnum(gl::TextureEnvParameter::Op2Alpha, currentEnv.op2Alpha, defaultEnv.op2Alpha); |
| |
| auto capFloat = [capIfNe, replayState](gl::TextureEnvParameter pname, auto currentState, |
| auto defaultState) { |
| capIfNe(currentState, defaultState, |
| CaptureTexEnvf(*replayState, true, gl::TextureEnvTarget::Env, pname, currentState)); |
| }; |
| |
| capFloat(gl::TextureEnvParameter::RgbScale, currentEnv.rgbScale, defaultEnv.rgbScale); |
| capFloat(gl::TextureEnvParameter::AlphaScale, currentEnv.alphaScale, defaultEnv.alphaScale); |
| |
| capIfNe(currentEnv.color, defaultEnv.color, |
| CaptureTexEnvfv(*replayState, true, gl::TextureEnvTarget::Env, |
| gl::TextureEnvParameter::Color, currentEnv.color.data())); |
| |
| // PointCoordReplace is the only parameter that uses the PointSprite TextureEnvTarget. |
| capIfNe(currentEnv.pointSpriteCoordReplace, defaultEnv.pointSpriteCoordReplace, |
| CaptureTexEnvi(*replayState, true, gl::TextureEnvTarget::PointSprite, |
| gl::TextureEnvParameter::PointCoordReplace, |
| currentEnv.pointSpriteCoordReplace)); |
| |
| // In case of non-default sampler units, the default unit must be set back here. |
| capIfNe(currentUnit, defaultUnit, CaptureActiveTexture(*replayState, true, defaultUnit)); |
| } |
| |
| bool VertexBindingMatchesAttribStride(const gl::VertexAttribute &attrib, |
| const gl::VertexBinding &binding) |
| { |
| if (attrib.vertexAttribArrayStride == 0 && |
| binding.getStride() == ComputeVertexAttributeTypeSize(attrib)) |
| { |
| return true; |
| } |
| |
| return attrib.vertexAttribArrayStride == binding.getStride(); |
| } |
| |
| void CaptureVertexArrayState(std::vector<CallCapture> *setupCalls, |
| const gl::Context *context, |
| const gl::VertexArray *vertexArray, |
| gl::State *replayState) |
| { |
| const std::vector<gl::VertexAttribute> &vertexAttribs = vertexArray->getVertexAttributes(); |
| const std::vector<gl::VertexBinding> &vertexBindings = vertexArray->getVertexBindings(); |
| |
| gl::AttributesMask vertexPointerBindings; |
| |
| ASSERT(vertexAttribs.size() <= vertexBindings.size()); |
| for (GLuint attribIndex = 0; attribIndex < vertexAttribs.size(); ++attribIndex) |
| { |
| const gl::VertexAttribute defaultAttrib(attribIndex); |
| const gl::VertexBinding defaultBinding; |
| |
| const gl::VertexAttribute &attrib = vertexAttribs[attribIndex]; |
| const gl::VertexBinding &binding = vertexBindings[attrib.bindingIndex]; |
| |
| if (attrib.enabled != defaultAttrib.enabled) |
| { |
| if (context->isGLES1()) |
| { |
| Capture(setupCalls, |
| CaptureEnableClientState(*replayState, false, |
| gl::GLES1Renderer::VertexArrayType(attribIndex))); |
| } |
| else |
| { |
| Capture(setupCalls, |
| CaptureEnableVertexAttribArray(*replayState, false, attribIndex)); |
| } |
| } |
| |
| // Don't capture CaptureVertexAttribPointer calls when a non-default VAO is bound, the array |
| // buffer is null and a non-null attrib pointer is used. |
| bool skipInvalidAttrib = vertexArray->id().value != 0 && |
| binding.getBuffer().get() == nullptr && attrib.pointer != nullptr; |
| |
| if (!skipInvalidAttrib && |
| (attrib.format != defaultAttrib.format || attrib.pointer != defaultAttrib.pointer || |
| binding.getStride() != defaultBinding.getStride() || |
| attrib.bindingIndex != defaultAttrib.bindingIndex || |
| binding.getBuffer().get() != nullptr)) |
| { |
| // Each attribute can pull from a separate buffer, so check the binding |
| gl::Buffer *buffer = binding.getBuffer().get(); |
| if (buffer != replayState->getArrayBuffer()) |
| { |
| replayState->setBufferBinding(context, gl::BufferBinding::Array, buffer); |
| |
| gl::BufferID bufferID = {0}; |
| if (buffer) |
| { |
| bufferID = buffer->id(); |
| } |
| Capture(setupCalls, |
| CaptureBindBuffer(*replayState, true, gl::BufferBinding::Array, bufferID)); |
| } |
| |
| // Establish the relationship between currently bound buffer and the VAO |
| if (context->isGLES1()) |
| { |
| // Track indexes that used ES1 calls |
| vertexPointerBindings.set(attribIndex); |
| |
| CaptureVertexPointerES1(setupCalls, replayState, attribIndex, attrib, binding); |
| } |
| else if (attrib.bindingIndex == attribIndex && |
| VertexBindingMatchesAttribStride(attrib, binding) && |
| (!buffer || binding.getOffset() == reinterpret_cast<GLintptr>(attrib.pointer))) |
| { |
| // Check if we can use strictly ES2 semantics, and track indexes that do. |
| vertexPointerBindings.set(attribIndex); |
| |
| if (attrib.format->isPureInt()) |
| { |
| Capture(setupCalls, CaptureVertexAttribIPointer(*replayState, true, attribIndex, |
| attrib.format->channelCount, |
| attrib.format->vertexAttribType, |
| attrib.vertexAttribArrayStride, |
| attrib.pointer)); |
| } |
| else |
| { |
| Capture(setupCalls, |
| CaptureVertexAttribPointer( |
| *replayState, true, attribIndex, attrib.format->channelCount, |
| attrib.format->vertexAttribType, attrib.format->isNorm(), |
| attrib.vertexAttribArrayStride, attrib.pointer)); |
| } |
| |
| if (binding.getDivisor() != 0) |
| { |
| Capture(setupCalls, CaptureVertexAttribDivisor(*replayState, true, attribIndex, |
| binding.getDivisor())); |
| } |
| } |
| else |
| { |
| ASSERT(context->getClientVersion() >= gl::ES_3_1); |
| |
| if (attrib.format->isPureInt()) |
| { |
| Capture(setupCalls, CaptureVertexAttribIFormat(*replayState, true, attribIndex, |
| attrib.format->channelCount, |
| attrib.format->vertexAttribType, |
| attrib.relativeOffset)); |
| } |
| else |
| { |
| Capture(setupCalls, CaptureVertexAttribFormat(*replayState, true, attribIndex, |
| attrib.format->channelCount, |
| attrib.format->vertexAttribType, |
| attrib.format->isNorm(), |
| attrib.relativeOffset)); |
| } |
| |
| Capture(setupCalls, CaptureVertexAttribBinding(*replayState, true, attribIndex, |
| attrib.bindingIndex)); |
| } |
| } |
| } |
| |
| // The loop below expects attribs and bindings to have equal counts |
| static_assert(gl::MAX_VERTEX_ATTRIBS == gl::MAX_VERTEX_ATTRIB_BINDINGS, |
| "Max vertex attribs and bindings count mismatch"); |
| |
| // Loop through binding indices that weren't used by VertexAttribPointer |
| for (size_t bindingIndex : vertexPointerBindings.flip()) |
| { |
| const gl::VertexBinding &binding = vertexBindings[bindingIndex]; |
| |
| if (binding.getBuffer().id().value != 0) |
| { |
| Capture(setupCalls, |
| CaptureBindVertexBuffer(*replayState, true, static_cast<GLuint>(bindingIndex), |
| binding.getBuffer().id(), binding.getOffset(), |
| binding.getStride())); |
| } |
| |
| if (binding.getDivisor() != 0) |
| { |
| Capture(setupCalls, CaptureVertexBindingDivisor(*replayState, true, |
| static_cast<GLuint>(bindingIndex), |
| binding.getDivisor())); |
| } |
| } |
| |
| // The element array buffer is not per attribute, but per VAO |
| gl::Buffer *elementArrayBuffer = vertexArray->getElementArrayBuffer(); |
| if (elementArrayBuffer) |
| { |
| Capture(setupCalls, CaptureBindBuffer(*replayState, true, gl::BufferBinding::ElementArray, |
| elementArrayBuffer->id())); |
| } |
| } |
| |
| void CaptureTextureStorage(std::vector<CallCapture> *setupCalls, |
| gl::State *replayState, |
| const gl::Texture *texture) |
| { |
| // Use mip-level 0 for the base dimensions |
| gl::ImageIndex imageIndex = gl::ImageIndex::MakeFromType(texture->getType(), 0); |
| const gl::ImageDesc &desc = texture->getTextureState().getImageDesc(imageIndex); |
| |
| switch (texture->getType()) |
| { |
| case gl::TextureType::_2D: |
| case gl::TextureType::CubeMap: |
| { |
| Capture(setupCalls, CaptureTexStorage2D(*replayState, true, texture->getType(), |
| texture->getImmutableLevels(), |
| desc.format.info->internalFormat, |
| desc.size.width, desc.size.height)); |
| break; |
| } |
| case gl::TextureType::_3D: |
| case gl::TextureType::_2DArray: |
| case gl::TextureType::CubeMapArray: |
| { |
| Capture(setupCalls, CaptureTexStorage3D( |
| *replayState, true, texture->getType(), |
| texture->getImmutableLevels(), desc.format.info->internalFormat, |
| desc.size.width, desc.size.height, desc.size.depth)); |
| break; |
| } |
| case gl::TextureType::Buffer: |
| { |
| // Do nothing. This will already be captured as a buffer. |
| break; |
| } |
| default: |
| UNIMPLEMENTED(); |
| break; |
| } |
| } |
| |
| void CaptureTextureContents(std::vector<CallCapture> *setupCalls, |
| gl::State *replayState, |
| const gl::Texture *texture, |
| const gl::ImageIndex &index, |
| const gl::ImageDesc &desc, |
| GLuint size, |
| const void *data) |
| { |
| const gl::InternalFormat &format = *desc.format.info; |
| |
| if (index.getType() == gl::TextureType::Buffer) |
| { |
| // Zero binding size indicates full buffer bound |
| if (texture->getBuffer().getSize() == 0) |
| { |
| Capture(setupCalls, |
| CaptureTexBufferEXT(*replayState, true, index.getType(), format.internalFormat, |
| texture->getBuffer().get()->id())); |
| } |
| else |
| { |
| Capture(setupCalls, CaptureTexBufferRangeEXT(*replayState, true, index.getType(), |
| format.internalFormat, |
| texture->getBuffer().get()->id(), |
| texture->getBuffer().getOffset(), |
| texture->getBuffer().getSize())); |
| } |
| |
| // For buffers, we're done |
| return; |
| } |
| |
| bool is3D = |
| (index.getType() == gl::TextureType::_3D || index.getType() == gl::TextureType::_2DArray || |
| index.getType() == gl::TextureType::CubeMapArray); |
| |
| if (format.compressed || format.paletted) |
| { |
| if (is3D) |
| { |
| if (texture->getImmutableFormat()) |
| { |
| Capture(setupCalls, |
| CaptureCompressedTexSubImage3D( |
| *replayState, true, index.getTarget(), index.getLevelIndex(), 0, 0, 0, |
| desc.size.width, desc.size.height, desc.size.depth, |
| format.internalFormat, size, data)); |
| } |
| else |
| { |
| Capture(setupCalls, |
| CaptureCompressedTexImage3D(*replayState, true, index.getTarget(), |
| index.getLevelIndex(), format.internalFormat, |
| desc.size.width, desc.size.height, |
| desc.size.depth, 0, size, data)); |
| } |
| } |
| else |
| { |
| if (texture->getImmutableFormat()) |
| { |
| Capture(setupCalls, |
| CaptureCompressedTexSubImage2D( |
| *replayState, true, index.getTarget(), index.getLevelIndex(), 0, 0, |
| desc.size.width, desc.size.height, format.internalFormat, size, data)); |
| } |
| else |
| { |
| Capture(setupCalls, CaptureCompressedTexImage2D( |
| *replayState, true, index.getTarget(), |
| index.getLevelIndex(), format.internalFormat, |
| desc.size.width, desc.size.height, 0, size, data)); |
| } |
| } |
| } |
| else |
| { |
| if (is3D) |
| { |
| if (texture->getImmutableFormat()) |
| { |
| Capture(setupCalls, |
| CaptureTexSubImage3D(*replayState, true, index.getTarget(), |
| index.getLevelIndex(), 0, 0, 0, desc.size.width, |
| desc.size.height, desc.size.depth, format.format, |
| format.type, data)); |
| } |
| else |
| { |
| Capture( |
| setupCalls, |
| CaptureTexImage3D(*replayState, true, index.getTarget(), index.getLevelIndex(), |
| format.internalFormat, desc.size.width, desc.size.height, |
| desc.size.depth, 0, format.format, format.type, data)); |
| } |
| } |
| else |
| { |
| if (texture->getImmutableFormat()) |
| { |
| Capture(setupCalls, |
| CaptureTexSubImage2D(*replayState, true, index.getTarget(), |
| index.getLevelIndex(), 0, 0, desc.size.width, |
| desc.size.height, format.format, format.type, data)); |
| } |
| else |
| { |
| Capture(setupCalls, CaptureTexImage2D(*replayState, true, index.getTarget(), |
| index.getLevelIndex(), format.internalFormat, |
| desc.size.width, desc.size.height, 0, |
| format.format, format.type, data)); |
| } |
| } |
| } |
| } |
| |
| void CaptureCustomUniformBlockBinding(const CallCapture &callIn, std::vector<CallCapture> &callsOut) |
| { |
| const ParamBuffer ¶msIn = callIn.params; |
| |
| const ParamCapture &programID = |
| paramsIn.getParam("programPacked", ParamType::TShaderProgramID, 0); |
| const ParamCapture &blockIndex = |
| paramsIn.getParam("uniformBlockIndexPacked", ParamType::TUniformBlockIndex, 1); |
| const ParamCapture &blockBinding = |
| paramsIn.getParam("uniformBlockBinding", ParamType::TGLuint, 2); |
| |
| ParamBuffer params; |
| params.addValueParam("program", ParamType::TGLuint, programID.value.ShaderProgramIDVal.value); |
| params.addValueParam("uniformBlockIndex", ParamType::TGLuint, |
| blockIndex.value.UniformBlockIndexVal.value); |
| params.addValueParam("uniformBlockBinding", ParamType::TGLuint, blockBinding.value.GLuintVal); |
| |
| callsOut.emplace_back("UniformBlockBinding", std::move(params)); |
| } |
| |
| void CaptureCustomMapBuffer(const char *entryPointName, |
| CallCapture &call, |
| std::vector<CallCapture> &callsOut, |
| gl::BufferID mappedBufferID) |
| { |
| call.params.addValueParam("buffer", ParamType::TGLuint, mappedBufferID.value); |
| callsOut.emplace_back(entryPointName, std::move(call.params)); |
| } |
| |
| void CaptureCustomShaderProgram(const char *name, |
| CallCapture &call, |
| std::vector<CallCapture> &callsOut) |
| { |
| call.params.addValueParam("shaderProgram", ParamType::TGLuint, |
| call.params.getReturnValue().value.GLuintVal); |
| call.customFunctionName = name; |
| callsOut.emplace_back(std::move(call)); |
| } |
| |
| void CaptureCustomFenceSync(CallCapture &call, std::vector<CallCapture> &callsOut) |
| { |
| ParamBuffer &¶ms = std::move(call.params); |
| params.addValueParam("fenceSync", ParamType::TGLuint64, |
| params.getReturnValue().value.GLuint64Val); |
| call.customFunctionName = "FenceSync2"; |
| call.isSyncPoint = true; |
| callsOut.emplace_back(std::move(call)); |
| } |
| |
| const egl::Image *GetImageFromParam(const gl::Context *context, const ParamCapture ¶m) |
| { |
| const egl::ImageID eglImageID = egl::PackParam<egl::ImageID>(param.value.EGLImageVal); |
| const egl::Image *eglImage = context->getDisplay()->getImage(eglImageID); |
| ASSERT(eglImage != nullptr); |
| return eglImage; |
| } |
| |
| void CaptureCustomCreateEGLImage(const gl::Context *context, |
| const char *name, |
| size_t width, |
| size_t height, |
| CallCapture &call, |
| std::vector<CallCapture> &callsOut) |
| { |
| ParamBuffer &¶ms = std::move(call.params); |
| EGLImage returnVal = params.getReturnValue().value.EGLImageVal; |
| egl::ImageID imageID = egl::PackParam<egl::ImageID>(returnVal); |
| call.customFunctionName = name; |
| |
| // Clear client buffer value if it is a pointer to a hardware buffer. It is |
| // not used by replay and will not be portable to 32-bit builds |
| if (params.getParam("target", ParamType::TEGLenum, 2).value.EGLenumVal == |
| EGL_NATIVE_BUFFER_ANDROID) |
| { |
| params.setValueParamAtIndex("buffer", ParamType::TEGLClientBuffer, |
| reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(0)), |
| 3); |
| } |
| |
| // Record image dimensions in case a backing resource needs to be created during replay |
| params.addValueParam("width", ParamType::TGLsizei, static_cast<GLsizei>(width)); |
| params.addValueParam("height", ParamType::TGLsizei, static_cast<GLsizei>(height)); |
| |
| params.addValueParam("image", ParamType::TGLuint, imageID.value); |
| callsOut.emplace_back(std::move(call)); |
| } |
| |
| void CaptureCustomDestroyEGLImage(const char *name, |
| CallCapture &call, |
| std::vector<CallCapture> &callsOut) |
| { |
| call.customFunctionName = name; |
| ParamBuffer &¶ms = std::move(call.params); |
| |
| const ParamCapture &imageID = params.getParam("imagePacked", ParamType::TImageID, 1); |
| params.addValueParam("imageID", ParamType::TGLuint, imageID.value.ImageIDVal.value); |
| |
| callsOut.emplace_back(std::move(call)); |
| } |
| |
| void CaptureCustomCreateEGLSync(const char *name, |
| CallCapture &call, |
| std::vector<CallCapture> &callsOut) |
| { |
| ParamBuffer &¶ms = std::move(call.params); |
| EGLSync returnVal = params.getReturnValue().value.EGLSyncVal; |
| egl::SyncID syncID = egl::PackParam<egl::SyncID>(returnVal); |
| params.addValueParam("sync", ParamType::TGLuint, syncID.value); |
| call.customFunctionName = name; |
| callsOut.emplace_back(std::move(call)); |
| } |
| |
| void CaptureCustomCreatePbufferSurface(CallCapture &call, std::vector<CallCapture> &callsOut) |
| { |
| ParamBuffer &¶ms = std::move(call.params); |
| EGLSurface returnVal = params.getReturnValue().value.EGLSurfaceVal; |
| egl::SurfaceID surfaceID = egl::PackParam<egl::SurfaceID>(returnVal); |
| |
| params.addValueParam("surface", ParamType::TGLuint, surfaceID.value); |
| call.customFunctionName = "CreatePbufferSurface"; |
| callsOut.emplace_back(std::move(call)); |
| } |
| |
| void CaptureCustomCreateNativeClientbuffer(CallCapture &call, std::vector<CallCapture> &callsOut) |
| { |
| ParamBuffer &¶ms = std::move(call.params); |
| params.addValueParam("clientBuffer", ParamType::TEGLClientBuffer, |
| params.getReturnValue().value.EGLClientBufferVal); |
| call.customFunctionName = "CreateNativeClientBufferANDROID"; |
| callsOut.emplace_back(std::move(call)); |
| } |
| |
| void GenerateLinkedProgram(const gl::Context *context, |
| const gl::State &replayState, |
| ResourceTracker *resourceTracker, |
| std::vector<CallCapture> *setupCalls, |
| gl::Program *program, |
| gl::ShaderProgramID id, |
| gl::ShaderProgramID tempIDStart, |
| const ProgramSources &linkedSources) |
| { |
| // A map to store the gShaderProgram map lookup index of the temp shaders we attached below. We |
| // need this map to retrieve the lookup index to pass to CaptureDetachShader calls at the end of |
| // GenerateLinkedProgram. |
| PackedEnumMap<gl::ShaderType, gl::ShaderProgramID> tempShaderIDTracker; |
| |
| const gl::ProgramExecutable &executable = program->getExecutable(); |
| |
| // Compile with last linked sources. |
| for (gl::ShaderType shaderType : executable.getLinkedShaderStages()) |
| { |
| // Bump the max shader program id for each new tempIDStart we use to create, compile, and |
| // attach the temp shader object. |
| resourceTracker->onShaderProgramAccess(tempIDStart); |
| // Store the tempIDStart in the tempShaderIDTracker to retrieve for CaptureDetachShader |
| // calls later. |
| tempShaderIDTracker[shaderType] = tempIDStart; |
| const std::string &sourceString = linkedSources[shaderType]; |
| const char *sourcePointer = sourceString.c_str(); |
| |
| if (sourceString.empty()) |
| { |
| // If we don't have source for this shader, that means it was populated by the app |
| // using glProgramBinary. We need to look it up from our cached copy. |
| const ProgramSources &cachedLinkedSources = |
| context->getShareGroup()->getFrameCaptureShared()->getProgramSources(id); |
| |
| const std::string &cachedSourceString = cachedLinkedSources[shaderType]; |
| sourcePointer = cachedSourceString.c_str(); |
| ASSERT(!cachedSourceString.empty()); |
| } |
| |
| // Compile and attach the temporary shader. Then free it immediately. |
| CallCapture createShader = |
| CaptureCreateShader(replayState, true, shaderType, tempIDStart.value); |
| CaptureCustomShaderProgram("CreateShader", createShader, *setupCalls); |
| Capture(setupCalls, |
| CaptureShaderSource(replayState, true, tempIDStart, 1, &sourcePointer, nullptr)); |
| Capture(setupCalls, CaptureCompileShader(replayState, true, tempIDStart)); |
| Capture(setupCalls, CaptureAttachShader(replayState, true, id, tempIDStart)); |
| // Increment tempIDStart to get a new gShaderProgram map index for the next linked stage |
| // shader object. We can't reuse the same tempIDStart as we need to retrieve the index of |
| // each attached shader object later to pass to CaptureDetachShader calls. |
| tempIDStart.value += 1; |
| } |
| |
| // Gather XFB varyings |
| std::vector<std::string> xfbVaryings; |
| for (const gl::TransformFeedbackVarying &xfbVarying : |
| executable.getLinkedTransformFeedbackVaryings()) |
| { |
| xfbVaryings.push_back(xfbVarying.nameWithArrayIndex()); |
| } |
| |
| if (!xfbVaryings.empty()) |
| { |
| std::vector<const char *> varyingsStrings; |
| for (const std::string &varyingString : xfbVaryings) |
| { |
| varyingsStrings.push_back(varyingString.data()); |
| } |
| |
| GLenum xfbMode = executable.getTransformFeedbackBufferMode(); |
| Capture(setupCalls, CaptureTransformFeedbackVaryings(replayState, true, id, |
| static_cast<GLint>(xfbVaryings.size()), |
| varyingsStrings.data(), xfbMode)); |
| } |
| |
| // Force the attributes to be bound the same way as in the existing program. |
| // This can affect attributes that are optimized out in some implementations. |
| for (const gl::ProgramInput &attrib : executable.getProgramInputs()) |
| { |
| if (gl::IsBuiltInName(attrib.name)) |
| { |
| // Don't try to bind built-in attributes |
| continue; |
| } |
| |
| // Separable programs may not have a VS, meaning it may not have attributes. |
| if (executable.hasLinkedShaderStage(gl::ShaderType::Vertex)) |
| { |
| ASSERT(attrib.getLocation() != -1); |
| Capture(setupCalls, CaptureBindAttribLocation(replayState, true, id, |
| static_cast<GLuint>(attrib.getLocation()), |
| attrib.name.c_str())); |
| } |
| } |
| |
| if (program->isSeparable()) |
| { |
| // MEC manually recreates separable programs, rather than attempting to recreate a call |
| // to glCreateShaderProgramv(), so insert a call to mark it separable. |
| Capture(setupCalls, |
| CaptureProgramParameteri(replayState, true, id, GL_PROGRAM_SEPARABLE, GL_TRUE)); |
| } |
| |
| Capture(setupCalls, CaptureLinkProgram(replayState, true, id)); |
| CaptureUpdateUniformLocations(program, setupCalls); |
| CaptureUpdateUniformValues(replayState, context, program, resourceTracker, setupCalls); |
| CaptureUpdateUniformBlockIndexes(program, setupCalls); |
| |
| // Capture uniform block bindings for each program |
| for (uint32_t uniformBlockIndex = 0; |
| uniformBlockIndex < static_cast<uint32_t>(executable.getUniformBlocks().size()); |
| uniformBlockIndex++) |
| { |
| GLuint blockBinding = executable.getUniformBlocks()[uniformBlockIndex].pod.inShaderBinding; |
| CallCapture updateCallCapture = |
| CaptureUniformBlockBinding(replayState, true, id, {uniformBlockIndex}, blockBinding); |
| CaptureCustomUniformBlockBinding(updateCallCapture, *setupCalls); |
| } |
| |
| // Add DetachShader call if that's what the app does, so that the |
| // ResourceManagerBase::mHandleAllocator can release the ShaderProgramID handle assigned to the |
| // shader object when glDeleteShader is called. This ensures the ShaderProgramID handles used in |
| // SetupReplayContextShared() are consistent with the ShaderProgramID handles used by the app. |
| for (gl::ShaderType shaderType : executable.getLinkedShaderStages()) |
| { |
| gl::Shader *attachedShader = program->getAttachedShader(shaderType); |
| if (attachedShader == nullptr) |
| { |
| Capture(setupCalls, |
| CaptureDetachShader(replayState, true, id, tempShaderIDTracker[shaderType])); |
| } |
| Capture(setupCalls, |
| CaptureDeleteShader(replayState, true, tempShaderIDTracker[shaderType])); |
| } |
| } |
| |
| // TODO(http://anglebug.com/42263204): Improve reset/restore call generation |
| // There are multiple ways to track reset calls for individual resources. For now, we are tracking |
| // separate lists of instructions that mirror the calls created during mid-execution setup. Other |
| // methods could involve passing the original CallCaptures to this function, or tracking the |
| // indices of original setup calls. |
| void CaptureBufferResetCalls(const gl::Context *context, |
| const gl::State &replayState, |
| ResourceTracker *resourceTracker, |
| gl::BufferID *id, |
| const gl::Buffer *buffer) |
| { |
| GLuint bufferID = (*id).value; |
| |
| // Track this as a starting resource that may need to be restored. |
| TrackedResource &trackedBuffers = |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::Buffer); |
| |
| // Track calls to regenerate a given buffer |
| ResourceCalls &bufferRegenCalls = trackedBuffers.getResourceRegenCalls(); |
| Capture(&bufferRegenCalls[bufferID], CaptureGenBuffers(replayState, true, 1, id)); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, &bufferRegenCalls[bufferID]); |
| |
| // Call glBufferStorageEXT when regenerating immutable buffers, |
| // as we can't call glBufferData on restore. |
| if (buffer->isImmutable()) |
| { |
| Capture(&bufferRegenCalls[bufferID], |
| CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, *id)); |
| Capture( |
| &bufferRegenCalls[bufferID], |
| CaptureBufferStorageEXT(replayState, true, gl::BufferBinding::Array, |
| static_cast<GLsizeiptr>(buffer->getSize()), |
| buffer->getMapPointer(), buffer->getStorageExtUsageFlags())); |
| } |
| |
| // Track calls to restore a given buffer's contents |
| ResourceCalls &bufferRestoreCalls = trackedBuffers.getResourceRestoreCalls(); |
| Capture(&bufferRestoreCalls[bufferID], |
| CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, *id)); |
| |
| // Mutable buffers will be restored here using glBufferData. |
| // Immutable buffers need to be restored below, after maping. |
| if (!buffer->isImmutable()) |
| { |
| Capture(&bufferRestoreCalls[bufferID], |
| CaptureBufferData(replayState, true, gl::BufferBinding::Array, |
| static_cast<GLsizeiptr>(buffer->getSize()), |
| buffer->getMapPointer(), buffer->getUsage())); |
| } |
| |
| if (buffer->isMapped()) |
| { |
| // Track calls to remap a buffer that started as mapped |
| BufferCalls &bufferMapCalls = resourceTracker->getBufferMapCalls(); |
| |
| Capture(&bufferMapCalls[bufferID], |
| CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, *id)); |
| |
| void *dontCare = nullptr; |
| CallCapture mapBufferRange = CaptureMapBufferRange( |
| replayState, true, gl::BufferBinding::Array, |
| static_cast<GLsizeiptr>(buffer->getMapOffset()), |
| static_cast<GLsizeiptr>(buffer->getMapLength()), buffer->getAccessFlags(), dontCare); |
| CaptureCustomMapBuffer("MapBufferRange", mapBufferRange, bufferMapCalls[bufferID], |
| buffer->id()); |
| |
| // Restore immutable mapped buffers. Needs to happen after mapping. |
| if (buffer->isImmutable()) |
| { |
| ParamBuffer dataParamBuffer; |
| dataParamBuffer.addValueParam("dest", ParamType::TGLuint, buffer->id().value); |
| ParamCapture captureData("source", ParamType::TvoidConstPointer); |
| CaptureMemory(buffer->getMapPointer(), static_cast<GLsizeiptr>(buffer->getSize()), |
| &captureData); |
| dataParamBuffer.addParam(std::move(captureData)); |
| dataParamBuffer.addValueParam<GLsizeiptr>("size", ParamType::TGLsizeiptr, |
| static_cast<GLsizeiptr>(buffer->getSize())); |
| bufferMapCalls[bufferID].emplace_back("UpdateClientBufferData", |
| std::move(dataParamBuffer)); |
| } |
| } |
| |
| // Track calls unmap a buffer that started as unmapped |
| BufferCalls &bufferUnmapCalls = resourceTracker->getBufferUnmapCalls(); |
| Capture(&bufferUnmapCalls[bufferID], |
| CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, *id)); |
| Capture(&bufferUnmapCalls[bufferID], |
| CaptureUnmapBuffer(replayState, true, gl::BufferBinding::Array, GL_TRUE)); |
| } |
| |
| void CaptureFenceSyncResetCalls(const gl::Context *context, |
| const gl::State &replayState, |
| ResourceTracker *resourceTracker, |
| gl::SyncID syncID, |
| GLsync syncObject, |
| const gl::Sync *sync) |
| { |
| // Track calls to regenerate a given fence sync |
| FenceSyncCalls &fenceSyncRegenCalls = resourceTracker->getFenceSyncRegenCalls(); |
| CallCapture fenceSync = |
| CaptureFenceSync(replayState, true, sync->getCondition(), sync->getFlags(), syncObject); |
| CaptureCustomFenceSync(fenceSync, fenceSyncRegenCalls[syncID]); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, &fenceSyncRegenCalls[syncID]); |
| } |
| |
| void CaptureEGLSyncResetCalls(const gl::Context *context, |
| const gl::State &replayState, |
| ResourceTracker *resourceTracker, |
| egl::SyncID eglSyncID, |
| EGLSync eglSyncObject, |
| const egl::Sync *eglSync) |
| { |
| // Track this as a starting resource that may need to be restored. |
| TrackedResource &trackedEGLSyncs = |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::egl_Sync); |
| |
| // Track calls to regenerate a given buffer |
| ResourceCalls &eglSyncRegenCalls = trackedEGLSyncs.getResourceRegenCalls(); |
| |
| CallCapture createEGLSync = |
| CaptureCreateSyncKHR(nullptr, true, context->getDisplay(), eglSync->getType(), |
| eglSync->getAttributeMap(), eglSyncObject); |
| CaptureCustomCreateEGLSync("CreateEGLSyncKHR", createEGLSync, |
| eglSyncRegenCalls[eglSyncID.value]); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, &eglSyncRegenCalls[eglSyncID.value]); |
| } |
| |
| void CaptureBufferBindingResetCalls(const gl::State &replayState, |
| ResourceTracker *resourceTracker, |
| gl::BufferBinding binding, |
| gl::BufferID id) |
| { |
| // Track the calls to reset it |
| std::vector<CallCapture> &bufferBindingCalls = resourceTracker->getBufferBindingCalls(); |
| Capture(&bufferBindingCalls, CaptureBindBuffer(replayState, true, binding, id)); |
| } |
| |
| void CaptureIndexedBuffers(const gl::State &glState, |
| const gl::BufferVector &indexedBuffers, |
| gl::BufferBinding binding, |
| std::vector<CallCapture> *setupCalls) |
| { |
| for (unsigned int index = 0; index < indexedBuffers.size(); ++index) |
| { |
| const gl::OffsetBindingPointer<gl::Buffer> &buffer = indexedBuffers[index]; |
| |
| if (buffer.get() == nullptr) |
| { |
| continue; |
| } |
| |
| GLintptr offset = buffer.getOffset(); |
| GLsizeiptr size = buffer.getSize(); |
| gl::BufferID bufferID = buffer.get()->id(); |
| |
| // Context::bindBufferBase() calls Context::bindBufferRange() with size and offset = 0. |
| if ((offset == 0) && (size == 0)) |
| { |
| Capture(setupCalls, CaptureBindBufferBase(glState, true, binding, index, bufferID)); |
| } |
| else |
| { |
| Capture(setupCalls, |
| CaptureBindBufferRange(glState, true, binding, index, bufferID, offset, size)); |
| } |
| } |
| } |
| |
| void CaptureDefaultVertexAttribs(const gl::State &replayState, |
| const gl::State &apiState, |
| std::vector<CallCapture> *setupCalls) |
| { |
| const std::vector<gl::VertexAttribCurrentValueData> ¤tValues = |
| apiState.getVertexAttribCurrentValues(); |
| |
| for (GLuint attribIndex = 0; attribIndex < currentValues.size(); ++attribIndex) |
| { |
| const gl::VertexAttribCurrentValueData &defaultValue = currentValues[attribIndex]; |
| if (!IsDefaultCurrentValue(defaultValue)) |
| { |
| Capture(setupCalls, CaptureVertexAttrib4fv(replayState, true, attribIndex, |
| defaultValue.Values.FloatValues)); |
| } |
| } |
| } |
| |
| void CaptureBindFramebufferForContext(const gl::Context *context, |
| std::vector<CallCapture> *calls, |
| FramebufferCaptureFuncs &framebufferFuncs, |
| const gl::State &glState, |
| GLenum target, |
| const gl::FramebufferID &id) |
| { |
| Capture(calls, framebufferFuncs.bindFramebuffer(glState, true, target, id)); |
| |
| // Set current context for this CallCapture since we track these in gFramebufferMapPerContext |
| calls->back().contextID = context->id(); |
| } |
| |
| void CompressPalettedTexture(angle::MemoryBuffer &data, |
| angle::MemoryBuffer &tmp, |
| const gl::InternalFormat &compressedFormat, |
| const gl::Extents &extents) |
| { |
| constexpr int uncompressedChannelCount = 4; |
| |
| uint32_t indexBits = 0, redBlueBits = 0, greenBits = 0, alphaBits = 0; |
| switch (compressedFormat.internalFormat) |
| { |
| case GL_PALETTE4_RGB8_OES: |
| indexBits = 4; |
| redBlueBits = 8; |
| greenBits = 8; |
| alphaBits = 0; |
| break; |
| case GL_PALETTE4_RGBA8_OES: |
| indexBits = 4; |
| redBlueBits = 8; |
| greenBits = 8; |
| alphaBits = 8; |
| break; |
| case GL_PALETTE4_R5_G6_B5_OES: |
| indexBits = 4; |
| redBlueBits = 5; |
| greenBits = 6; |
| alphaBits = 0; |
| break; |
| case GL_PALETTE4_RGBA4_OES: |
| indexBits = 4; |
| redBlueBits = 4; |
| greenBits = 4; |
| alphaBits = 4; |
| break; |
| case GL_PALETTE4_RGB5_A1_OES: |
| indexBits = 4; |
| redBlueBits = 5; |
| greenBits = 5; |
| alphaBits = 1; |
| break; |
| case GL_PALETTE8_RGB8_OES: |
| indexBits = 8; |
| redBlueBits = 8; |
| greenBits = 8; |
| alphaBits = 0; |
| break; |
| case GL_PALETTE8_RGBA8_OES: |
| indexBits = 8; |
| redBlueBits = 8; |
| greenBits = 8; |
| alphaBits = 8; |
| break; |
| case GL_PALETTE8_R5_G6_B5_OES: |
| indexBits = 8; |
| redBlueBits = 5; |
| greenBits = 6; |
| alphaBits = 0; |
| break; |
| case GL_PALETTE8_RGBA4_OES: |
| indexBits = 8; |
| redBlueBits = 4; |
| greenBits = 4; |
| alphaBits = 4; |
| break; |
| case GL_PALETTE8_RGB5_A1_OES: |
| indexBits = 8; |
| redBlueBits = 5; |
| greenBits = 5; |
| alphaBits = 1; |
| break; |
| |
| default: |
| UNREACHABLE(); |
| break; |
| } |
| |
| bool result = data.resize( |
| // Palette size |
| (1 << indexBits) * (2 * redBlueBits + greenBits + alphaBits) / 8 + |
| // Texels size |
| indexBits * extents.width * extents.height * extents.depth / 8); |
| ASSERT(result); |
| |
| angle::StoreRGBA8ToPalettedImpl( |
| extents.width, extents.height, extents.depth, indexBits, redBlueBits, greenBits, alphaBits, |
| tmp.data(), |
| uncompressedChannelCount * extents.width, // inputRowPitch |
| uncompressedChannelCount * extents.width * extents.height, // inputDepthPitch |
| data.data(), // output |
| indexBits * extents.width / 8, // outputRowPitch |
| indexBits * extents.width * extents.height / 8 // outputDepthPitch |
| ); |
| } |
| |
| // Capture the setup of the state that's shared by all of the contexts in the share group |
| // See IsSharedObjectResource for the list of objects covered here. |
| void CaptureShareGroupMidExecutionSetup( |
| gl::Context *context, |
| std::vector<CallCapture> *setupCalls, |
| ResourceTracker *resourceTracker, |
| gl::State &replayState, |
| const PackedEnumMap<ResourceIDType, uint32_t> &maxAccessedResourceIDs) |
| { |
| FrameCaptureShared *frameCaptureShared = context->getShareGroup()->getFrameCaptureShared(); |
| const gl::State &apiState = context->getState(); |
| |
| // Small helper function to make the code more readable. |
| auto cap = [setupCalls](CallCapture &&call) { setupCalls->emplace_back(std::move(call)); }; |
| |
| // Capture Buffer data. |
| const gl::BufferManager &buffers = apiState.getBufferManagerForCapture(); |
| for (const auto &bufferIter : gl::UnsafeResourceMapIter(buffers.getResourcesForCapture())) |
| { |
| gl::BufferID id = {bufferIter.first}; |
| gl::Buffer *buffer = bufferIter.second; |
| |
| if (id.value == 0) |
| { |
| continue; |
| } |
| |
| // Generate binding. |
| cap(CaptureGenBuffers(replayState, true, 1, &id)); |
| |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::Buffer) |
| .getStartingResources() |
| .insert(id.value); |
| |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls); |
| |
| // glBufferData. Would possibly be better implemented using a getData impl method. |
| // Saving buffers that are mapped during a swap is not yet handled. |
| if (buffer->getSize() == 0) |
| { |
| resourceTracker->setStartingBufferMapped(buffer->id().value, false); |
| continue; |
| } |
| |
| // Remember if the buffer was already mapped |
| GLboolean bufferMapped = buffer->isMapped(); |
| |
| // If needed, map the buffer so we can capture its contents |
| if (!bufferMapped) |
| { |
| (void)buffer->mapRange(context, 0, static_cast<GLsizeiptr>(buffer->getSize()), |
| GL_MAP_READ_BIT); |
| } |
| |
| // Always use the array buffer binding point to upload data to keep things simple. |
| if (buffer != replayState.getArrayBuffer()) |
| { |
| replayState.setBufferBinding(context, gl::BufferBinding::Array, buffer); |
| cap(CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, id)); |
| } |
| |
| if (buffer->isImmutable()) |
| { |
| cap(CaptureBufferStorageEXT(replayState, true, gl::BufferBinding::Array, |
| static_cast<GLsizeiptr>(buffer->getSize()), |
| buffer->getMapPointer(), |
| buffer->getStorageExtUsageFlags())); |
| } |
| else |
| { |
| cap(CaptureBufferData(replayState, true, gl::BufferBinding::Array, |
| static_cast<GLsizeiptr>(buffer->getSize()), |
| buffer->getMapPointer(), buffer->getUsage())); |
| } |
| |
| if (bufferMapped) |
| { |
| void *dontCare = nullptr; |
| CallCapture mapBufferRange = |
| CaptureMapBufferRange(replayState, true, gl::BufferBinding::Array, |
| static_cast<GLsizeiptr>(buffer->getMapOffset()), |
| static_cast<GLsizeiptr>(buffer->getMapLength()), |
| buffer->getAccessFlags(), dontCare); |
| CaptureCustomMapBuffer("MapBufferRange", mapBufferRange, *setupCalls, buffer->id()); |
| |
| resourceTracker->setStartingBufferMapped(buffer->id().value, true); |
| |
| frameCaptureShared->trackBufferMapping( |
| context, &setupCalls->back(), buffer->id(), buffer, |
| static_cast<GLsizeiptr>(buffer->getMapOffset()), |
| static_cast<GLsizeiptr>(buffer->getMapLength()), |
| (buffer->getAccessFlags() & GL_MAP_WRITE_BIT) != 0, |
| (buffer->getStorageExtUsageFlags() & GL_MAP_COHERENT_BIT_EXT) != 0); |
| } |
| else |
| { |
| resourceTracker->setStartingBufferMapped(buffer->id().value, false); |
| } |
| |
| // Generate the calls needed to restore this buffer to original state for frame looping |
| CaptureBufferResetCalls(context, replayState, resourceTracker, &id, buffer); |
| |
| // Unmap the buffer if it wasn't already mapped |
| if (!bufferMapped) |
| { |
| GLboolean dontCare; |
| (void)buffer->unmap(context, &dontCare); |
| } |
| } |
| |
| // Clear the array buffer binding. |
| if (replayState.getTargetBuffer(gl::BufferBinding::Array)) |
| { |
| cap(CaptureBindBuffer(replayState, true, gl::BufferBinding::Array, {0})); |
| replayState.setBufferBinding(context, gl::BufferBinding::Array, nullptr); |
| } |
| |
| // Set a unpack alignment of 1. Otherwise, computeRowPitch() will compute the wrong value, |
| // leading to a crash in memcpy() when capturing the texture contents. |
| gl::PixelUnpackState ¤tUnpackState = replayState.getUnpackState(); |
| if (currentUnpackState.alignment != 1) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_UNPACK_ALIGNMENT, 1)); |
| replayState.getMutablePrivateStateForCapture()->setUnpackAlignment(1); |
| } |
| |
| const egl::ImageMap eglImageMap = context->getDisplay()->getImagesForCapture(); |
| for (const auto &[eglImageID, eglImage] : eglImageMap) |
| { |
| // Track this as a starting resource that may need to be restored. |
| TrackedResource &trackedImages = |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::Image); |
| trackedImages.getStartingResources().insert(eglImageID); |
| |
| ResourceCalls &imageRegenCalls = trackedImages.getResourceRegenCalls(); |
| CallVector imageGenCalls({setupCalls, &imageRegenCalls[eglImageID]}); |
| |
| auto eglImageAttribIter = resourceTracker->getImageToAttribTable().find( |
| reinterpret_cast<EGLImage>(static_cast<uintptr_t>(eglImageID))); |
| ASSERT(eglImageAttribIter != resourceTracker->getImageToAttribTable().end()); |
| const egl::AttributeMap &attribs = eglImageAttribIter->second; |
| |
| for (std::vector<CallCapture> *calls : imageGenCalls) |
| { |
| // Create the image on demand with the same attrib retrieved above |
| CallCapture eglCreateImageKHRCall = egl::CaptureCreateImageKHR( |
| nullptr, true, nullptr, context->id(), EGL_GL_TEXTURE_2D, |
| reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(0)), attribs, |
| reinterpret_cast<EGLImage>(static_cast<uintptr_t>(eglImageID))); |
| |
| // Convert the CaptureCreateImageKHR CallCapture to the customized CallCapture |
| CaptureCustomCreateEGLImage(context, "CreateEGLImageKHR", eglImage->getWidth(), |
| eglImage->getHeight(), eglCreateImageKHRCall, *calls); |
| } |
| } |
| |
| // Capture Texture setup and data. |
| const gl::TextureManager &textures = apiState.getTextureManagerForCapture(); |
| |
| for (const auto &textureIter : gl::UnsafeResourceMapIter(textures.getResourcesForCapture())) |
| { |
| gl::TextureID id = {textureIter.first}; |
| gl::Texture *texture = textureIter.second; |
| |
| if (id.value == 0) |
| { |
| continue; |
| } |
| |
| size_t textureSetupStart = setupCalls->size(); |
| |
| // Track this as a starting resource that may need to be restored. |
| TrackedResource &trackedTextures = |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::Texture); |
| ResourceSet &startingTextures = trackedTextures.getStartingResources(); |
| startingTextures.insert(id.value); |
| |
| // For the initial texture creation calls, track in the generate list |
| ResourceCalls &textureRegenCalls = trackedTextures.getResourceRegenCalls(); |
| CallVector texGenCalls({setupCalls, &textureRegenCalls[id.value]}); |
| |
| // Gen the Texture. |
| for (std::vector<CallCapture> *calls : texGenCalls) |
| { |
| Capture(calls, CaptureGenTextures(replayState, true, 1, &id)); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, calls); |
| } |
| |
| // For the remaining texture setup calls, track in the restore list |
| ResourceCalls &textureRestoreCalls = trackedTextures.getResourceRestoreCalls(); |
| CallVector texSetupCalls({setupCalls, &textureRestoreCalls[id.value]}); |
| |
| // For each texture, set the correct active texture and binding |
| // There is similar code in CaptureMidExecutionSetup for per-context setup |
| const gl::TextureBindingMap ¤tBoundTextures = apiState.getBoundTexturesForCapture(); |
| const gl::TextureBindingVector ¤tBindings = currentBoundTextures[texture->getType()]; |
| const gl::TextureBindingVector &replayBindings = |
| replayState.getBoundTexturesForCapture()[texture->getType()]; |
| ASSERT(currentBindings.size() == replayBindings.size()); |
| |
| // Look up the replay binding |
| size_t replayActiveTexture = replayState.getActiveSampler(); |
| // If there ends up being no binding, just use the replay binding |
| // TODO: We may want to start using index 0 for everything, mark it dirty, and restore it |
| size_t currentActiveTexture = replayActiveTexture; |
| |
| // Iterate through current bindings and find the correct index for this texture ID |
| for (size_t bindingIndex = 0; bindingIndex < currentBindings.size(); ++bindingIndex) |
| { |
| gl::TextureID currentTextureID = currentBindings[bindingIndex].id(); |
| gl::TextureID replayTextureID = replayBindings[bindingIndex].id(); |
| |
| // Only check the texture we care about |
| if (currentTextureID == texture->id()) |
| { |
| // If the binding doesn't match, track it |
| if (currentTextureID != replayTextureID) |
| { |
| currentActiveTexture = bindingIndex; |
| } |
| |
| break; |
| } |
| } |
| |
| // Set the correct active texture before performing any state changes, including binding |
| if (currentActiveTexture != replayActiveTexture) |
| { |
| for (std::vector<CallCapture> *calls : texSetupCalls) |
| { |
| Capture(calls, CaptureActiveTexture( |
| replayState, true, |
| GL_TEXTURE0 + static_cast<GLenum>(currentActiveTexture))); |
| } |
| replayState.getMutablePrivateStateForCapture()->setActiveSampler( |
| static_cast<unsigned int>(currentActiveTexture)); |
| } |
| |
| for (std::vector<CallCapture> *calls : texSetupCalls) |
| { |
| Capture(calls, CaptureBindTexture(replayState, true, texture->getType(), id)); |
| } |
| replayState.setSamplerTexture(context, texture->getType(), texture); |
| |
| // Capture sampler parameter states. |
| // TODO(jmadill): More sampler / texture states. http://anglebug.com/42262323 |
| gl::SamplerState defaultSamplerState = |
| gl::SamplerState::CreateDefaultForTarget(texture->getType()); |
| const gl::SamplerState &textureSamplerState = texture->getSamplerState(); |
| |
| auto capTexParam = [&replayState, texture, &texSetupCalls](GLenum pname, GLint param) { |
| for (std::vector<CallCapture> *calls : texSetupCalls) |
| { |
| Capture(calls, |
| CaptureTexParameteri(replayState, true, texture->getType(), pname, param)); |
| } |
| }; |
| |
| auto capTexParamf = [&replayState, texture, &texSetupCalls](GLenum pname, GLfloat param) { |
| for (std::vector<CallCapture> *calls : texSetupCalls) |
| { |
| Capture(calls, |
| CaptureTexParameterf(replayState, true, texture->getType(), pname, param)); |
| } |
| }; |
| |
| if (textureSamplerState.getMinFilter() != defaultSamplerState.getMinFilter()) |
| { |
| capTexParam(GL_TEXTURE_MIN_FILTER, textureSamplerState.getMinFilter()); |
| } |
| |
| if (textureSamplerState.getMagFilter() != defaultSamplerState.getMagFilter()) |
| { |
| capTexParam(GL_TEXTURE_MAG_FILTER, textureSamplerState.getMagFilter()); |
| } |
| |
| if (textureSamplerState.getWrapR() != defaultSamplerState.getWrapR()) |
| { |
| capTexParam(GL_TEXTURE_WRAP_R, textureSamplerState.getWrapR()); |
| } |
| |
| if (textureSamplerState.getWrapS() != defaultSamplerState.getWrapS()) |
| { |
| capTexParam(GL_TEXTURE_WRAP_S, textureSamplerState.getWrapS()); |
| } |
| |
| if (textureSamplerState.getWrapT() != defaultSamplerState.getWrapT()) |
| { |
| capTexParam(GL_TEXTURE_WRAP_T, textureSamplerState.getWrapT()); |
| } |
| |
| if (textureSamplerState.getMinLod() != defaultSamplerState.getMinLod()) |
| { |
| capTexParamf(GL_TEXTURE_MIN_LOD, textureSamplerState.getMinLod()); |
| } |
| |
| if (textureSamplerState.getMaxLod() != defaultSamplerState.getMaxLod()) |
| { |
| capTexParamf(GL_TEXTURE_MAX_LOD, textureSamplerState.getMaxLod()); |
| } |
| |
| if (textureSamplerState.getCompareMode() != defaultSamplerState.getCompareMode()) |
| { |
| capTexParam(GL_TEXTURE_COMPARE_MODE, textureSamplerState.getCompareMode()); |
| } |
| |
| if (textureSamplerState.getCompareFunc() != defaultSamplerState.getCompareFunc()) |
| { |
| capTexParam(GL_TEXTURE_COMPARE_FUNC, textureSamplerState.getCompareFunc()); |
| } |
| |
| // Texture parameters |
| if (texture->getSwizzleRed() != GL_RED) |
| { |
| capTexParam(GL_TEXTURE_SWIZZLE_R, texture->getSwizzleRed()); |
| } |
| |
| if (texture->getSwizzleGreen() != GL_GREEN) |
| { |
| capTexParam(GL_TEXTURE_SWIZZLE_G, texture->getSwizzleGreen()); |
| } |
| |
| if (texture->getSwizzleBlue() != GL_BLUE) |
| { |
| capTexParam(GL_TEXTURE_SWIZZLE_B, texture->getSwizzleBlue()); |
| } |
| |
| if (texture->getSwizzleAlpha() != GL_ALPHA) |
| { |
| capTexParam(GL_TEXTURE_SWIZZLE_A, texture->getSwizzleAlpha()); |
| } |
| |
| if (texture->getBaseLevel() != 0) |
| { |
| capTexParam(GL_TEXTURE_BASE_LEVEL, texture->getBaseLevel()); |
| } |
| |
| if (texture->getMaxLevel() != 1000) |
| { |
| capTexParam(GL_TEXTURE_MAX_LEVEL, texture->getMaxLevel()); |
| } |
| |
| // If the texture is immutable, initialize it with TexStorage |
| if (texture->getImmutableFormat()) |
| { |
| // We can only call TexStorage *once* on an immutable texture, so it needs special |
| // handling. To solve this, immutable textures will have a BindTexture and TexStorage as |
| // part of their textureRegenCalls. The resulting regen sequence will be: |
| // |
| // const GLuint glDeleteTextures_texturesPacked_0[] = { gTextureMap[52] }; |
| // glDeleteTextures(1, glDeleteTextures_texturesPacked_0); |
| // glGenTextures(1, reinterpret_cast<GLuint *>(gReadBuffer)); |
| // UpdateTextureID(52, 0); |
| // glBindTexture(GL_TEXTURE_2D, gTextureMap[52]); |
| // glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8, 256, 512); |
| |
| // Bind the texture first just for textureRegenCalls |
| Capture(&textureRegenCalls[id.value], |
| CaptureBindTexture(replayState, true, texture->getType(), id)); |
| |
| // Then add TexStorage to texGenCalls instead of texSetupCalls |
| for (std::vector<CallCapture> *calls : texGenCalls) |
| { |
| CaptureTextureStorage(calls, &replayState, texture); |
| } |
| } |
| |
| // Iterate texture levels and layers. |
| gl::ImageIndexIterator imageIter = gl::ImageIndexIterator::MakeGeneric( |
| texture->getType(), 0, texture->getMipmapMaxLevel() + 1, gl::ImageIndex::kEntireLevel, |
| gl::ImageIndex::kEntireLevel); |
| while (imageIter.hasNext()) |
| { |
| gl::ImageIndex index = imageIter.next(); |
| |
| const gl::ImageDesc &desc = texture->getTextureState().getImageDesc(index); |
| |
| if (desc.size.empty()) |
| { |
| continue; |
| } |
| |
| const gl::InternalFormat &format = *desc.format.info; |
| |
| bool supportedType = (index.getType() == gl::TextureType::_2D || |
| index.getType() == gl::TextureType::_3D || |
| index.getType() == gl::TextureType::_2DArray || |
| index.getType() == gl::TextureType::Buffer || |
| index.getType() == gl::TextureType::CubeMap || |
| index.getType() == gl::TextureType::CubeMapArray || |
| index.getType() == gl::TextureType::External); |
| |
| // Check for supported textures |
| if (!supportedType) |
| { |
| ERR() << "Unsupported texture type: " << index.getType(); |
| UNREACHABLE(); |
| } |
| |
| if (index.getType() == gl::TextureType::Buffer) |
| { |
| // The buffer contents are already backed up, but we need to emit the TexBuffer |
| // binding calls |
| for (std::vector<CallCapture> *calls : texSetupCalls) |
| { |
| CaptureTextureContents(calls, &replayState, texture, index, desc, 0, 0); |
| } |
| continue; |
| } |
| |
| if (index.getType() == gl::TextureType::External) |
| { |
| // Lookup the eglImage ID associated with this texture when the app issued |
| // glEGLImageTargetTexture2DOES() |
| auto eglImageIter = resourceTracker->getTextureIDToImageTable().find(id.value); |
| egl::ImageID eglImageID; |
| if (eglImageIter != resourceTracker->getTextureIDToImageTable().end()) |
| { |
| eglImageID = eglImageIter->second; |
| } |
| else |
| { |
| // Original image was deleted and needs to be recreated first |
| eglImageID = {maxAccessedResourceIDs[ResourceIDType::Image] + 1}; |
| for (std::vector<CallCapture> *calls : texSetupCalls) |
| { |
| egl::AttributeMap attribs = egl::AttributeMap::CreateFromIntArray(nullptr); |
| CallCapture eglCreateImageKHRCall = egl::CaptureCreateImageKHR( |
| nullptr, true, nullptr, context->id(), EGL_GL_TEXTURE_2D, |
| reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(0)), attribs, |
| reinterpret_cast<EGLImage>(static_cast<uintptr_t>(eglImageID.value))); |
| CaptureCustomCreateEGLImage(context, "CreateEGLImageKHR", desc.size.width, |
| desc.size.height, eglCreateImageKHRCall, |
| *calls); |
| } |
| } |
| // Pass the eglImage to the texture that is bound to GL_TEXTURE_EXTERNAL_OES target |
| for (std::vector<CallCapture> *calls : texSetupCalls) |
| { |
| Capture(calls, CaptureEGLImageTargetTexture2DOES( |
| replayState, true, gl::TextureType::External, eglImageID)); |
| } |
| } |
| else if (context->getExtensions().getImageANGLE) |
| { |
| // Use ANGLE_get_image to read back pixel data. |
| angle::MemoryBuffer data; |
| |
| const gl::Extents extents(desc.size.width, desc.size.height, desc.size.depth); |
| |
| gl::PixelPackState packState; |
| packState.alignment = 1; |
| |
| if (format.paletted) |
| { |
| // Read back the uncompressed texture, then re-compress it |
| // to store in the trace. |
| |
| angle::MemoryBuffer tmp; |
| |
| // The uncompressed format (R8G8B8A8) is 4 bytes per texel |
| bool result = tmp.resize(4 * extents.width * extents.height * extents.depth); |
| ASSERT(result); |
| |
| (void)texture->getTexImage(context, packState, nullptr, index.getTarget(), |
| index.getLevelIndex(), GL_RGBA, GL_UNSIGNED_BYTE, |
| tmp.data()); |
| |
| CompressPalettedTexture(data, tmp, format, extents); |
| } |
| else if (format.compressed) |
| { |
| // Calculate the size needed to store the compressed level |
| GLuint sizeInBytes; |
| bool result = format.computeCompressedImageSize(extents, &sizeInBytes); |
| ASSERT(result); |
| |
| result = data.resize(sizeInBytes); |
| ASSERT(result); |
| |
| (void)texture->getCompressedTexImage(context, packState, nullptr, |
| index.getTarget(), index.getLevelIndex(), |
| data.data()); |
| } |
| else |
| { |
| GLenum getFormat = format.format; |
| GLenum getType = format.type; |
| |
| const gl::PixelUnpackState &unpack = apiState.getUnpackState(); |
| |
| GLuint endByte = 0; |
| bool unpackSize = |
| format.computePackUnpackEndByte(getType, extents, unpack, true, &endByte); |
| ASSERT(unpackSize); |
| |
| bool result = data.resize(endByte); |
| ASSERT(result); |
| |
| (void)texture->getTexImage(context, packState, nullptr, index.getTarget(), |
| index.getLevelIndex(), getFormat, getType, |
| data.data()); |
| } |
| |
| for (std::vector<CallCapture> *calls : texSetupCalls) |
| { |
| CaptureTextureContents(calls, &replayState, texture, index, desc, |
| static_cast<GLuint>(data.size()), data.data()); |
| } |
| } |
| else |
| { |
| for (std::vector<CallCapture> *calls : texSetupCalls) |
| { |
| CaptureTextureContents(calls, &replayState, texture, index, desc, 0, nullptr); |
| } |
| } |
| } |
| |
| size_t textureSetupEnd = setupCalls->size(); |
| |
| // Mark the range of calls used to setup this texture |
| frameCaptureShared->markResourceSetupCallsInactive( |
| setupCalls, ResourceIDType::Texture, id.value, |
| gl::Range<size_t>(textureSetupStart, textureSetupEnd)); |
| } |
| |
| // Capture Renderbuffers. |
| const gl::RenderbufferManager &renderbuffers = apiState.getRenderbufferManagerForCapture(); |
| FramebufferCaptureFuncs framebufferFuncs(context->isGLES1()); |
| |
| for (const auto &renderbufIter : |
| gl::UnsafeResourceMapIter(renderbuffers.getResourcesForCapture())) |
| { |
| gl::RenderbufferID id = {renderbufIter.first}; |
| const gl::Renderbuffer *renderbuffer = renderbufIter.second; |
| |
| // Track this as a starting resource that may need to be restored. |
| TrackedResource &trackedRenderbuffers = |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::Renderbuffer); |
| ResourceSet &startingRenderbuffers = trackedRenderbuffers.getStartingResources(); |
| startingRenderbuffers.insert(id.value); |
| |
| // For the initial renderbuffer creation calls, track in the generate list |
| ResourceCalls &renderbufferRegenCalls = trackedRenderbuffers.getResourceRegenCalls(); |
| CallVector rbGenCalls({setupCalls, &renderbufferRegenCalls[id.value]}); |
| |
| // Generate renderbuffer id. |
| for (std::vector<CallCapture> *calls : rbGenCalls) |
| { |
| Capture(calls, framebufferFuncs.genRenderbuffers(replayState, true, 1, &id)); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, calls); |
| Capture(calls, |
| framebufferFuncs.bindRenderbuffer(replayState, true, GL_RENDERBUFFER, id)); |
| } |
| |
| GLenum internalformat = renderbuffer->getFormat().info->internalFormat; |
| |
| if (renderbuffer->getSamples() > 0) |
| { |
| // Note: We could also use extensions if available. |
| for (std::vector<CallCapture> *calls : rbGenCalls) |
| { |
| Capture(calls, |
| CaptureRenderbufferStorageMultisample( |
| replayState, true, GL_RENDERBUFFER, renderbuffer->getSamples(), |
| internalformat, renderbuffer->getWidth(), renderbuffer->getHeight())); |
| } |
| } |
| else |
| { |
| for (std::vector<CallCapture> *calls : rbGenCalls) |
| { |
| Capture(calls, framebufferFuncs.renderbufferStorage( |
| replayState, true, GL_RENDERBUFFER, internalformat, |
| renderbuffer->getWidth(), renderbuffer->getHeight())); |
| } |
| } |
| |
| // TODO: Capture renderbuffer contents. http://anglebug.com/42262323 |
| } |
| |
| // Capture Shaders and Programs. |
| const gl::ShaderProgramManager &shadersAndPrograms = |
| apiState.getShaderProgramManagerForCapture(); |
| const gl::ResourceMap<gl::Shader, gl::ShaderProgramID> &shaders = |
| shadersAndPrograms.getShadersForCapture(); |
| const gl::ResourceMap<gl::Program, gl::ShaderProgramID> &programs = |
| shadersAndPrograms.getProgramsForCaptureAndPerf(); |
| |
| TrackedResource &trackedShaderPrograms = |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::ShaderProgram); |
| |
| // Capture Program binary state. |
| gl::ShaderProgramID tempShaderStartID = {resourceTracker->getMaxShaderPrograms()}; |
| std::map<gl::ShaderProgramID, std::vector<gl::ShaderProgramID>> deferredAttachCalls; |
| for (const auto &programIter : gl::UnsafeResourceMapIter(programs)) |
| { |
| gl::ShaderProgramID id = {programIter.first}; |
| gl::Program *program = programIter.second; |
| |
| // Unlinked programs don't have an executable so track in case linking is deferred |
| // Programs are shared by contexts in the share group and only need to be captured once. |
| if (!program->isLinked()) |
| { |
| frameCaptureShared->setDeferredLinkProgram(id); |
| |
| if (program->getAttachedShadersCount() > 0) |
| { |
| // AttachShader calls will be generated at shader-handling time |
| for (gl::ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| gl::Shader *shader = program->getAttachedShader(shaderType); |
| if (shader != nullptr) |
| { |
| deferredAttachCalls[shader->getHandle()].push_back(id); |
| } |
| } |
| } |
| else |
| { |
| WARN() << "Deferred attachment of shaders is not yet supported"; |
| } |
| } |
| |
| size_t programSetupStart = setupCalls->size(); |
| |
| // Create two lists for program regen calls |
| ResourceCalls &shaderProgramRegenCalls = trackedShaderPrograms.getResourceRegenCalls(); |
| CallVector programRegenCalls({setupCalls, &shaderProgramRegenCalls[id.value]}); |
| |
| for (std::vector<CallCapture> *calls : programRegenCalls) |
| { |
| CallCapture createProgram = CaptureCreateProgram(replayState, true, id.value); |
| CaptureCustomShaderProgram("CreateProgram", createProgram, *calls); |
| } |
| |
| if (program->isLinked()) |
| { |
| // Get last linked shader source. |
| const ProgramSources &linkedSources = |
| context->getShareGroup()->getFrameCaptureShared()->getProgramSources(id); |
| |
| // Create two lists for program restore calls |
| ResourceCalls &shaderProgramRestoreCalls = |
| trackedShaderPrograms.getResourceRestoreCalls(); |
| CallVector programRestoreCalls({setupCalls, &shaderProgramRestoreCalls[id.value]}); |
| |
| for (std::vector<CallCapture> *calls : programRestoreCalls) |
| { |
| GenerateLinkedProgram(context, replayState, resourceTracker, calls, program, id, |
| tempShaderStartID, linkedSources); |
| } |
| |
| // Update the program in replayState |
| if (!replayState.getProgram() || replayState.getProgram()->id() != program->id()) |
| { |
| // Note: We don't do this in GenerateLinkedProgram because it can't modify state |
| (void)replayState.setProgram(context, program); |
| } |
| } |
| |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::ShaderProgram) |
| .getStartingResources() |
| .insert(id.value); |
| resourceTracker->setShaderProgramType(id, ShaderProgramType::ProgramType); |
| |
| // Mark linked programs/shaders as inactive, leaving deferred-linked programs/shaders marked |
| // as active |
| if (!frameCaptureShared->isDeferredLinkProgram(id)) |
| { |
| size_t programSetupEnd = setupCalls->size(); |
| |
| // Mark the range of calls used to setup this program |
| frameCaptureShared->markResourceSetupCallsInactive( |
| setupCalls, ResourceIDType::ShaderProgram, id.value, |
| gl::Range<size_t>(programSetupStart, programSetupEnd)); |
| } |
| } |
| |
| // Handle shaders. |
| for (const auto &shaderIter : gl::UnsafeResourceMapIter(shaders)) |
| { |
| gl::ShaderProgramID id = {shaderIter.first}; |
| gl::Shader *shader = shaderIter.second; |
| |
| // Skip shaders scheduled for deletion. |
| // Shaders are shared by contexts in the share group and only need to be captured once. |
| if (shader->hasBeenDeleted()) |
| { |
| continue; |
| } |
| |
| size_t shaderSetupStart = setupCalls->size(); |
| |
| // Create two lists for shader regen calls |
| ResourceCalls &shaderProgramRegenCalls = trackedShaderPrograms.getResourceRegenCalls(); |
| CallVector shaderRegenCalls({setupCalls, &shaderProgramRegenCalls[id.value]}); |
| |
| for (std::vector<CallCapture> *calls : shaderRegenCalls) |
| { |
| CallCapture createShader = |
| CaptureCreateShader(replayState, true, shader->getType(), id.value); |
| CaptureCustomShaderProgram("CreateShader", createShader, *calls); |
| |
| // If unlinked programs have been created which reference this shader emit corresponding |
| // attach calls |
| for (const auto deferredAttachedProgramID : deferredAttachCalls[id]) |
| { |
| CallCapture attachShader = |
| CaptureAttachShader(replayState, true, deferredAttachedProgramID, id); |
| calls->emplace_back(std::move(attachShader)); |
| } |
| } |
| |
| std::string shaderSource = shader->getSourceString(); |
| const char *sourcePointer = shaderSource.empty() ? nullptr : shaderSource.c_str(); |
| |
| // Create two lists for shader restore calls |
| ResourceCalls &shaderProgramRestoreCalls = trackedShaderPrograms.getResourceRestoreCalls(); |
| CallVector shaderRestoreCalls({setupCalls, &shaderProgramRestoreCalls[id.value]}); |
| |
| // This does not handle some more tricky situations like attaching and then deleting a |
| // shader. |
| // TODO(jmadill): Handle trickier program uses. http://anglebug.com/42262323 |
| if (shader->isCompiled(context)) |
| { |
| const std::string &capturedSource = |
| context->getShareGroup()->getFrameCaptureShared()->getShaderSource(id); |
| if (capturedSource != shaderSource) |
| { |
| ASSERT(!capturedSource.empty()); |
| sourcePointer = capturedSource.c_str(); |
| } |
| |
| for (std::vector<CallCapture> *calls : shaderRestoreCalls) |
| { |
| Capture(calls, |
| CaptureShaderSource(replayState, true, id, 1, &sourcePointer, nullptr)); |
| Capture(calls, CaptureCompileShader(replayState, true, id)); |
| } |
| } |
| |
| if (sourcePointer && |
| (!shader->isCompiled(context) || sourcePointer != shaderSource.c_str())) |
| { |
| for (std::vector<CallCapture> *calls : shaderRestoreCalls) |
| { |
| Capture(calls, |
| CaptureShaderSource(replayState, true, id, 1, &sourcePointer, nullptr)); |
| } |
| } |
| |
| // Deferred-linked programs/shaders must be left marked as active |
| if (deferredAttachCalls[id].empty()) |
| { |
| // Mark the range of calls used to setup this shader |
| frameCaptureShared->markResourceSetupCallsInactive( |
| setupCalls, ResourceIDType::ShaderProgram, id.value, |
| gl::Range<size_t>(shaderSetupStart, setupCalls->size())); |
| } |
| |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::ShaderProgram) |
| .getStartingResources() |
| .insert(id.value); |
| resourceTracker->setShaderProgramType(id, ShaderProgramType::ShaderType); |
| } |
| |
| // Capture Sampler Objects |
| const gl::SamplerManager &samplers = apiState.getSamplerManagerForCapture(); |
| for (const auto &samplerIter : gl::UnsafeResourceMapIter(samplers.getResourcesForCapture())) |
| { |
| gl::SamplerID samplerID = {samplerIter.first}; |
| |
| // Don't gen the sampler if we've seen it before, since they are shared across the context |
| // share group. |
| cap(CaptureGenSamplers(replayState, true, 1, &samplerID)); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls); |
| |
| gl::Sampler *sampler = samplerIter.second; |
| if (!sampler) |
| { |
| continue; |
| } |
| |
| gl::SamplerState defaultSamplerState; |
| if (sampler->getMinFilter() != defaultSamplerState.getMinFilter()) |
| { |
| cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_MIN_FILTER, |
| sampler->getMinFilter())); |
| } |
| if (sampler->getMagFilter() != defaultSamplerState.getMagFilter()) |
| { |
| cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_MAG_FILTER, |
| sampler->getMagFilter())); |
| } |
| if (sampler->getWrapS() != defaultSamplerState.getWrapS()) |
| { |
| cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_WRAP_S, |
| sampler->getWrapS())); |
| } |
| if (sampler->getWrapR() != defaultSamplerState.getWrapR()) |
| { |
| cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_WRAP_R, |
| sampler->getWrapR())); |
| } |
| if (sampler->getWrapT() != defaultSamplerState.getWrapT()) |
| { |
| cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_WRAP_T, |
| sampler->getWrapT())); |
| } |
| if (sampler->getMinLod() != defaultSamplerState.getMinLod()) |
| { |
| cap(CaptureSamplerParameterf(replayState, true, samplerID, GL_TEXTURE_MIN_LOD, |
| sampler->getMinLod())); |
| } |
| if (sampler->getMaxLod() != defaultSamplerState.getMaxLod()) |
| { |
| cap(CaptureSamplerParameterf(replayState, true, samplerID, GL_TEXTURE_MAX_LOD, |
| sampler->getMaxLod())); |
| } |
| if (sampler->getCompareMode() != defaultSamplerState.getCompareMode()) |
| { |
| cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_COMPARE_MODE, |
| sampler->getCompareMode())); |
| } |
| if (sampler->getCompareFunc() != defaultSamplerState.getCompareFunc()) |
| { |
| cap(CaptureSamplerParameteri(replayState, true, samplerID, GL_TEXTURE_COMPARE_FUNC, |
| sampler->getCompareFunc())); |
| } |
| } |
| |
| // Capture Sync Objects |
| const gl::SyncManager &syncs = apiState.getSyncManagerForCapture(); |
| for (const auto &syncIter : gl::UnsafeResourceMapIter(syncs.getResourcesForCapture())) |
| { |
| gl::SyncID syncID = {syncIter.first}; |
| const gl::Sync *sync = syncIter.second; |
| GLsync syncObject = gl::unsafe_int_to_pointer_cast<GLsync>(syncID.value); |
| |
| if (!sync) |
| { |
| continue; |
| } |
| CallCapture fenceSync = |
| CaptureFenceSync(replayState, true, sync->getCondition(), sync->getFlags(), syncObject); |
| CaptureCustomFenceSync(fenceSync, *setupCalls); |
| CaptureFenceSyncResetCalls(context, replayState, resourceTracker, syncID, syncObject, sync); |
| resourceTracker->getStartingFenceSyncs().insert(syncID); |
| } |
| |
| // Capture EGL Sync Objects |
| const egl::SyncMap &eglSyncMap = context->getDisplay()->getSyncsForCapture(); |
| for (const auto &eglSyncIter : eglSyncMap) |
| { |
| egl::SyncID eglSyncID = {eglSyncIter.first}; |
| const egl::Sync *eglSync = eglSyncIter.second.get(); |
| EGLSync eglSyncObject = gl::unsafe_int_to_pointer_cast<EGLSync>(eglSyncID.value); |
| |
| if (!eglSync) |
| { |
| continue; |
| } |
| CallCapture createEGLSync = |
| CaptureCreateSync(nullptr, true, context->getDisplay(), eglSync->getType(), |
| eglSync->getAttributeMap(), eglSyncObject); |
| CaptureCustomCreateEGLSync("CreateEGLSyncKHR", createEGLSync, *setupCalls); |
| CaptureEGLSyncResetCalls(context, replayState, resourceTracker, eglSyncID, eglSyncObject, |
| eglSync); |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::egl_Sync) |
| .getStartingResources() |
| .insert(eglSyncID.value); |
| } |
| |
| GLint contextUnpackAlignment = context->getState().getUnpackState().alignment; |
| if (currentUnpackState.alignment != contextUnpackAlignment) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_UNPACK_ALIGNMENT, contextUnpackAlignment)); |
| replayState.getMutablePrivateStateForCapture()->setUnpackAlignment(contextUnpackAlignment); |
| } |
| } |
| |
| void CaptureMidExecutionSetup(const gl::Context *context, |
| std::vector<CallCapture> *setupCalls, |
| StateResetHelper &resetHelper, |
| std::vector<CallCapture> *shareGroupSetupCalls, |
| ResourceIDToSetupCallsMap *resourceIDToSetupCalls, |
| ResourceTracker *resourceTracker, |
| gl::State &replayState, |
| bool validationEnabled) |
| { |
| const gl::State &apiState = context->getState(); |
| |
| // Small helper function to make the code more readable. |
| auto cap = [setupCalls](CallCapture &&call) { setupCalls->emplace_back(std::move(call)); }; |
| |
| cap(egl::CaptureMakeCurrent(nullptr, true, nullptr, {0}, {0}, context->id(), EGL_TRUE)); |
| |
| // Vertex input states. Must happen after buffer data initialization. Do not capture on GLES1. |
| if (!context->isGLES1()) |
| { |
| CaptureDefaultVertexAttribs(replayState, apiState, setupCalls); |
| } |
| |
| // Capture vertex array objects |
| VertexArrayCaptureFuncs vertexArrayFuncs(context->isGLES1()); |
| |
| const gl::VertexArrayMap &vertexArrayMap = context->getVertexArraysForCapture(); |
| gl::VertexArrayID boundVertexArrayID = {0}; |
| for (const auto &vertexArrayIter : gl::UnsafeResourceMapIter(vertexArrayMap)) |
| { |
| TrackedResource &trackedVertexArrays = |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::VertexArray); |
| |
| gl::VertexArrayID vertexArrayID = {vertexArrayIter.first}; |
| |
| // Track this as a starting resource that may need to be restored |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::VertexArray) |
| .getStartingResources() |
| .insert(vertexArrayID.value); |
| |
| // Create two lists of calls for initial setup |
| ResourceCalls &vertexArrayRegenCalls = trackedVertexArrays.getResourceRegenCalls(); |
| CallVector vertexArrayGenCalls({setupCalls, &vertexArrayRegenCalls[vertexArrayID.value]}); |
| |
| if (vertexArrayID.value != 0) |
| { |
| // Gen the vertex array |
| for (std::vector<CallCapture> *calls : vertexArrayGenCalls) |
| { |
| Capture(calls, |
| vertexArrayFuncs.genVertexArrays(replayState, true, 1, &vertexArrayID)); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, calls); |
| } |
| } |
| |
| // Create two lists of calls for populating the vertex array |
| ResourceCalls &vertexArrayRestoreCalls = trackedVertexArrays.getResourceRestoreCalls(); |
| CallVector vertexArraySetupCalls( |
| {setupCalls, &vertexArrayRestoreCalls[vertexArrayID.value]}); |
| |
| if (vertexArrayIter.second) |
| { |
| const gl::VertexArray *vertexArray = vertexArrayIter.second; |
| |
| // Populate the vertex array |
| for (std::vector<CallCapture> *calls : vertexArraySetupCalls) |
| { |
| // Bind the vertexArray (if needed) and populate it |
| if (vertexArrayID != boundVertexArrayID) |
| { |
| Capture(calls, |
| vertexArrayFuncs.bindVertexArray(replayState, true, vertexArrayID)); |
| } |
| CaptureVertexArrayState(calls, context, vertexArray, &replayState); |
| } |
| boundVertexArrayID = vertexArrayID; |
| } |
| } |
| |
| // Bind the current vertex array |
| const gl::VertexArray *currentVertexArray = apiState.getVertexArray(); |
| if (currentVertexArray->id() != boundVertexArrayID) |
| { |
| cap(vertexArrayFuncs.bindVertexArray(replayState, true, currentVertexArray->id())); |
| } |
| |
| // Track the calls necessary to bind the vertex array back to initial state |
| CallResetMap &resetCalls = resetHelper.getResetCalls(); |
| Capture(&resetCalls[angle::EntryPoint::GLBindVertexArray], |
| vertexArrayFuncs.bindVertexArray(replayState, true, currentVertexArray->id())); |
| |
| // Capture indexed buffer bindings. |
| const gl::BufferVector &uniformIndexedBuffers = |
| apiState.getOffsetBindingPointerUniformBuffers(); |
| const gl::BufferVector &atomicCounterIndexedBuffers = |
| apiState.getOffsetBindingPointerAtomicCounterBuffers(); |
| const gl::BufferVector &shaderStorageIndexedBuffers = |
| apiState.getOffsetBindingPointerShaderStorageBuffers(); |
| CaptureIndexedBuffers(replayState, uniformIndexedBuffers, gl::BufferBinding::Uniform, |
| setupCalls); |
| CaptureIndexedBuffers(replayState, atomicCounterIndexedBuffers, |
| gl::BufferBinding::AtomicCounter, setupCalls); |
| CaptureIndexedBuffers(replayState, shaderStorageIndexedBuffers, |
| gl::BufferBinding::ShaderStorage, setupCalls); |
| |
| // Capture Buffer bindings. |
| const gl::BoundBufferMap &boundBuffers = apiState.getBoundBuffersForCapture(); |
| for (gl::BufferBinding binding : angle::AllEnums<gl::BufferBinding>()) |
| { |
| gl::BufferID bufferID = boundBuffers[binding].id(); |
| |
| // Filter out redundant buffer binding commands. Note that the code in the previous section |
| // only binds to ARRAY_BUFFER. Therefore we only check the array binding against the binding |
| // we set earlier. |
| const gl::Buffer *arrayBuffer = replayState.getArrayBuffer(); |
| bool isArray = binding == gl::BufferBinding::Array; |
| bool isArrayBufferChanging = isArray && arrayBuffer && arrayBuffer->id() != bufferID; |
| if (isArrayBufferChanging || (!isArray && bufferID.value != 0)) |
| { |
| cap(CaptureBindBuffer(replayState, true, binding, bufferID)); |
| replayState.setBufferBinding(context, binding, boundBuffers[binding].get()); |
| } |
| |
| // Restore all buffer bindings for Reset |
| if (bufferID.value != 0 || isArrayBufferChanging) |
| { |
| CaptureBufferBindingResetCalls(replayState, resourceTracker, binding, bufferID); |
| |
| // Track this as a starting binding |
| resetHelper.setStartingBufferBinding(binding, bufferID); |
| } |
| } |
| |
| // Set a unpack alignment of 1. Otherwise, computeRowPitch() will compute the wrong value, |
| // leading to a crash in memcpy() when capturing the texture contents. |
| gl::PixelUnpackState ¤tUnpackState = replayState.getUnpackState(); |
| if (currentUnpackState.alignment != 1) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_UNPACK_ALIGNMENT, 1)); |
| replayState.getMutablePrivateStateForCapture()->setUnpackAlignment(1); |
| } |
| |
| // Capture Texture setup and data. |
| const gl::TextureBindingMap &apiBoundTextures = apiState.getBoundTexturesForCapture(); |
| resetHelper.setResetActiveTexture(apiState.getActiveSampler()); |
| |
| // Set Texture bindings. |
| for (gl::TextureType textureType : angle::AllEnums<gl::TextureType>()) |
| { |
| const gl::TextureBindingVector &apiBindings = apiBoundTextures[textureType]; |
| const gl::TextureBindingVector &replayBindings = |
| replayState.getBoundTexturesForCapture()[textureType]; |
| ASSERT(apiBindings.size() == replayBindings.size()); |
| for (size_t bindingIndex = 0; bindingIndex < apiBindings.size(); ++bindingIndex) |
| { |
| gl::TextureID apiTextureID = apiBindings[bindingIndex].id(); |
| gl::TextureID replayTextureID = replayBindings[bindingIndex].id(); |
| |
| if (apiTextureID != replayTextureID) |
| { |
| if (replayState.getActiveSampler() != bindingIndex) |
| { |
| cap(CaptureActiveTexture(replayState, true, |
| GL_TEXTURE0 + static_cast<GLenum>(bindingIndex))); |
| replayState.getMutablePrivateStateForCapture()->setActiveSampler( |
| static_cast<unsigned int>(bindingIndex)); |
| } |
| |
| cap(CaptureBindTexture(replayState, true, textureType, apiTextureID)); |
| replayState.setSamplerTexture(context, textureType, |
| apiBindings[bindingIndex].get()); |
| } |
| |
| if (apiTextureID.value) |
| { |
| // Set this texture as active so it will be generated in Setup |
| MarkResourceIDActive(ResourceIDType::Texture, apiTextureID.value, |
| shareGroupSetupCalls, resourceIDToSetupCalls); |
| // Save currently bound textures for reset |
| resetHelper.getResetTextureBindings().emplace( |
| std::make_pair(bindingIndex, textureType), apiTextureID); |
| } |
| } |
| } |
| |
| // Capture Texture Environment |
| if (context->isGLES1()) |
| { |
| const gl::Caps &caps = context->getCaps(); |
| for (GLuint unit = 0; unit < caps.maxMultitextureUnits; ++unit) |
| { |
| CaptureTextureEnvironmentState(setupCalls, &replayState, &apiState, unit); |
| } |
| } |
| |
| // Set active Texture. |
| if (replayState.getActiveSampler() != apiState.getActiveSampler()) |
| { |
| cap(CaptureActiveTexture(replayState, true, |
| GL_TEXTURE0 + static_cast<GLenum>(apiState.getActiveSampler()))); |
| replayState.getMutablePrivateStateForCapture()->setActiveSampler( |
| apiState.getActiveSampler()); |
| } |
| |
| // Set Renderbuffer binding. |
| FramebufferCaptureFuncs framebufferFuncs(context->isGLES1()); |
| |
| const gl::RenderbufferManager &renderbuffers = apiState.getRenderbufferManagerForCapture(); |
| gl::RenderbufferID currentRenderbuffer = {0}; |
| for (const auto &renderbufIter : |
| gl::UnsafeResourceMapIter(renderbuffers.getResourcesForCapture())) |
| { |
| currentRenderbuffer = renderbufIter.second->id(); |
| } |
| |
| if (currentRenderbuffer != apiState.getRenderbufferId()) |
| { |
| cap(framebufferFuncs.bindRenderbuffer(replayState, true, GL_RENDERBUFFER, |
| apiState.getRenderbufferId())); |
| } |
| |
| // Capture Framebuffers. |
| const gl::FramebufferManager &framebuffers = apiState.getFramebufferManagerForCapture(); |
| |
| gl::FramebufferID currentDrawFramebuffer = {0}; |
| gl::FramebufferID currentReadFramebuffer = {0}; |
| |
| for (const auto &framebufferIter : |
| gl::UnsafeResourceMapIter(framebuffers.getResourcesForCapture())) |
| { |
| gl::FramebufferID id = {framebufferIter.first}; |
| const gl::Framebuffer *framebuffer = framebufferIter.second; |
| |
| // The default Framebuffer exists (by default). |
| if (framebuffer->isDefault()) |
| { |
| continue; |
| } |
| |
| // Track this as a starting resource that may need to be restored |
| TrackedResource &trackedFramebuffers = |
| resourceTracker->getTrackedResource(context->id(), ResourceIDType::Framebuffer); |
| ResourceSet &startingFramebuffers = trackedFramebuffers.getStartingResources(); |
| startingFramebuffers.insert(id.value); |
| |
| // Create two lists of calls for initial setup |
| ResourceCalls &framebufferRegenCalls = trackedFramebuffers.getResourceRegenCalls(); |
| CallVector framebufferGenCalls({setupCalls, &framebufferRegenCalls[id.value]}); |
| |
| // Gen the framebuffer |
| for (std::vector<CallCapture> *calls : framebufferGenCalls) |
| { |
| Capture(calls, framebufferFuncs.genFramebuffers(replayState, true, 1, &id)); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, calls); |
| } |
| |
| // Create two lists of calls for remaining setup calls. One for setup, and one for restore |
| // during reset. |
| ResourceCalls &framebufferRestoreCalls = trackedFramebuffers.getResourceRestoreCalls(); |
| CallVector framebufferSetupCalls({setupCalls, &framebufferRestoreCalls[id.value]}); |
| |
| for (std::vector<CallCapture> *calls : framebufferSetupCalls) |
| { |
| CaptureBindFramebufferForContext(context, calls, framebufferFuncs, replayState, |
| GL_FRAMEBUFFER, id); |
| } |
| currentDrawFramebuffer = currentReadFramebuffer = id; |
| |
| // Color Attachments. |
| for (const gl::FramebufferAttachment &colorAttachment : framebuffer->getColorAttachments()) |
| { |
| if (!colorAttachment.isAttached()) |
| { |
| continue; |
| } |
| |
| for (std::vector<CallCapture> *calls : framebufferSetupCalls) |
| { |
| CaptureFramebufferAttachment(calls, replayState, framebufferFuncs, colorAttachment, |
| shareGroupSetupCalls, resourceIDToSetupCalls); |
| } |
| } |
| |
| const gl::FramebufferAttachment *depthAttachment = framebuffer->getDepthAttachment(); |
| if (depthAttachment) |
| { |
| ASSERT(depthAttachment->getBinding() == GL_DEPTH_ATTACHMENT || |
| depthAttachment->getBinding() == GL_DEPTH_STENCIL_ATTACHMENT); |
| for (std::vector<CallCapture> *calls : framebufferSetupCalls) |
| { |
| CaptureFramebufferAttachment(calls, replayState, framebufferFuncs, *depthAttachment, |
| shareGroupSetupCalls, resourceIDToSetupCalls); |
| } |
| } |
| |
| const gl::FramebufferAttachment *stencilAttachment = framebuffer->getStencilAttachment(); |
| if (stencilAttachment) |
| { |
| ASSERT(stencilAttachment->getBinding() == GL_STENCIL_ATTACHMENT || |
| depthAttachment->getBinding() == GL_DEPTH_STENCIL_ATTACHMENT); |
| for (std::vector<CallCapture> *calls : framebufferSetupCalls) |
| { |
| CaptureFramebufferAttachment(calls, replayState, framebufferFuncs, |
| *stencilAttachment, shareGroupSetupCalls, |
| resourceIDToSetupCalls); |
| } |
| } |
| |
| gl::FramebufferState defaultFramebufferState( |
| context->getCaps(), framebuffer->getState().id(), |
| framebuffer->getState().getFramebufferSerial()); |
| const gl::DrawBuffersVector<GLenum> &defaultDrawBufferStates = |
| defaultFramebufferState.getDrawBufferStates(); |
| const gl::DrawBuffersVector<GLenum> &drawBufferStates = framebuffer->getDrawBufferStates(); |
| |
| if (drawBufferStates != defaultDrawBufferStates) |
| { |
| for (std::vector<CallCapture> *calls : framebufferSetupCalls) |
| { |
| Capture(calls, CaptureDrawBuffers(replayState, true, |
| static_cast<GLsizei>(drawBufferStates.size()), |
| drawBufferStates.data())); |
| } |
| } |
| } |
| |
| // Capture framebuffer bindings. |
| if (apiState.getDrawFramebuffer()) |
| { |
| ASSERT(apiState.getReadFramebuffer()); |
| gl::FramebufferID stateReadFramebuffer = apiState.getReadFramebuffer()->id(); |
| gl::FramebufferID stateDrawFramebuffer = apiState.getDrawFramebuffer()->id(); |
| if (stateDrawFramebuffer == stateReadFramebuffer) |
| { |
| if (currentDrawFramebuffer != stateDrawFramebuffer || |
| currentReadFramebuffer != stateReadFramebuffer) |
| { |
| CaptureBindFramebufferForContext(context, setupCalls, framebufferFuncs, replayState, |
| GL_FRAMEBUFFER, stateDrawFramebuffer); |
| currentDrawFramebuffer = currentReadFramebuffer = stateDrawFramebuffer; |
| } |
| } |
| else |
| { |
| if (currentDrawFramebuffer != stateDrawFramebuffer) |
| { |
| CaptureBindFramebufferForContext(context, setupCalls, framebufferFuncs, replayState, |
| GL_DRAW_FRAMEBUFFER, stateDrawFramebuffer); |
| currentDrawFramebuffer = stateDrawFramebuffer; |
| } |
| |
| if (currentReadFramebuffer != stateReadFramebuffer) |
| { |
| CaptureBindFramebufferForContext(context, setupCalls, framebufferFuncs, replayState, |
| GL_READ_FRAMEBUFFER, stateReadFramebuffer); |
| currentReadFramebuffer = stateReadFramebuffer; |
| } |
| } |
| } |
| |
| // Capture Program Pipelines |
| const gl::ProgramPipelineManager *programPipelineManager = |
| apiState.getProgramPipelineManagerForCapture(); |
| |
| for (const auto &ppoIterator : |
| gl::UnsafeResourceMapIter(programPipelineManager->getResourcesForCapture())) |
| { |
| gl::ProgramPipeline *pipeline = ppoIterator.second; |
| gl::ProgramPipelineID id = {ppoIterator.first}; |
| cap(CaptureGenProgramPipelines(replayState, true, 1, &id)); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls); |
| |
| // PPOs can contain graphics and compute programs, so loop through all shader types rather |
| // than just the linked ones since getLinkedShaderStages() will return either only graphics |
| // or compute stages. |
| for (gl::ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| const gl::Program *program = pipeline->getShaderProgram(shaderType); |
| if (!program) |
| { |
| continue; |
| } |
| ASSERT(program->isLinked()); |
| GLbitfield gLbitfield = GetBitfieldFromShaderType(shaderType); |
| cap(CaptureUseProgramStages(replayState, true, pipeline->id(), gLbitfield, |
| program->id())); |
| |
| // Set this program as active so it will be generated in Setup |
| // Note: We aren't filtering ProgramPipelines, so this could be setting programs |
| // active that aren't actually used. |
| MarkResourceIDActive(ResourceIDType::ShaderProgram, program->id().value, |
| shareGroupSetupCalls, resourceIDToSetupCalls); |
| } |
| |
| gl::Program *program = pipeline->getActiveShaderProgram(); |
| if (program) |
| { |
| cap(CaptureActiveShaderProgram(replayState, true, id, program->id())); |
| } |
| } |
| |
| // For now we assume the installed program executable is the same as the current program. |
| // TODO(jmadill): Handle installed program executable. http://anglebug.com/42262323 |
| if (!context->isGLES1()) |
| { |
| // If we have a program bound in the API, or if there is no program bound to the API at |
| // time of capture and we bound a program for uniform updates during MEC, we must add |
| // a set program call to replay the correct states. |
| GLuint currentProgram = 0; |
| if (apiState.getProgram()) |
| { |
| cap(CaptureUseProgram(replayState, true, apiState.getProgram()->id())); |
| CaptureUpdateCurrentProgram(setupCalls->back(), 0, setupCalls); |
| (void)replayState.setProgram(context, apiState.getProgram()); |
| |
| // Set this program as active so it will be generated in Setup |
| MarkResourceIDActive(ResourceIDType::ShaderProgram, apiState.getProgram()->id().value, |
| shareGroupSetupCalls, resourceIDToSetupCalls); |
| |
| currentProgram = apiState.getProgram()->id().value; |
| } |
| else if (replayState.getProgram()) |
| { |
| cap(CaptureUseProgram(replayState, true, {0})); |
| CaptureUpdateCurrentProgram(setupCalls->back(), 0, setupCalls); |
| (void)replayState.setProgram(context, nullptr); |
| } |
| |
| // Track the calls necessary to reset active program back to initial state |
| Capture(&resetCalls[angle::EntryPoint::GLUseProgram], |
| CaptureUseProgram(replayState, true, {currentProgram})); |
| CaptureUpdateCurrentProgram((&resetCalls[angle::EntryPoint::GLUseProgram])->back(), 0, |
| &resetCalls[angle::EntryPoint::GLUseProgram]); |
| |
| // Same for program pipelines as for programs, see comment above. |
| if (apiState.getProgramPipeline()) |
| { |
| cap(CaptureBindProgramPipeline(replayState, true, apiState.getProgramPipeline()->id())); |
| } |
| else if (replayState.getProgramPipeline()) |
| { |
| cap(CaptureBindProgramPipeline(replayState, true, {0})); |
| } |
| } |
| |
| // Capture Queries |
| const gl::QueryMap &queryMap = context->getQueriesForCapture(); |
| |
| // Create existing queries. Note that queries may be genned and not yet started. In that |
| // case the queries will exist in the query map as nullptr entries. |
| // If any queries are active between frames, we want to defer creation and do them last, |
| // otherwise you'll get GL errors about starting a query while one is already active. |
| for (gl::QueryMap::Iterator queryIter = gl::UnsafeResourceMapIter(queryMap).beginWithNull(), |
| endIter = gl::UnsafeResourceMapIter(queryMap).endWithNull(); |
| queryIter != endIter; ++queryIter) |
| { |
| ASSERT(queryIter->first); |
| gl::QueryID queryID = {queryIter->first}; |
| |
| cap(CaptureGenQueries(replayState, true, 1, &queryID)); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls); |
| |
| gl::Query *query = queryIter->second; |
| if (query) |
| { |
| gl::QueryType queryType = query->getType(); |
| |
| // Defer active queries until we've created them all |
| if (IsQueryActive(apiState, queryID)) |
| { |
| continue; |
| } |
| |
| // Begin the query to generate the object |
| cap(CaptureBeginQuery(replayState, true, queryType, queryID)); |
| |
| // End the query if it was not active |
| cap(CaptureEndQuery(replayState, true, queryType)); |
| } |
| } |
| |
| const gl::ActiveQueryMap &activeQueries = apiState.getActiveQueriesForCapture(); |
| for (const auto &activeQueryIter : activeQueries) |
| { |
| const gl::Query *activeQuery = activeQueryIter.get(); |
| if (activeQuery) |
| { |
| cap(CaptureBeginQuery(replayState, true, activeQuery->getType(), activeQuery->id())); |
| } |
| } |
| |
| // Transform Feedback |
| const gl::TransformFeedbackMap &xfbMap = context->getTransformFeedbacksForCapture(); |
| for (const auto &xfbIter : gl::UnsafeResourceMapIter(xfbMap)) |
| { |
| gl::TransformFeedbackID xfbID = {xfbIter.first}; |
| |
| // Do not capture the default XFB object. |
| if (xfbID.value == 0) |
| { |
| continue; |
| } |
| |
| cap(CaptureGenTransformFeedbacks(replayState, true, 1, &xfbID)); |
| MaybeCaptureUpdateResourceIDs(context, resourceTracker, setupCalls); |
| |
| gl::TransformFeedback *xfb = xfbIter.second; |
| if (!xfb) |
| { |
| // The object was never created |
| continue; |
| } |
| |
| // Bind XFB to create the object |
| cap(CaptureBindTransformFeedback(replayState, true, GL_TRANSFORM_FEEDBACK, xfbID)); |
| |
| // Bind the buffers associated with this XFB object |
| for (size_t i = 0; i < xfb->getIndexedBufferCount(); ++i) |
| { |
| const gl::OffsetBindingPointer<gl::Buffer> &xfbBuffer = xfb->getIndexedBuffer(i); |
| |
| // Note: Buffers bound with BindBufferBase can be used with BindBuffer |
| cap(CaptureBindBufferRange(replayState, true, gl::BufferBinding::TransformFeedback, 0, |
| xfbBuffer.id(), xfbBuffer.getOffset(), xfbBuffer.getSize())); |
| } |
| |
| if (xfb->isActive() || xfb->isPaused()) |
| { |
| // We don't support active XFB in MEC yet |
| UNIMPLEMENTED(); |
| } |
| } |
| |
| // Bind the current XFB buffer after populating XFB objects |
| gl::TransformFeedback *currentXFB = apiState.getCurrentTransformFeedback(); |
| if (currentXFB) |
| { |
| cap(CaptureBindTransformFeedback(replayState, true, GL_TRANSFORM_FEEDBACK, |
| currentXFB->id())); |
| } |
| |
| // Bind samplers |
| const gl::SamplerBindingVector &samplerBindings = apiState.getSamplers(); |
| for (GLuint bindingIndex = 0; bindingIndex < static_cast<GLuint>(samplerBindings.size()); |
| ++bindingIndex) |
| { |
| gl::SamplerID samplerID = samplerBindings[bindingIndex].id(); |
| if (samplerID.value != 0) |
| { |
| cap(CaptureBindSampler(replayState, true, bindingIndex, samplerID)); |
| } |
| } |
| |
| // Capture Image Texture bindings |
| const std::vector<gl::ImageUnit> &imageUnits = apiState.getImageUnits(); |
| for (GLuint bindingIndex = 0; bindingIndex < static_cast<GLuint>(imageUnits.size()); |
| ++bindingIndex) |
| { |
| const gl::ImageUnit &imageUnit = imageUnits[bindingIndex]; |
| |
| if (imageUnit.texture == 0) |
| { |
| continue; |
| } |
| |
| cap(CaptureBindImageTexture(replayState, true, bindingIndex, imageUnit.texture.id(), |
| imageUnit.level, imageUnit.layered, imageUnit.layer, |
| imageUnit.access, imageUnit.format)); |
| } |
| |
| // Capture GL Context states. |
| auto capCap = [cap, &replayState](GLenum capEnum, bool capValue) { |
| if (capValue) |
| { |
| cap(CaptureEnable(replayState, true, capEnum)); |
| } |
| else |
| { |
| cap(CaptureDisable(replayState, true, capEnum)); |
| } |
| }; |
| |
| // Capture GLES1 context states. |
| if (context->isGLES1()) |
| { |
| const bool currentTextureState = apiState.getEnableFeature(GL_TEXTURE_2D); |
| const bool defaultTextureState = replayState.getEnableFeature(GL_TEXTURE_2D); |
| if (currentTextureState != defaultTextureState) |
| { |
| capCap(GL_TEXTURE_2D, currentTextureState); |
| } |
| |
| cap(CaptureMatrixMode(replayState, true, gl::MatrixType::Projection)); |
| for (angle::Mat4 projectionMatrix : |
| apiState.gles1().getMatrixStack(gl::MatrixType::Projection)) |
| { |
| cap(CapturePushMatrix(replayState, true)); |
| cap(CaptureLoadMatrixf(replayState, true, projectionMatrix.elements().data())); |
| } |
| |
| cap(CaptureMatrixMode(replayState, true, gl::MatrixType::Modelview)); |
| for (angle::Mat4 modelViewMatrix : |
| apiState.gles1().getMatrixStack(gl::MatrixType::Modelview)) |
| { |
| cap(CapturePushMatrix(replayState, true)); |
| cap(CaptureLoadMatrixf(replayState, true, modelViewMatrix.elements().data())); |
| } |
| |
| gl::MatrixType currentMatrixMode = apiState.gles1().getMatrixMode(); |
| if (currentMatrixMode != gl::MatrixType::Modelview) |
| { |
| cap(CaptureMatrixMode(replayState, true, currentMatrixMode)); |
| } |
| |
| // Alpha Test state |
| const bool currentAlphaTestState = apiState.getEnableFeature(GL_ALPHA_TEST); |
| const bool defaultAlphaTestState = replayState.getEnableFeature(GL_ALPHA_TEST); |
| |
| if (currentAlphaTestState != defaultAlphaTestState) |
| { |
| capCap(GL_ALPHA_TEST, currentAlphaTestState); |
| } |
| |
| const gl::AlphaTestParameters currentAlphaTestParameters = |
| apiState.gles1().getAlphaTestParameters(); |
| const gl::AlphaTestParameters defaultAlphaTestParameters = |
| replayState.gles1().getAlphaTestParameters(); |
| |
| if (currentAlphaTestParameters != defaultAlphaTestParameters) |
| { |
| cap(CaptureAlphaFunc(replayState, true, currentAlphaTestParameters.func, |
| currentAlphaTestParameters.ref)); |
| } |
| } |
| |
| // Rasterizer state. Missing ES 3.x features. |
| const gl::RasterizerState &defaultRasterState = replayState.getRasterizerState(); |
| const gl::RasterizerState ¤tRasterState = apiState.getRasterizerState(); |
| if (currentRasterState.cullFace != defaultRasterState.cullFace) |
| { |
| capCap(GL_CULL_FACE, currentRasterState.cullFace); |
| } |
| |
| if (currentRasterState.cullMode != defaultRasterState.cullMode) |
| { |
| cap(CaptureCullFace(replayState, true, currentRasterState.cullMode)); |
| } |
| |
| if (currentRasterState.frontFace != defaultRasterState.frontFace) |
| { |
| cap(CaptureFrontFace(replayState, true, currentRasterState.frontFace)); |
| } |
| |
| if (currentRasterState.polygonMode != defaultRasterState.polygonMode) |
| { |
| if (context->getExtensions().polygonModeNV) |
| { |
| cap(CapturePolygonModeNV(replayState, true, GL_FRONT_AND_BACK, |
| currentRasterState.polygonMode)); |
| } |
| else if (context->getExtensions().polygonModeANGLE) |
| { |
| cap(CapturePolygonModeANGLE(replayState, true, GL_FRONT_AND_BACK, |
| currentRasterState.polygonMode)); |
| } |
| else |
| { |
| UNREACHABLE(); |
| } |
| } |
| |
| if (currentRasterState.polygonOffsetPoint != defaultRasterState.polygonOffsetPoint) |
| { |
| capCap(GL_POLYGON_OFFSET_POINT_NV, currentRasterState.polygonOffsetPoint); |
| } |
| |
| if (currentRasterState.polygonOffsetLine != defaultRasterState.polygonOffsetLine) |
| { |
| capCap(GL_POLYGON_OFFSET_LINE_NV, currentRasterState.polygonOffsetLine); |
| } |
| |
| if (currentRasterState.polygonOffsetFill != defaultRasterState.polygonOffsetFill) |
| { |
| capCap(GL_POLYGON_OFFSET_FILL, currentRasterState.polygonOffsetFill); |
| } |
| |
| if (currentRasterState.polygonOffsetFactor != defaultRasterState.polygonOffsetFactor || |
| currentRasterState.polygonOffsetUnits != defaultRasterState.polygonOffsetUnits || |
| currentRasterState.polygonOffsetClamp != defaultRasterState.polygonOffsetClamp) |
| { |
| if (currentRasterState.polygonOffsetClamp == 0.0f) |
| { |
| cap(CapturePolygonOffset(replayState, true, currentRasterState.polygonOffsetFactor, |
| currentRasterState.polygonOffsetUnits)); |
| } |
| else |
| { |
| cap(CapturePolygonOffsetClampEXT( |
| replayState, true, currentRasterState.polygonOffsetFactor, |
| currentRasterState.polygonOffsetUnits, currentRasterState.polygonOffsetClamp)); |
| } |
| } |
| |
| if (currentRasterState.depthClamp != defaultRasterState.depthClamp) |
| { |
| capCap(GL_DEPTH_CLAMP_EXT, currentRasterState.depthClamp); |
| } |
| |
| // pointDrawMode/multiSample are only used in the D3D back-end right now. |
| |
| if (currentRasterState.rasterizerDiscard != defaultRasterState.rasterizerDiscard) |
| { |
| capCap(GL_RASTERIZER_DISCARD, currentRasterState.rasterizerDiscard); |
| } |
| |
| if (currentRasterState.dither != defaultRasterState.dither) |
| { |
| capCap(GL_DITHER, currentRasterState.dither); |
| } |
| |
| // Depth/stencil state. |
| const gl::DepthStencilState &defaultDSState = replayState.getDepthStencilState(); |
| const gl::DepthStencilState ¤tDSState = apiState.getDepthStencilState(); |
| if (defaultDSState.depthFunc != currentDSState.depthFunc) |
| { |
| cap(CaptureDepthFunc(replayState, true, currentDSState.depthFunc)); |
| } |
| |
| if (defaultDSState.depthMask != currentDSState.depthMask) |
| { |
| cap(CaptureDepthMask(replayState, true, gl::ConvertToGLBoolean(currentDSState.depthMask))); |
| } |
| |
| if (defaultDSState.depthTest != currentDSState.depthTest) |
| { |
| capCap(GL_DEPTH_TEST, currentDSState.depthTest); |
| } |
| |
| if (defaultDSState.stencilTest != currentDSState.stencilTest) |
| { |
| capCap(GL_STENCIL_TEST, currentDSState.stencilTest); |
| } |
| |
| if (currentDSState.stencilFunc == currentDSState.stencilBackFunc && |
| currentDSState.stencilMask == currentDSState.stencilBackMask) |
| { |
| // Front and back are equal |
| if (defaultDSState.stencilFunc != currentDSState.stencilFunc || |
| defaultDSState.stencilMask != currentDSState.stencilMask || |
| apiState.getStencilRef() != 0) |
| { |
| cap(CaptureStencilFunc(replayState, true, currentDSState.stencilFunc, |
| apiState.getStencilRef(), currentDSState.stencilMask)); |
| } |
| } |
| else |
| { |
| // Front and back are separate |
| if (defaultDSState.stencilFunc != currentDSState.stencilFunc || |
| defaultDSState.stencilMask != currentDSState.stencilMask || |
| apiState.getStencilRef() != 0) |
| { |
| cap(CaptureStencilFuncSeparate(replayState, true, GL_FRONT, currentDSState.stencilFunc, |
| apiState.getStencilRef(), currentDSState.stencilMask)); |
| } |
| |
| if (defaultDSState.stencilBackFunc != currentDSState.stencilBackFunc || |
| defaultDSState.stencilBackMask != currentDSState.stencilBackMask || |
| apiState.getStencilBackRef() != 0) |
| { |
| cap(CaptureStencilFuncSeparate( |
| replayState, true, GL_BACK, currentDSState.stencilBackFunc, |
| apiState.getStencilBackRef(), currentDSState.stencilBackMask)); |
| } |
| } |
| |
| if (currentDSState.stencilFail == currentDSState.stencilBackFail && |
| currentDSState.stencilPassDepthFail == currentDSState.stencilBackPassDepthFail && |
| currentDSState.stencilPassDepthPass == currentDSState.stencilBackPassDepthPass) |
| { |
| // Front and back are equal |
| if (defaultDSState.stencilFail != currentDSState.stencilFail || |
| defaultDSState.stencilPassDepthFail != currentDSState.stencilPassDepthFail || |
| defaultDSState.stencilPassDepthPass != currentDSState.stencilPassDepthPass) |
| { |
| cap(CaptureStencilOp(replayState, true, currentDSState.stencilFail, |
| currentDSState.stencilPassDepthFail, |
| currentDSState.stencilPassDepthPass)); |
| } |
| } |
| else |
| { |
| // Front and back are separate |
| if (defaultDSState.stencilFail != currentDSState.stencilFail || |
| defaultDSState.stencilPassDepthFail != currentDSState.stencilPassDepthFail || |
| defaultDSState.stencilPassDepthPass != currentDSState.stencilPassDepthPass) |
| { |
| cap(CaptureStencilOpSeparate(replayState, true, GL_FRONT, currentDSState.stencilFail, |
| currentDSState.stencilPassDepthFail, |
| currentDSState.stencilPassDepthPass)); |
| } |
| |
| if (defaultDSState.stencilBackFail != currentDSState.stencilBackFail || |
| defaultDSState.stencilBackPassDepthFail != currentDSState.stencilBackPassDepthFail || |
| defaultDSState.stencilBackPassDepthPass != currentDSState.stencilBackPassDepthPass) |
| { |
| cap(CaptureStencilOpSeparate(replayState, true, GL_BACK, currentDSState.stencilBackFail, |
| currentDSState.stencilBackPassDepthFail, |
| currentDSState.stencilBackPassDepthPass)); |
| } |
| } |
| |
| if (currentDSState.stencilWritemask == currentDSState.stencilBackWritemask) |
| { |
| // Front and back are equal |
| if (defaultDSState.stencilWritemask != currentDSState.stencilWritemask) |
| { |
| cap(CaptureStencilMask(replayState, true, currentDSState.stencilWritemask)); |
| } |
| } |
| else |
| { |
| // Front and back are separate |
| if (defaultDSState.stencilWritemask != currentDSState.stencilWritemask) |
| { |
| cap(CaptureStencilMaskSeparate(replayState, true, GL_FRONT, |
| currentDSState.stencilWritemask)); |
| } |
| |
| if (defaultDSState.stencilBackWritemask != currentDSState.stencilBackWritemask) |
| { |
| cap(CaptureStencilMaskSeparate(replayState, true, GL_BACK, |
| currentDSState.stencilBackWritemask)); |
| } |
| } |
| |
| // Blend state. |
| const gl::BlendState &defaultBlendState = replayState.getBlendState(); |
| const gl::BlendState ¤tBlendState = apiState.getBlendState(); |
| |
| if (currentBlendState.blend != defaultBlendState.blend) |
| { |
| capCap(GL_BLEND, currentBlendState.blend); |
| } |
| |
| if (currentBlendState.sourceBlendRGB != defaultBlendState.sourceBlendRGB || |
| currentBlendState.destBlendRGB != defaultBlendState.destBlendRGB || |
| currentBlendState.sourceBlendAlpha != defaultBlendState.sourceBlendAlpha || |
| currentBlendState.destBlendAlpha != defaultBlendState.destBlendAlpha) |
| { |
| if (context->isGLES1()) |
| { |
| // Even though their states are tracked independently, in GLES1 blendAlpha |
| // and blendRGB cannot be set separately and are always equal |
| cap(CaptureBlendFunc(replayState, true, currentBlendState.sourceBlendRGB, |
| currentBlendState.destBlendRGB)); |
| Capture(&resetCalls[angle::EntryPoint::GLBlendFunc], |
| CaptureBlendFunc(replayState, true, currentBlendState.sourceBlendRGB, |
| currentBlendState.destBlendRGB)); |
| } |
| else |
| { |
| // Always use BlendFuncSeparate for non-GLES1 as it covers all cases |
| cap(CaptureBlendFuncSeparate( |
| replayState, true, currentBlendState.sourceBlendRGB, currentBlendState.destBlendRGB, |
| currentBlendState.sourceBlendAlpha, currentBlendState.destBlendAlpha)); |
| Capture(&resetCalls[angle::EntryPoint::GLBlendFuncSeparate], |
| CaptureBlendFuncSeparate(replayState, true, currentBlendState.sourceBlendRGB, |
| currentBlendState.destBlendRGB, |
| currentBlendState.sourceBlendAlpha, |
| currentBlendState.destBlendAlpha)); |
| } |
| } |
| |
| if (currentBlendState.blendEquationRGB != defaultBlendState.blendEquationRGB || |
| currentBlendState.blendEquationAlpha != defaultBlendState.blendEquationAlpha) |
| { |
| // Similarly to BlendFunc, using BlendEquation in some cases complicates Reset. |
| cap(CaptureBlendEquationSeparate(replayState, true, currentBlendState.blendEquationRGB, |
| currentBlendState.blendEquationAlpha)); |
| Capture(&resetCalls[angle::EntryPoint::GLBlendEquationSeparate], |
| CaptureBlendEquationSeparate(replayState, true, currentBlendState.blendEquationRGB, |
| currentBlendState.blendEquationAlpha)); |
| } |
| |
| if (currentBlendState.colorMaskRed != defaultBlendState.colorMaskRed || |
| currentBlendState.colorMaskGreen != defaultBlendState.colorMaskGreen || |
| currentBlendState.colorMaskBlue != defaultBlendState.colorMaskBlue || |
| currentBlendState.colorMaskAlpha != defaultBlendState.colorMaskAlpha) |
| { |
| cap(CaptureColorMask(replayState, true, |
| gl::ConvertToGLBoolean(currentBlendState.colorMaskRed), |
| gl::ConvertToGLBoolean(currentBlendState.colorMaskGreen), |
| gl::ConvertToGLBoolean(currentBlendState.colorMaskBlue), |
| gl::ConvertToGLBoolean(currentBlendState.colorMaskAlpha))); |
| Capture(&resetCalls[angle::EntryPoint::GLColorMask], |
| CaptureColorMask(replayState, true, |
| gl::ConvertToGLBoolean(currentBlendState.colorMaskRed), |
| gl::ConvertToGLBoolean(currentBlendState.colorMaskGreen), |
| gl::ConvertToGLBoolean(currentBlendState.colorMaskBlue), |
| gl::ConvertToGLBoolean(currentBlendState.colorMaskAlpha))); |
| } |
| |
| const gl::ColorF ¤tBlendColor = apiState.getBlendColor(); |
| if (currentBlendColor != gl::ColorF()) |
| { |
| cap(CaptureBlendColor(replayState, true, currentBlendColor.red, currentBlendColor.green, |
| currentBlendColor.blue, currentBlendColor.alpha)); |
| Capture(&resetCalls[angle::EntryPoint::GLBlendColor], |
| CaptureBlendColor(replayState, true, currentBlendColor.red, currentBlendColor.green, |
| currentBlendColor.blue, currentBlendColor.alpha)); |
| } |
| |
| // Pixel storage states. |
| gl::PixelPackState ¤tPackState = replayState.getPackState(); |
| if (currentPackState.alignment != apiState.getPackAlignment()) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_PACK_ALIGNMENT, apiState.getPackAlignment())); |
| currentPackState.alignment = apiState.getPackAlignment(); |
| } |
| |
| if (currentPackState.rowLength != apiState.getPackRowLength()) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_PACK_ROW_LENGTH, apiState.getPackRowLength())); |
| currentPackState.rowLength = apiState.getPackRowLength(); |
| } |
| |
| if (currentPackState.skipRows != apiState.getPackSkipRows()) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_PACK_SKIP_ROWS, apiState.getPackSkipRows())); |
| currentPackState.skipRows = apiState.getPackSkipRows(); |
| } |
| |
| if (currentPackState.skipPixels != apiState.getPackSkipPixels()) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_PACK_SKIP_PIXELS, |
| apiState.getPackSkipPixels())); |
| currentPackState.skipPixels = apiState.getPackSkipPixels(); |
| } |
| |
| // We set unpack alignment above, no need to change it here |
| ASSERT(currentUnpackState.alignment == 1); |
| if (currentUnpackState.rowLength != apiState.getUnpackRowLength()) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_UNPACK_ROW_LENGTH, |
| apiState.getUnpackRowLength())); |
| currentUnpackState.rowLength = apiState.getUnpackRowLength(); |
| } |
| |
| if (currentUnpackState.skipRows != apiState.getUnpackSkipRows()) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_UNPACK_SKIP_ROWS, |
| apiState.getUnpackSkipRows())); |
| currentUnpackState.skipRows = apiState.getUnpackSkipRows(); |
| } |
| |
| if (currentUnpackState.skipPixels != apiState.getUnpackSkipPixels()) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_UNPACK_SKIP_PIXELS, |
| apiState.getUnpackSkipPixels())); |
| currentUnpackState.skipPixels = apiState.getUnpackSkipPixels(); |
| } |
| |
| if (currentUnpackState.imageHeight != apiState.getUnpackImageHeight()) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_UNPACK_IMAGE_HEIGHT, |
| apiState.getUnpackImageHeight())); |
| currentUnpackState.imageHeight = apiState.getUnpackImageHeight(); |
| } |
| |
| if (currentUnpackState.skipImages != apiState.getUnpackSkipImages()) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_UNPACK_SKIP_IMAGES, |
| apiState.getUnpackSkipImages())); |
| currentUnpackState.skipImages = apiState.getUnpackSkipImages(); |
| } |
| |
| // Clear state. Missing ES 3.x features. |
| // TODO(http://anglebug.com/42262323): Complete state capture. |
| const gl::ColorF ¤tClearColor = apiState.getColorClearValue(); |
| if (currentClearColor != gl::ColorF()) |
| { |
| cap(CaptureClearColor(replayState, true, currentClearColor.red, currentClearColor.green, |
| currentClearColor.blue, currentClearColor.alpha)); |
| } |
| |
| if (apiState.getDepthClearValue() != 1.0f) |
| { |
| cap(CaptureClearDepthf(replayState, true, apiState.getDepthClearValue())); |
| } |
| |
| if (apiState.getStencilClearValue() != 0) |
| { |
| cap(CaptureClearStencil(replayState, true, apiState.getStencilClearValue())); |
| } |
| |
| // Viewport / scissor / clipping planes. |
| const gl::Rectangle ¤tViewport = apiState.getViewport(); |
| if (currentViewport != gl::Rectangle()) |
| { |
| cap(CaptureViewport(replayState, true, currentViewport.x, currentViewport.y, |
| currentViewport.width, currentViewport.height)); |
| } |
| |
| if (apiState.getNearPlane() != 0.0f || apiState.getFarPlane() != 1.0f) |
| { |
| cap(CaptureDepthRangef(replayState, true, apiState.getNearPlane(), apiState.getFarPlane())); |
| } |
| |
| if (apiState.getClipOrigin() != gl::ClipOrigin::LowerLeft || |
| apiState.getClipDepthMode() != gl::ClipDepthMode::NegativeOneToOne) |
| { |
| cap(CaptureClipControlEXT(replayState, true, apiState.getClipOrigin(), |
| apiState.getClipDepthMode())); |
| } |
| |
| if (apiState.isScissorTestEnabled()) |
| { |
| capCap(GL_SCISSOR_TEST, apiState.isScissorTestEnabled()); |
| } |
| |
| const gl::Rectangle ¤tScissor = apiState.getScissor(); |
| if (currentScissor != gl::Rectangle()) |
| { |
| cap(CaptureScissor(replayState, true, currentScissor.x, currentScissor.y, |
| currentScissor.width, currentScissor.height)); |
| } |
| |
| // Allow the replayState object to be destroyed conveniently. |
| replayState.setBufferBinding(context, gl::BufferBinding::Array, nullptr); |
| |
| // Clean up the replay state. |
| replayState.reset(context); |
| |
| GLint contextUnpackAlignment = context->getState().getUnpackState().alignment; |
| if (currentUnpackState.alignment != contextUnpackAlignment) |
| { |
| cap(CapturePixelStorei(replayState, true, GL_UNPACK_ALIGNMENT, contextUnpackAlignment)); |
| replayState.getMutablePrivateStateForCapture()->setUnpackAlignment(contextUnpackAlignment); |
| } |
| |
| if (validationEnabled) |
| { |
| CaptureValidateSerializedState(context, setupCalls); |
| } |
| } |
| |
| bool SkipCall(EntryPoint entryPoint) |
| { |
| switch (entryPoint) |
| { |
| case EntryPoint::GLDebugMessageCallback: |
| case EntryPoint::GLDebugMessageCallbackKHR: |
| case EntryPoint::GLDebugMessageControl: |
| case EntryPoint::GLDebugMessageControlKHR: |
| case EntryPoint::GLDebugMessageInsert: |
| case EntryPoint::GLDebugMessageInsertKHR: |
| case EntryPoint::GLGetDebugMessageLog: |
| case EntryPoint::GLGetDebugMessageLogKHR: |
| case EntryPoint::GLGetObjectLabel: |
| case EntryPoint::GLGetObjectLabelEXT: |
| case EntryPoint::GLGetObjectLabelKHR: |
| case EntryPoint::GLGetObjectPtrLabelKHR: |
| case EntryPoint::GLGetPointervKHR: |
| case EntryPoint::GLInsertEventMarkerEXT: |
| case EntryPoint::GLLabelObjectEXT: |
| case EntryPoint::GLObjectLabel: |
| case EntryPoint::GLObjectLabelKHR: |
| case EntryPoint::GLObjectPtrLabelKHR: |
| case EntryPoint::GLPopDebugGroupKHR: |
| case EntryPoint::GLPopGroupMarkerEXT: |
| case EntryPoint::GLPushDebugGroupKHR: |
| case EntryPoint::GLPushGroupMarkerEXT: |
| // Purposefully skip entry points from: |
| // - KHR_debug |
| // - EXT_debug_label |
| // - EXT_debug_marker |
| // There is no need to capture these for replaying a trace in our harness |
| return true; |
| |
| case EntryPoint::GLGetActiveUniform: |
| case EntryPoint::GLGetActiveUniformsiv: |
| // Skip these calls because: |
| // - We don't use the return values. |
| // - Active uniform counts can vary between platforms due to cross stage optimizations |
| // and asking about uniforms above GL_ACTIVE_UNIFORMS triggers errors. |
| return true; |
| |
| case EntryPoint::GLGetActiveAttrib: |
| // Skip these calls because: |
| // - We don't use the return values. |
| // - Same as uniforms, the value can vary, asking above GL_ACTIVE_ATTRIBUTES is an error |
| return true; |
| |
| case EntryPoint::GLGetActiveUniformBlockiv: |
| case EntryPoint::GLGetActiveUniformBlockName: |
| // Skip these calls because: |
| // - We don't use the return values. |
| // - It reduces the number of references to the uniform block index map. |
| return true; |
| |
| case EntryPoint::EGLChooseConfig: |
| case EntryPoint::EGLGetProcAddress: |
| case EntryPoint::EGLGetConfigAttrib: |
| case EntryPoint::EGLGetConfigs: |
| case EntryPoint::EGLGetSyncAttrib: |
| case EntryPoint::EGLGetSyncAttribKHR: |
| case EntryPoint::EGLQueryContext: |
| case EntryPoint::EGLQuerySurface: |
| // Skip these calls because: |
| // - We don't use the return values. |
| // - Some EGL types and pointer parameters aren't yet implemented in EGL capture. |
| return true; |
| |
| case EntryPoint::EGLPrepareSwapBuffersANGLE: |
| // Skip this call because: |
| // - eglPrepareSwapBuffersANGLE is automatically called by eglSwapBuffers |
| return true; |
| |
| case EntryPoint::EGLSwapBuffers: |
| // Skip these calls because: |
| // - Swap is handled specially by the trace harness. |
| return true; |
| |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| PageRange::PageRange(size_t start, size_t end) : start(start), end(end) {} |
| PageRange::~PageRange() = default; |
| |
| AddressRange::AddressRange() {} |
| AddressRange::AddressRange(uintptr_t start, size_t size) : start(start), size(size) {} |
| AddressRange::~AddressRange() = default; |
| |
| uintptr_t AddressRange::end() |
| { |
| return start + size; |
| } |
| |
| bool IsTrackedPerContext(ResourceIDType type) |
| { |
| // This helper function informs us which context-local (not shared) objects are tracked |
| // with per-context object maps. |
| if (IsSharedObjectResource(type)) |
| { |
| return false; |
| } |
| |
| // TODO (https://issuetracker.google.com/169868803): Remaining context-local resources (VAOs, |
| // PPOs, Transform Feedback Objects, and Query Objects) must also tracked per-context. Once all |
| // per-context resource handling is correctly updated then this function can be replaced with |
| // !IsSharedObjectResource(). |
| switch (type) |
| { |
| case ResourceIDType::Framebuffer: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| CoherentBuffer::CoherentBuffer(uintptr_t start, |
| size_t size, |
| size_t pageSize, |
| bool isShadowMemoryEnabled) |
| : mPageSize(pageSize), |
| mShadowMemoryEnabled(isShadowMemoryEnabled), |
| mBufferStart(start), |
| mShadowMemory(nullptr), |
| mShadowDirty(false) |
| { |
| if (mShadowMemoryEnabled) |
| { |
| // Shadow memory needs to have at least the size of one page, to not protect outside. |
| size_t numShadowPages = (size / pageSize) + 1; |
| mShadowMemory = AlignedAlloc(numShadowPages * pageSize, pageSize); |
| ASSERT(mShadowMemory != nullptr); |
| start = reinterpret_cast<uintptr_t>(mShadowMemory); |
| } |
| |
| mRange.start = start; |
| mRange.size = size; |
| mProtectionRange.start = rx::roundDownPow2(start, pageSize); |
| |
| uintptr_t protectionEnd = rx::roundUpPow2(start + size, pageSize); |
| |
| mProtectionRange.size = protectionEnd - mProtectionRange.start; |
| mPageCount = mProtectionRange.size / pageSize; |
| |
| mProtectionStartPage = mProtectionRange.start / mPageSize; |
| mProtectionEndPage = mProtectionStartPage + mPageCount; |
| |
| mDirtyPages = std::vector<bool>(mPageCount); |
| mDirtyPages.assign(mPageCount, true); |
| } |
| |
| std::vector<PageRange> CoherentBuffer::getDirtyPageRanges() |
| { |
| std::vector<PageRange> dirtyPageRanges; |
| |
| bool inDirty = false; |
| for (size_t i = 0; i < mPageCount; i++) |
| { |
| if (!inDirty && mDirtyPages[i]) |
| { |
| // Found start of a dirty range |
| inDirty = true; |
| // Set end page as last page initially |
| dirtyPageRanges.push_back(PageRange(i, mPageCount)); |
| } |
| else if (inDirty && !mDirtyPages[i]) |
| { |
| // Found end of a dirty range |
| inDirty = false; |
| dirtyPageRanges.back().end = i; |
| } |
| } |
| |
| return dirtyPageRanges; |
| } |
| |
| AddressRange CoherentBuffer::getRange() |
| { |
| return mRange; |
| } |
| |
| AddressRange CoherentBuffer::getDirtyAddressRange(const PageRange &dirtyPageRange) |
| { |
| AddressRange range; |
| |
| if (dirtyPageRange.start == 0) |
| { |
| // First page, use non page aligned buffer start. |
| range.start = mRange.start; |
| } |
| else |
| { |
| range.start = mProtectionRange.start + dirtyPageRange.start * mPageSize; |
| } |
| |
| if (dirtyPageRange.end == mPageCount) |
| { |
| // Last page, use non page aligned buffer end. |
| range.size = mRange.end() - range.start; |
| } |
| else |
| { |
| range.size = (dirtyPageRange.end - dirtyPageRange.start) * mPageSize; |
| // This occurs when a buffer occupies 2 pages, but is smaller than a page. |
| if (mRange.end() < range.end()) |
| { |
| range.size = mRange.end() - range.start; |
| } |
| } |
| |
| // Dirty range must be in buffer |
| ASSERT(range.start >= mRange.start && mRange.end() >= range.end()); |
| |
| return range; |
| } |
| |
| CoherentBuffer::~CoherentBuffer() |
| { |
| if (mShadowMemory != nullptr) |
| { |
| AlignedFree(mShadowMemory); |
| } |
| } |
| |
| bool CoherentBuffer::isDirty() |
| { |
| return std::find(mDirtyPages.begin(), mDirtyPages.end(), true) != mDirtyPages.end(); |
| } |
| |
| bool CoherentBuffer::contains(size_t page, size_t *relativePage) |
| { |
| bool isInProtectionRange = page >= mProtectionStartPage && page < mProtectionEndPage; |
| if (!isInProtectionRange) |
| { |
| return false; |
| } |
| |
| *relativePage = page - mProtectionStartPage; |
| |
| ASSERT(page >= mProtectionStartPage); |
| |
| return true; |
| } |
| |
| void CoherentBuffer::protectPageRange(const PageRange &pageRange) |
| { |
| for (size_t i = pageRange.start; i < pageRange.end; i++) |
| { |
| setDirty(i, false); |
| } |
| } |
| |
| void CoherentBuffer::protectAll() |
| { |
| for (size_t i = 0; i < mPageCount; i++) |
| { |
| setDirty(i, false); |
| } |
| } |
| |
| void CoherentBuffer::updateBufferMemory() |
| { |
| memcpy(reinterpret_cast<void *>(mBufferStart), reinterpret_cast<void *>(mRange.start), |
| mRange.size); |
| } |
| |
| void CoherentBuffer::updateShadowMemory() |
| { |
| memcpy(reinterpret_cast<void *>(mRange.start), reinterpret_cast<void *>(mBufferStart), |
| mRange.size); |
| mShadowDirty = false; |
| } |
| |
| void CoherentBuffer::setDirty(size_t relativePage, bool dirty) |
| { |
| if (mDirtyPages[relativePage] == dirty) |
| { |
| // The page is already set. |
| // This can happen when tracked buffers overlap in a page. |
| return; |
| } |
| |
| uintptr_t pageStart = mProtectionRange.start + relativePage * mPageSize; |
| |
| // Last page end must be the same as protection end |
| if (relativePage + 1 == mPageCount) |
| { |
| ASSERT(mProtectionRange.end() == pageStart + mPageSize); |
| } |
| |
| bool ret; |
| if (dirty) |
| { |
| ret = UnprotectMemory(pageStart, mPageSize); |
| } |
| else |
| { |
| ret = ProtectMemory(pageStart, mPageSize); |
| } |
| |
| if (!ret) |
| { |
| ERR() << "Could not set protection for buffer page " << relativePage << " at " |
| << reinterpret_cast<void *>(pageStart) << " with size " << mPageSize; |
| } |
| mDirtyPages[relativePage] = dirty; |
| } |
| |
| void CoherentBuffer::removeProtection(PageSharingType sharingType) |
| { |
| uintptr_t start = mProtectionRange.start; |
| size_t size = mProtectionRange.size; |
| |
| switch (sharingType) |
| { |
| case PageSharingType::FirstShared: |
| case PageSharingType::FirstAndLastShared: |
| start += mPageSize; |
| break; |
| default: |
| break; |
| } |
| |
| switch (sharingType) |
| { |
| case PageSharingType::FirstShared: |
| case PageSharingType::LastShared: |
| size -= mPageSize; |
| break; |
| case PageSharingType::FirstAndLastShared: |
| size -= (2 * mPageSize); |
| break; |
| default: |
| break; |
| } |
| |
| if (size == 0) |
| { |
| return; |
| } |
| |
| if (!UnprotectMemory(start, size)) |
| { |
| ERR() << "Could not remove protection for buffer at " << start << " with size " << size; |
| } |
| } |
| |
| bool CoherentBufferTracker::canProtectDirectly(gl::Context *context) |
| { |
| gl::BufferID bufferId = context->createBuffer(); |
| |
| gl::BufferBinding targetPacked = gl::BufferBinding::Array; |
| context->bindBuffer(targetPacked, bufferId); |
| |
| // Allocate 2 pages so we will always have a full aligned page to protect |
| GLsizei size = static_cast<GLsizei>(mPageSize * 2); |
| |
| context->bufferStorage(targetPacked, size, nullptr, |
| GL_DYNAMIC_STORAGE_BIT_EXT | GL_MAP_WRITE_BIT | |
| GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT); |
| |
| gl::Buffer *buffer = context->getBuffer(bufferId); |
| |
| angle::Result result = buffer->mapRange( |
| context, 0, size, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT); |
| if (result != angle::Result::Continue) |
| { |
| ERR() << "Failed to mapRange of buffer."; |
| } |
| |
| void *map = buffer->getMapPointer(); |
| if (map == nullptr) |
| { |
| ERR() << "Failed to getMapPointer of buffer."; |
| } |
| |
| // Test mprotect |
| auto start = reinterpret_cast<uintptr_t>(map); |
| |
| // Only protect a whole page inside the allocated memory |
| uintptr_t protectionStart = rx::roundUpPow2(start, mPageSize); |
| uintptr_t protectionEnd = protectionStart + mPageSize; |
| |
| ASSERT(protectionStart < protectionEnd); |
| |
| angle::PageFaultCallback callback = [](uintptr_t address) { |
| return angle::PageFaultHandlerRangeType::InRange; |
| }; |
| |
| std::unique_ptr<angle::PageFaultHandler> handler(CreatePageFaultHandler(callback)); |
| |
| if (!handler->enable()) |
| { |
| GLboolean unmapResult; |
| if (buffer->unmap(context, &unmapResult) != angle::Result::Continue) |
| { |
| ERR() << "Could not unmap buffer."; |
| } |
| context->bindBuffer(targetPacked, {0}); |
| |
| // Page fault handler could not be enabled, memory can't be protected directly. |
| return false; |
| } |
| |
| size_t protectionSize = protectionEnd - protectionStart; |
| |
| ASSERT(protectionSize == mPageSize); |
| |
| bool canProtect = angle::ProtectMemory(protectionStart, protectionSize); |
| if (canProtect) |
| { |
| angle::UnprotectMemory(protectionStart, protectionSize); |
| } |
| |
| // Clean up |
| handler->disable(); |
| |
| GLboolean unmapResult; |
| if (buffer->unmap(context, &unmapResult) != angle::Result::Continue) |
| { |
| ERR() << "Could not unmap buffer."; |
| } |
| context->bindBuffer(targetPacked, {0}); |
| context->deleteBuffer(buffer->id()); |
| |
| return canProtect; |
| } |
| |
| PageFaultHandlerRangeType CoherentBufferTracker::handleWrite(uintptr_t address) |
| { |
| std::lock_guard<angle::SimpleMutex> lock(mMutex); |
| auto pagesInBuffers = getBufferPagesForAddress(address); |
| |
| if (pagesInBuffers.empty()) |
| { |
| ERR() << "Didn't find a tracked buffer containing " << reinterpret_cast<void *>(address); |
| } |
| |
| for (const auto &page : pagesInBuffers) |
| { |
| std::shared_ptr<CoherentBuffer> buffer = page.first; |
| size_t relativePage = page.second; |
| buffer->setDirty(relativePage, true); |
| } |
| |
| return pagesInBuffers.empty() ? PageFaultHandlerRangeType::OutOfRange |
| : PageFaultHandlerRangeType::InRange; |
| } |
| |
| HashMap<std::shared_ptr<CoherentBuffer>, size_t> CoherentBufferTracker::getBufferPagesForAddress( |
| uintptr_t address) |
| { |
| HashMap<std::shared_ptr<CoherentBuffer>, size_t> foundPages; |
| |
| #if defined(ANGLE_PLATFORM_ANDROID) |
| size_t page; |
| if (mShadowMemoryEnabled) |
| { |
| // Starting with Android 11 heap pointers get a tag which is stripped by the POSIX mprotect |
| // callback. We need to add this tag manually to the untagged pointer in order to determine |
| // the corresponding page. |
| // See: https://source.android.com/docs/security/test/tagged-pointers |
| // TODO(http://anglebug.com/42265874): Determine when heap pointer tagging is not enabled. |
| constexpr unsigned long long POINTER_TAG = 0xb400000000000000; |
| unsigned long long taggedAddress = address | POINTER_TAG; |
| page = static_cast<size_t>(taggedAddress / mPageSize); |
| } |
| else |
| { |
| // VMA allocated memory pointers are not tagged. |
| page = address / mPageSize; |
| } |
| #else |
| size_t page = address / mPageSize; |
| #endif |
| |
| for (const auto &pair : mBuffers) |
| { |
| std::shared_ptr<CoherentBuffer> buffer = pair.second; |
| size_t relativePage; |
| if (buffer->contains(page, &relativePage)) |
| { |
| foundPages.insert(std::make_pair(buffer, relativePage)); |
| } |
| } |
| |
| return foundPages; |
| } |
| |
| bool CoherentBufferTracker::isDirty(gl::BufferID id) |
| { |
| return mBuffers[id.value]->isDirty(); |
| } |
| |
| void CoherentBufferTracker::enable() |
| { |
| if (mEnabled) |
| { |
| return; |
| } |
| |
| PageFaultCallback callback = [this](uintptr_t address) { return handleWrite(address); }; |
| |
| // This needs to be initialized after canProtectDirectly ran and can only be initialized once. |
| if (!mPageFaultHandler) |
| { |
| mPageFaultHandler = std::unique_ptr<PageFaultHandler>(CreatePageFaultHandler(callback)); |
| } |
| |
| bool ret = mPageFaultHandler->enable(); |
| if (ret) |
| { |
| mEnabled = true; |
| } |
| else |
| { |
| ERR() << "Could not enable page fault handler."; |
| } |
| } |
| |
| bool CoherentBufferTracker::haveBuffer(gl::BufferID id) |
| { |
| return mBuffers.find(id.value) != mBuffers.end(); |
| } |
| |
| void CoherentBufferTracker::onEndFrame() |
| { |
| std::lock_guard<angle::SimpleMutex> lock(mMutex); |
| |
| if (!mEnabled) |
| { |
| return; |
| } |
| |
| mHasBeenReset = true; |
| |
| // Remove protection from all buffers |
| for (const auto &pair : mBuffers) |
| { |
| std::shared_ptr<CoherentBuffer> buffer = pair.second; |
| buffer->removeProtection(PageSharingType::NoneShared); |
| } |
| |
| disable(); |
| } |
| |
| uintptr_t CoherentBufferTracker::addBuffer(gl::BufferID id, uintptr_t start, size_t size) |
| { |
| std::lock_guard<angle::SimpleMutex> lock(mMutex); |
| |
| if (haveBuffer(id)) |
| { |
| auto buffer = mBuffers[id.value]; |
| return buffer->getRange().start; |
| } |
| |
| auto buffer = std::make_shared<CoherentBuffer>(start, size, mPageSize, mShadowMemoryEnabled); |
| uintptr_t realOrShadowStart = buffer->getRange().start; |
| |
| mBuffers.insert(std::make_pair(id.value, std::move(buffer))); |
| |
| return realOrShadowStart; |
| } |
| |
| void CoherentBufferTracker::maybeUpdateShadowMemory() |
| { |
| for (const auto &pair : mBuffers) |
| { |
| std::shared_ptr<CoherentBuffer> cb = pair.second; |
| if (cb->isShadowDirty()) |
| { |
| cb->removeProtection(PageSharingType::NoneShared); |
| cb->updateShadowMemory(); |
| cb->protectAll(); |
| } |
| } |
| } |
| |
| void CoherentBufferTracker::markAllShadowDirty() |
| { |
| for (const auto &pair : mBuffers) |
| { |
| std::shared_ptr<CoherentBuffer> cb = pair.second; |
| cb->markShadowDirty(); |
| } |
| } |
| |
| PageSharingType CoherentBufferTracker::doesBufferSharePage(gl::BufferID id) |
| { |
| bool firstPageShared = false; |
| bool lastPageShared = false; |
| |
| std::shared_ptr<CoherentBuffer> buffer = mBuffers[id.value]; |
| |
| AddressRange range = buffer->getRange(); |
| |
| size_t firstPage = range.start / mPageSize; |
| size_t lastPage = range.end() / mPageSize; |
| |
| for (const auto &pair : mBuffers) |
| { |
| gl::BufferID otherId = {pair.first}; |
| if (otherId != id) |
| { |
| std::shared_ptr<CoherentBuffer> otherBuffer = pair.second; |
| size_t relativePage; |
| if (otherBuffer->contains(firstPage, &relativePage)) |
| { |
| firstPageShared = true; |
| } |
| else if (otherBuffer->contains(lastPage, &relativePage)) |
| { |
| lastPageShared = true; |
| } |
| } |
| } |
| |
| if (firstPageShared && !lastPageShared) |
| { |
| return PageSharingType::FirstShared; |
| } |
| else if (!firstPageShared && lastPageShared) |
| { |
| return PageSharingType::LastShared; |
| } |
| else if (firstPageShared && lastPageShared) |
| { |
| return PageSharingType::FirstAndLastShared; |
| } |
| else |
| { |
| return PageSharingType::NoneShared; |
| } |
| } |
| |
| void CoherentBufferTracker::removeBuffer(gl::BufferID id) |
| { |
| std::lock_guard<angle::SimpleMutex> lock(mMutex); |
| |
| if (!haveBuffer(id)) |
| { |
| return; |
| } |
| |
| // Synchronize graphics buffer memory before the buffer is removed from the tracker. |
| if (mShadowMemoryEnabled) |
| { |
| mBuffers[id.value]->updateBufferMemory(); |
| } |
| |
| // If the buffer shares pages with other tracked buffers, |
| // don't unprotect the overlapping pages. |
| PageSharingType sharingType = doesBufferSharePage(id); |
| mBuffers[id.value]->removeProtection(sharingType); |
| mBuffers.erase(id.value); |
| } |
| |
| void *FrameCaptureShared::maybeGetShadowMemoryPointer(gl::Buffer *buffer, |
| GLsizeiptr length, |
| GLbitfield access) |
| { |
| if (!(access & GL_MAP_COHERENT_BIT_EXT) || !mCoherentBufferTracker.isShadowMemoryEnabled()) |
| { |
| return buffer->getMapPointer(); |
| } |
| |
| mCoherentBufferTracker.enable(); |
| uintptr_t realMapPointer = reinterpret_cast<uintptr_t>(buffer->getMapPointer()); |
| return (void *)mCoherentBufferTracker.addBuffer(buffer->id(), realMapPointer, length); |
| } |
| |
| void FrameCaptureShared::determineMemoryProtectionSupport(gl::Context *context) |
| { |
| // Skip this test if shadow memory was force enabled or shadow memory requirement was detected |
| // previously |
| if (mCoherentBufferTracker.isShadowMemoryEnabled()) |
| { |
| return; |
| } |
| |
| // These known devices must use shadow memory |
| HashMap<std::string, std::vector<std::string>> denyList = { |
| {"Google", {"Pixel 6", "Pixel 6 Pro", "Pixel 6a", "Pixel 7", "Pixel 7 Pro"}}, |
| }; |
| |
| angle::SystemInfo info; |
| angle::GetSystemInfo(&info); |
| bool isDeviceDenyListed = false; |
| |
| if (rx::GetAndroidSDKVersion() < 34) |
| { |
| // Before Android 14, there was a bug in Mali based Pixel preventing mprotect |
| // on Vulkan surfaces. (https://b.corp.google.com/issues/269535398) |
| // Check the denylist in this case. |
| if (denyList.find(info.machineManufacturer) != denyList.end()) |
| { |
| const std::vector<std::string> &models = denyList[info.machineManufacturer]; |
| isDeviceDenyListed = |
| std::find(models.begin(), models.end(), info.machineModelName) != models.end(); |
| } |
| } |
| |
| if (isDeviceDenyListed) |
| { |
| WARN() << "Direct memory protection not possible on deny listed device '" |
| << info.machineModelName |
| << "', enabling shadow memory for coherent buffer tracking."; |
| mCoherentBufferTracker.enableShadowMemory(); |
| } |
| else |
| { |
| // Device is not on deny listed. Run a test if we actually can protect directly. Do this |
| // only on assertion enabled builds. |
| ASSERT(mCoherentBufferTracker.canProtectDirectly(context)); |
| } |
| } |
| |
| void FrameCaptureShared::trackBufferMapping(const gl::Context *context, |
| CallCapture *call, |
| gl::BufferID id, |
| gl::Buffer *buffer, |
| GLintptr offset, |
| GLsizeiptr length, |
| bool writable, |
| bool coherent) |
| { |
| // Track that the buffer was mapped |
| mResourceTracker.setBufferMapped(context->id(), id.value); |
| |
| if (writable) |
| { |
| // If this buffer was mapped writable, we don't have any visibility into what |
| // happens to it. Therefore, remember the details about it, and we'll read it back |
| // on Unmap to repopulate it during replay. |
| mBufferDataMap[id] = std::make_pair(offset, length); |
| |
| // Track that this buffer was potentially modified |
| mResourceTracker.getTrackedResource(context->id(), ResourceIDType::Buffer) |
| .setModifiedResource(id.value); |
| |
| // Track coherent buffer |
| // Check if capture is active to not initialize the coherent buffer tracker on the |
| // first coherent glMapBufferRange call. |
| if (coherent && isCaptureActive()) |
| { |
| if (mCoherentBufferTracker.hasBeenReset()) |
| { |
| FATAL() << "Multi-capture not supprted for apps using persistent coherent memory"; |
| } |
| |
| mCoherentBufferTracker.enable(); |
| // When not using shadow memory, adding buffers to the tracking happens here instead of |
| // during mapping |
| if (!mCoherentBufferTracker.isShadowMemoryEnabled()) |
| { |
| uintptr_t data = reinterpret_cast<uintptr_t>(buffer->getMapPointer()); |
| mCoherentBufferTracker.addBuffer(id, data, length); |
| } |
| } |
| } |
| } |
| |
| void FrameCaptureShared::trackTextureUpdate(const gl::Context *context, const CallCapture &call) |
| { |
| int index = 0; |
| std::string paramName = "targetPacked"; |
| ParamType paramType = ParamType::TTextureTarget; |
| |
| // Some calls provide the textureID directly |
| // For the rest, look it up based on the currently bound texture |
| switch (call.entryPoint) |
| { |
| case EntryPoint::GLCompressedCopyTextureCHROMIUM: |
| index = 1; |
| paramName = "destIdPacked"; |
| paramType = ParamType::TTextureID; |
| break; |
| case EntryPoint::GLCopyTextureCHROMIUM: |
| case EntryPoint::GLCopySubTextureCHROMIUM: |
| case EntryPoint::GLCopyTexture3DANGLE: |
| index = 3; |
| paramName = "destIdPacked"; |
| paramType = ParamType::TTextureID; |
| break; |
| case EntryPoint::GLCopyImageSubData: |
| case EntryPoint::GLCopyImageSubDataEXT: |
| case EntryPoint::GLCopyImageSubDataOES: |
| index = 7; |
| paramName = "dstTarget"; |
| paramType = ParamType::TGLenum; |
| break; |
| default: |
| break; |
| } |
| |
| GLuint id = 0; |
| switch (paramType) |
| { |
| case ParamType::TTextureTarget: |
| { |
| gl::TextureTarget targetPacked = |
| call.params.getParam(paramName.c_str(), ParamType::TTextureTarget, index) |
| .value.TextureTargetVal; |
| gl::TextureType textureType = gl::TextureTargetToType(targetPacked); |
| gl::Texture *texture = context->getState().getTargetTexture(textureType); |
| id = texture->id().value; |
| break; |
| } |
| case ParamType::TTextureID: |
| { |
| gl::TextureID destIDPacked = |
| call.params.getParam(paramName.c_str(), ParamType::TTextureID, index) |
| .value.TextureIDVal; |
| id = destIDPacked.value; |
| break; |
| } |
| case ParamType::TGLenum: |
| { |
| GLenum target = |
| call.params.getParam(paramName.c_str(), ParamType::TGLenum, index).value.GLenumVal; |
| |
| if (target == GL_TEXTURE_CUBE_MAP) |
| { |
| // CopyImageSubData doesn't support cube faces, but PackedParams requires one |
| target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; |
| } |
| |
| gl::TextureTarget targetPacked = gl::PackParam<gl::TextureTarget>(target); |
| gl::TextureType textureType = gl::TextureTargetToType(targetPacked); |
| gl::Texture *texture = context->getState().getTargetTexture(textureType); |
| id = texture->id().value; |
| break; |
| } |
| default: |
| ERR() << "Unhandled paramType= " << static_cast<int>(paramType); |
| UNREACHABLE(); |
| break; |
| } |
| |
| // Mark it as modified |
| mResourceTracker.getTrackedResource(context->id(), ResourceIDType::Texture) |
| .setModifiedResource(id); |
| } |
| |
| // Identify and mark writeable shader image textures as modified |
| void FrameCaptureShared::trackImageUpdate(const gl::Context *context, const CallCapture &call) |
| { |
| const gl::ProgramExecutable *executable = context->getState().getProgramExecutable(); |
| for (const gl::ImageBinding &imageBinding : executable->getImageBindings()) |
| { |
| for (GLuint binding : imageBinding.boundImageUnits) |
| { |
| const gl::ImageUnit &imageUnit = context->getState().getImageUnit(binding); |
| if (imageUnit.access != GL_READ_ONLY) |
| { |
| // Get image binding texture id and mark it as modified |
| GLuint id = imageUnit.texture.id().value; |
| mResourceTracker.getTrackedResource(context->id(), ResourceIDType::Texture) |
| .setModifiedResource(id); |
| } |
| } |
| } |
| } |
| |
| void FrameCaptureShared::trackDefaultUniformUpdate(const gl::Context *context, |
| const CallCapture &call) |
| { |
| DefaultUniformType defaultUniformType = GetDefaultUniformType(call); |
| |
| GLuint programID = 0; |
| int location = 0; |
| |
| // We track default uniform updates by program and location, so look them up in parameters |
| if (defaultUniformType == DefaultUniformType::CurrentProgram) |
| { |
| programID = context->getActiveLinkedProgram()->id().value; |
| |
| location = call.params.getParam("locationPacked", ParamType::TUniformLocation, 0) |
| .value.UniformLocationVal.value; |
| } |
| else |
| { |
| ASSERT(defaultUniformType == DefaultUniformType::SpecifiedProgram); |
| |
| programID = call.params.getParam("programPacked", ParamType::TShaderProgramID, 0) |
| .value.ShaderProgramIDVal.value; |
| |
| location = call.params.getParam("locationPacked", ParamType::TUniformLocation, 1) |
| .value.UniformLocationVal.value; |
| } |
| |
| const TrackedResource &trackedShaderProgram = |
| mResourceTracker.getTrackedResource(context->id(), ResourceIDType::ShaderProgram); |
| const ResourceSet &startingPrograms = trackedShaderProgram.getStartingResources(); |
| const ResourceSet &programsToRegen = trackedShaderProgram.getResourcesToRegen(); |
| |
| // If this program was in our starting set, track its uniform updates. Unless it was deleted, |
| // then its uniforms will all be regenned along wih with the program. |
| if (startingPrograms.find(programID) != startingPrograms.end() && |
| programsToRegen.find(programID) == programsToRegen.end()) |
| { |
| // Track that we need to set this default uniform value again |
| mResourceTracker.setModifiedDefaultUniform({programID}, {location}); |
| } |
| } |
| |
| void FrameCaptureShared::trackVertexArrayUpdate(const gl::Context *context, const CallCapture &call) |
| { |
| // Look up the currently bound vertex array |
| gl::VertexArrayID id = context->getState().getVertexArray()->id(); |
| |
| // Mark it as modified |
| mResourceTracker.getTrackedResource(context->id(), ResourceIDType::VertexArray) |
| .setModifiedResource(id.value); |
| } |
| |
| void FrameCaptureShared::updateCopyImageSubData(CallCapture &call) |
| { |
| // This call modifies srcName and dstName to no longer be object IDs (GLuint), but actual |
| // packed types that can remapped using gTextureMap and gRenderbufferMap |
| |
| GLint srcName = call.params.getParam("srcName", ParamType::TGLuint, 0).value.GLuintVal; |
| GLenum srcTarget = call.params.getParam("srcTarget", ParamType::TGLenum, 1).value.GLenumVal; |
| switch (srcTarget) |
| { |
| case GL_RENDERBUFFER: |
| { |
| // Convert the GLuint to RenderbufferID |
| gl::RenderbufferID srcRenderbufferID = {static_cast<GLuint>(srcName)}; |
| call.params.setValueParamAtIndex("srcName", ParamType::TRenderbufferID, |
| srcRenderbufferID, 0); |
| break; |
| } |
| case GL_TEXTURE_2D: |
| case GL_TEXTURE_2D_ARRAY: |
| case GL_TEXTURE_3D: |
| case GL_TEXTURE_CUBE_MAP: |
| case GL_TEXTURE_EXTERNAL_OES: |
| case GL_TEXTURE_2D_MULTISAMPLE: |
| case GL_TEXTURE_2D_MULTISAMPLE_ARRAY_OES: |
| { |
| // Convert the GLuint to TextureID |
| gl::TextureID srcTextureID = {static_cast<GLuint>(srcName)}; |
| call.params.setValueParamAtIndex("srcName", ParamType::TTextureID, srcTextureID, 0); |
| break; |
| } |
| default: |
| ERR() << "Unhandled srcTarget = " << srcTarget; |
| UNREACHABLE(); |
| break; |
| } |
| |
| // Change dstName to the appropriate type based on dstTarget |
| GLint dstName = call.params.getParam("dstName", ParamType::TGLuint, 6).value.GLuintVal; |
| GLenum dstTarget = call.params.getParam("dstTarget", ParamType::TGLenum, 7).value.GLenumVal; |
| switch (dstTarget) |
| { |
| case GL_RENDERBUFFER: |
| { |
| // Convert the GLuint to RenderbufferID |
| gl::RenderbufferID dstRenderbufferID = {static_cast<GLuint>(dstName)}; |
| call.params.setValueParamAtIndex("dstName", ParamType::TRenderbufferID, |
| dstRenderbufferID, 6); |
| break; |
| } |
| case GL_TEXTURE_2D: |
| case GL_TEXTURE_2D_ARRAY: |
| case GL_TEXTURE_3D: |
| case GL_TEXTURE_CUBE_MAP: |
| case GL_TEXTURE_EXTERNAL_OES: |
| case GL_TEXTURE_2D_MULTISAMPLE: |
| case GL_TEXTURE_2D_MULTISAMPLE_ARRAY_OES: |
| { |
| // Convert the GLuint to TextureID |
| gl::TextureID dstTextureID = {static_cast<GLuint>(dstName)}; |
| call.params.setValueParamAtIndex("dstName", ParamType::TTextureID, dstTextureID, 6); |
| break; |
| } |
| default: |
| ERR() << "Unhandled dstTarget = " << dstTarget; |
| UNREACHABLE(); |
| break; |
| } |
| } |
| |
| void FrameCaptureShared::overrideProgramBinary(const gl::Context *context, |
| CallCapture &inCall, |
| std::vector<CallCapture> &outCalls) |
| { |
| // Program binaries are inherently non-portable, even between two ANGLE builds. |
| // If an application is using glProgramBinary in the middle of a trace, we need to replace |
| // those calls with an equivalent sequence of portable calls. |
| // |
| // For example, here is a sequence an app could use for glProgramBinary: |
| // |
| // gShaderProgramMap[42] = glCreateProgram(); |
| // glProgramBinary(gShaderProgramMap[42], GL_PROGRAM_BINARY_ANGLE, gBinaryData[x], 1000); |
| // glGetProgramiv(gShaderProgramMap[42], GL_LINK_STATUS, gReadBuffer); |
| // glGetProgramiv(gShaderProgramMap[42], GL_PROGRAM_BINARY_LENGTH, gReadBuffer); |
| // |
| // With this override, the glProgramBinary call will be replaced like so: |
| // |
| // gShaderProgramMap[42] = glCreateProgram(); |
| // === Begin override === |
| // gShaderProgramMap[43] = glCreateShader(GL_VERTEX_SHADER); |
| // glShaderSource(gShaderProgramMap[43], 1, string_0, &gBinaryData[100]); |
| // glCompileShader(gShaderProgramMap[43]); |
| // glAttachShader(gShaderProgramMap[42], gShaderProgramMap[43]); |
| // glDeleteShader(gShaderProgramMap[43]); |
| // gShaderProgramMap[43] = glCreateShader(GL_FRAGMENT_SHADER); |
| // glShaderSource(gShaderProgramMap[43], 1, string_1, &gBinaryData[200]); |
| // glCompileShader(gShaderProgramMap[43]); |
| // glAttachShader(gShaderProgramMap[42], gShaderProgramMap[43]); |
| // glDeleteShader(gShaderProgramMap[43]); |
| // glBindAttribLocation(gShaderProgramMap[42], 0, "attrib1"); |
| // glBindAttribLocation(gShaderProgramMap[42], 1, "attrib2"); |
| // glLinkProgram(gShaderProgramMap[42]); |
| // UpdateUniformLocation(gShaderProgramMap[42], "foo", 0, 20); |
| // UpdateUniformLocation(gShaderProgramMap[42], "bar", 72, 1); |
| // glUseProgram(gShaderProgramMap[42]); |
| // UpdateCurrentProgram(gShaderProgramMap[42]); |
| // glUniform4fv(gUniformLocations[gCurrentProgram][0], 20, &gBinaryData[300]); |
| // glUniform1iv(gUniformLocations[gCurrentProgram][72], 1, &gBinaryData[400]); |
| // === End override === |
| // glGetProgramiv(gShaderProgramMap[42], GL_LINK_STATUS, gReadBuffer); |
| // glGetProgramiv(gShaderProgramMap[42], GL_PROGRAM_BINARY_LENGTH, gReadBuffer); |
| // |
| // To facilitate this override, we are serializing each shader stage source into the binary |
| // itself. See Program::serialize and Program::deserialize. Once extracted from the binary, |
| // they will be available via getProgramSources. |
| |
| gl::ShaderProgramID id = inCall.params.getParam("programPacked", ParamType::TShaderProgramID, 0) |
| .value.ShaderProgramIDVal; |
| |
| gl::Program *program = context->getProgramResolveLink(id); |
| ASSERT(program); |
| |
| mResourceTracker.onShaderProgramAccess(id); |
| gl::ShaderProgramID tempShaderStartID = {mResourceTracker.getMaxShaderPrograms()}; |
| GenerateLinkedProgram(context, context->getState(), &mResourceTracker, &outCalls, program, id, |
| tempShaderStartID, getProgramSources(id)); |
| } |
| |
| void FrameCaptureShared::captureCustomMapBufferFromContext(const gl::Context *context, |
| const char *entryPointName, |
| CallCapture &call, |
| std::vector<CallCapture> &callsOut) |
| { |
| gl::BufferBinding binding = |
| call.params.getParam("targetPacked", ParamType::TBufferBinding, 0).value.BufferBindingVal; |
| gl::Buffer *buffer = context->getState().getTargetBuffer(binding); |
| |
| if (call.entryPoint == EntryPoint::GLMapBufferRange || |
| call.entryPoint == EntryPoint::GLMapBufferRangeEXT) |
| { |
| GLintptr offset = call.params.getParam("offset", ParamType::TGLintptr, 1).value.GLintptrVal; |
| GLsizeiptr length = |
| call.params.getParam("length", ParamType::TGLsizeiptr, 2).value.GLsizeiptrVal; |
| GLbitfield access = |
| call.params.getParam("access", ParamType::TGLbitfield, 3).value.GLbitfieldVal; |
| |
| trackBufferMapping(context, &call, buffer->id(), buffer, offset, length, |
| access & GL_MAP_WRITE_BIT, access & GL_MAP_COHERENT_BIT_EXT); |
| } |
| else |
| { |
| ASSERT(call.entryPoint == EntryPoint::GLMapBufferOES); |
| GLenum access = call.params.getParam("access", ParamType::TGLenum, 1).value.GLenumVal; |
| bool writeAccess = |
| (access == GL_WRITE_ONLY_OES || access == GL_WRITE_ONLY || access == GL_READ_WRITE); |
| trackBufferMapping(context, &call, buffer->id(), buffer, 0, |
| static_cast<GLsizeiptr>(buffer->getSize()), writeAccess, false); |
| } |
| |
| CaptureCustomMapBuffer(entryPointName, call, callsOut, buffer->id()); |
| } |
| |
| void FrameCaptureShared::maybeOverrideEntryPoint(const gl::Context *context, |
| CallCapture &inCall, |
| std::vector<CallCapture> &outCalls) |
| { |
| switch (inCall.entryPoint) |
| { |
| case EntryPoint::GLCopyImageSubData: |
| case EntryPoint::GLCopyImageSubDataEXT: |
| case EntryPoint::GLCopyImageSubDataOES: |
| { |
| // We must look at the src and dst target types to determine which remap table to use |
| updateCopyImageSubData(inCall); |
| outCalls.emplace_back(std::move(inCall)); |
| break; |
| } |
| case EntryPoint::GLProgramBinary: |
| case EntryPoint::GLProgramBinaryOES: |
| { |
| // Binary formats are not portable at all, so replace the calls with full linking |
| // sequence |
| overrideProgramBinary(context, inCall, outCalls); |
| break; |
| } |
| case EntryPoint::GLUniformBlockBinding: |
| { |
| CaptureCustomUniformBlockBinding(inCall, outCalls); |
| break; |
| } |
| case EntryPoint::GLMapBufferRange: |
| { |
| captureCustomMapBufferFromContext(context, "MapBufferRange", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::GLMapBufferRangeEXT: |
| { |
| captureCustomMapBufferFromContext(context, "MapBufferRangeEXT", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::GLMapBufferOES: |
| { |
| captureCustomMapBufferFromContext(context, "MapBufferOES", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::GLCreateShader: |
| { |
| CaptureCustomShaderProgram("CreateShader", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::GLCreateProgram: |
| { |
| CaptureCustomShaderProgram("CreateProgram", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::GLCreateShaderProgramv: |
| { |
| CaptureCustomShaderProgram("CreateShaderProgramv", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::GLFenceSync: |
| { |
| CaptureCustomFenceSync(inCall, outCalls); |
| break; |
| } |
| case EntryPoint::EGLCreateImage: |
| { |
| const egl::Image *eglImage = GetImageFromParam(context, inCall.params.getReturnValue()); |
| CaptureCustomCreateEGLImage(context, "CreateEGLImage", eglImage->getWidth(), |
| eglImage->getHeight(), inCall, outCalls); |
| break; |
| } |
| case EntryPoint::EGLCreateImageKHR: |
| { |
| const egl::Image *eglImage = GetImageFromParam(context, inCall.params.getReturnValue()); |
| CaptureCustomCreateEGLImage(context, "CreateEGLImageKHR", eglImage->getWidth(), |
| eglImage->getHeight(), inCall, outCalls); |
| break; |
| } |
| case EntryPoint::EGLDestroyImage: |
| { |
| CaptureCustomDestroyEGLImage("DestroyEGLImage", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::EGLDestroyImageKHR: |
| { |
| CaptureCustomDestroyEGLImage("DestroyEGLImageKHR", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::EGLCreateSync: |
| { |
| CaptureCustomCreateEGLSync("CreateEGLSync", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::EGLCreateSyncKHR: |
| { |
| CaptureCustomCreateEGLSync("CreateEGLSyncKHR", inCall, outCalls); |
| break; |
| } |
| case EntryPoint::EGLCreatePbufferSurface: |
| { |
| CaptureCustomCreatePbufferSurface(inCall, outCalls); |
| break; |
| } |
| case EntryPoint::EGLCreateNativeClientBufferANDROID: |
| { |
| CaptureCustomCreateNativeClientbuffer(inCall, outCalls); |
| break; |
| } |
| |
| default: |
| { |
| // Pass the single call through |
| outCalls.emplace_back(std::move(inCall)); |
| break; |
| } |
| } |
| } |
| |
| void FrameCaptureShared::maybeCaptureCoherentBuffers(const gl::Context *context) |
| { |
| if (!isCaptureActive()) |
| { |
| return; |
| } |
| |
| std::lock_guard<angle::SimpleMutex> lock(mCoherentBufferTracker.mMutex); |
| |
| for (const auto &pair : mCoherentBufferTracker.mBuffers) |
| { |
| gl::BufferID id = {pair.first}; |
| if (mCoherentBufferTracker.isDirty(id)) |
| { |
| captureCoherentBufferSnapshot(context, id); |
| } |
| } |
| } |
| |
| void FrameCaptureShared::maybeCaptureDrawArraysClientData(const gl::Context *context, |
| CallCapture &call, |
| size_t instanceCount) |
| { |
| if (!context->getStateCache().hasAnyActiveClientAttrib()) |
| { |
| return; |
| } |
| |
| // Get counts from paramBuffer. |
| GLint firstVertex = |
| call.params.getParamFlexName("first", "start", ParamType::TGLint, 1).value.GLintVal; |
| GLsizei drawCount = call.params.getParam("count", ParamType::TGLsizei, 2).value.GLsizeiVal; |
| captureClientArraySnapshot(context, firstVertex + drawCount, instanceCount); |
| } |
| |
| void FrameCaptureShared::maybeCaptureDrawElementsClientData(const gl::Context *context, |
| CallCapture &call, |
| size_t instanceCount) |
| { |
| if (!context->getStateCache().hasAnyActiveClientAttrib()) |
| { |
| return; |
| } |
| |
| // if the count is zero then the index evaluation is not valid and we wouldn't be drawing |
| // anything anyway, so skip capturing |
| GLsizei count = call.params.getParam("count", ParamType::TGLsizei, 1).value.GLsizeiVal; |
| if (count == 0) |
| { |
| return; |
| } |
| |
| gl::DrawElementsType drawElementsType = |
| call.params.getParam("typePacked", ParamType::TDrawElementsType, 2) |
| .value.DrawElementsTypeVal; |
| const void *indices = |
| call.params.getParam("indices", ParamType::TvoidConstPointer, 3).value.voidConstPointerVal; |
| |
| gl::IndexRange indexRange; |
| |
| bool restart = context->getState().isPrimitiveRestartEnabled(); |
| |
| gl::Buffer *elementArrayBuffer = context->getState().getVertexArray()->getElementArrayBuffer(); |
| if (elementArrayBuffer) |
| { |
| size_t offset = reinterpret_cast<size_t>(indices); |
| (void)elementArrayBuffer->getIndexRange(context, drawElementsType, offset, count, restart, |
| &indexRange); |
| } |
| else |
| { |
| ASSERT(indices); |
| indexRange = gl::ComputeIndexRange(drawElementsType, indices, count, restart); |
| } |
| |
| // index starts from 0 |
| captureClientArraySnapshot(context, indexRange.end + 1, instanceCount); |
| } |
| |
| template <typename AttribT, typename FactoryT> |
| void CreateEGLImagePreCallUpdate(const CallCapture &call, |
| ResourceTracker &resourceTracker, |
| ParamType paramType, |
| FactoryT factory) |
| { |
| EGLImage image = call.params.getReturnValue().value.EGLImageVal; |
| const ParamCapture ¶m = call.params.getParam("attrib_list", paramType, 4); |
| const AttribT *attribs = |
| param.data.empty() ? nullptr : reinterpret_cast<const AttribT *>(param.data[0].data()); |
| egl::AttributeMap attributeMap = factory(attribs); |
| attributeMap.initializeWithoutValidation(); |
| resourceTracker.getImageToAttribTable().insert( |
| std::pair<EGLImage, egl::AttributeMap>(image, attributeMap)); |
| } |
| |
| void FrameCaptureShared::maybeCapturePreCallUpdates( |
| const gl::Context *context, |
| CallCapture &call, |
| std::vector<CallCapture> *shareGroupSetupCalls, |
| ResourceIDToSetupCallsMap *resourceIDToSetupCalls) |
| { |
| switch (call.entryPoint) |
| { |
| case EntryPoint::GLVertexAttribPointer: |
| case EntryPoint::GLVertexPointer: |
| case EntryPoint::GLColorPointer: |
| case EntryPoint::GLTexCoordPointer: |
| case EntryPoint::GLNormalPointer: |
| case EntryPoint::GLPointSizePointerOES: |
| { |
| // Get array location |
| GLuint index = 0; |
| if (call.entryPoint == EntryPoint::GLVertexAttribPointer) |
| { |
| index = call.params.getParam("index", ParamType::TGLuint, 0).value.GLuintVal; |
| } |
| else |
| { |
| gl::ClientVertexArrayType type; |
| switch (call.entryPoint) |
| { |
| case EntryPoint::GLVertexPointer: |
| type = gl::ClientVertexArrayType::Vertex; |
| break; |
| case EntryPoint::GLColorPointer: |
| type = gl::ClientVertexArrayType::Color; |
| break; |
| case EntryPoint::GLTexCoordPointer: |
| type = gl::ClientVertexArrayType::TextureCoord; |
| break; |
| case EntryPoint::GLNormalPointer: |
| type = gl::ClientVertexArrayType::Normal; |
| break; |
| case EntryPoint::GLPointSizePointerOES: |
| type = gl::ClientVertexArrayType::PointSize; |
| break; |
| default: |
| UNREACHABLE(); |
| type = gl::ClientVertexArrayType::InvalidEnum; |
| } |
| index = gl::GLES1Renderer::VertexArrayIndex(type, context->getState().gles1()); |
| } |
| |
| if (call.params.hasClientArrayData()) |
| { |
| mClientVertexArrayMap[index] = static_cast<int>(mFrameCalls.size()); |
| } |
| else |
| { |
| mClientVertexArrayMap[index] = -1; |
| } |
| break; |
| } |
| |
| case EntryPoint::GLGenFramebuffers: |
| case EntryPoint::GLGenFramebuffersOES: |
| { |
| GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| const gl::FramebufferID *framebufferIDs = |
| call.params.getParam("framebuffersPacked", ParamType::TFramebufferIDPointer, 1) |
| .value.FramebufferIDPointerVal; |
| for (GLsizei i = 0; i < count; i++) |
| { |
| handleGennedResource(context, framebufferIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLBindFramebuffer: |
| case EntryPoint::GLBindFramebufferOES: |
| maybeGenResourceOnBind<gl::FramebufferID>(context, call); |
| break; |
| |
| case EntryPoint::GLGenRenderbuffers: |
| case EntryPoint::GLGenRenderbuffersOES: |
| { |
| GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| const gl::RenderbufferID *renderbufferIDs = |
| call.params.getParam("renderbuffersPacked", ParamType::TRenderbufferIDPointer, 1) |
| .value.RenderbufferIDPointerVal; |
| for (GLsizei i = 0; i < count; i++) |
| { |
| handleGennedResource(context, renderbufferIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLBindRenderbuffer: |
| case EntryPoint::GLBindRenderbufferOES: |
| maybeGenResourceOnBind<gl::RenderbufferID>(context, call); |
| break; |
| |
| case EntryPoint::GLDeleteRenderbuffers: |
| case EntryPoint::GLDeleteRenderbuffersOES: |
| { |
| // Look up how many renderbuffers are being deleted |
| GLsizei n = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| |
| // Look up the pointer to list of renderbuffers |
| const gl::RenderbufferID *renderbufferIDs = |
| call.params |
| .getParam("renderbuffersPacked", ParamType::TRenderbufferIDConstPointer, 1) |
| .value.RenderbufferIDConstPointerVal; |
| |
| // For each renderbuffer listed for deletion |
| for (int32_t i = 0; i < n; ++i) |
| { |
| // If we're capturing, track what renderbuffers have been deleted |
| handleDeletedResource(context, renderbufferIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLGenTextures: |
| { |
| GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| const gl::TextureID *textureIDs = |
| call.params.getParam("texturesPacked", ParamType::TTextureIDPointer, 1) |
| .value.TextureIDPointerVal; |
| for (GLsizei i = 0; i < count; i++) |
| { |
| // If we're capturing, track what new textures have been genned |
| handleGennedResource(context, textureIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLBindTexture: |
| maybeGenResourceOnBind<gl::TextureID>(context, call); |
| if (isCaptureActive()) |
| { |
| gl::TextureType target = |
| call.params.getParam("targetPacked", ParamType::TTextureType, 0) |
| .value.TextureTypeVal; |
| context->getFrameCapture()->getStateResetHelper().setTextureBindingDirty( |
| context->getState().getActiveSampler(), target); |
| } |
| break; |
| |
| case EntryPoint::GLDeleteBuffers: |
| { |
| GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| const gl::BufferID *bufferIDs = |
| call.params.getParam("buffersPacked", ParamType::TBufferIDConstPointer, 1) |
| .value.BufferIDConstPointerVal; |
| for (GLsizei i = 0; i < count; i++) |
| { |
| // For each buffer being deleted, check our backup of data and remove it |
| const auto &bufferDataInfo = mBufferDataMap.find(bufferIDs[i]); |
| if (bufferDataInfo != mBufferDataMap.end()) |
| { |
| mBufferDataMap.erase(bufferDataInfo); |
| } |
| // If we're capturing, track what buffers have been deleted |
| handleDeletedResource(context, bufferIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLGenBuffers: |
| { |
| GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| const gl::BufferID *bufferIDs = |
| call.params.getParam("buffersPacked", ParamType::TBufferIDPointer, 1) |
| .value.BufferIDPointerVal; |
| for (GLsizei i = 0; i < count; i++) |
| { |
| handleGennedResource(context, bufferIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLBindBuffer: |
| maybeGenResourceOnBind<gl::BufferID>(context, call); |
| if (isCaptureActive()) |
| { |
| gl::BufferBinding binding = |
| call.params.getParam("targetPacked", ParamType::TBufferBinding, 0) |
| .value.BufferBindingVal; |
| |
| context->getFrameCapture()->getStateResetHelper().setBufferBindingDirty(binding); |
| } |
| break; |
| |
| case EntryPoint::GLBindBufferBase: |
| case EntryPoint::GLBindBufferRange: |
| if (isCaptureActive()) |
| { |
| WARN() << "Indexed buffer binding changed during capture, Reset doesn't handle it " |
| "yet."; |
| } |
| break; |
| |
| case EntryPoint::GLDeleteProgramPipelines: |
| case EntryPoint::GLDeleteProgramPipelinesEXT: |
| { |
| GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| const gl::ProgramPipelineID *pipelineIDs = |
| call.params |
| .getParam("pipelinesPacked", ParamType::TProgramPipelineIDConstPointer, 1) |
| .value.ProgramPipelineIDPointerVal; |
| for (GLsizei i = 0; i < count; i++) |
| { |
| handleDeletedResource(context, pipelineIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLGenProgramPipelines: |
| case EntryPoint::GLGenProgramPipelinesEXT: |
| { |
| GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| const gl::ProgramPipelineID *pipelineIDs = |
| call.params.getParam("pipelinesPacked", ParamType::TProgramPipelineIDPointer, 1) |
| .value.ProgramPipelineIDPointerVal; |
| for (GLsizei i = 0; i < count; i++) |
| { |
| handleGennedResource(context, pipelineIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLDeleteSync: |
| { |
| gl::SyncID sync = |
| call.params.getParam("syncPacked", ParamType::TSyncID, 0).value.SyncIDVal; |
| FrameCaptureShared *frameCaptureShared = |
| context->getShareGroup()->getFrameCaptureShared(); |
| // If we're capturing, track which fence sync has been deleted |
| if (frameCaptureShared->isCaptureActive()) |
| { |
| mResourceTracker.setDeletedFenceSync(sync); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLDrawArrays: |
| { |
| maybeCaptureDrawArraysClientData(context, call, 1); |
| maybeCaptureCoherentBuffers(context); |
| break; |
| } |
| |
| case EntryPoint::GLDrawArraysInstanced: |
| case EntryPoint::GLDrawArraysInstancedANGLE: |
| case EntryPoint::GLDrawArraysInstancedEXT: |
| { |
| GLsizei instancecount = |
| call.params.getParamFlexName("instancecount", "primcount", ParamType::TGLsizei, 3) |
| .value.GLsizeiVal; |
| |
| maybeCaptureDrawArraysClientData(context, call, instancecount); |
| maybeCaptureCoherentBuffers(context); |
| break; |
| } |
| |
| case EntryPoint::GLDrawElements: |
| { |
| maybeCaptureDrawElementsClientData(context, call, 1); |
| maybeCaptureCoherentBuffers(context); |
| break; |
| } |
| |
| case EntryPoint::GLDrawElementsInstanced: |
| case EntryPoint::GLDrawElementsInstancedANGLE: |
| case EntryPoint::GLDrawElementsInstancedEXT: |
| { |
| GLsizei instancecount = |
| call.params.getParamFlexName("instancecount", "primcount", ParamType::TGLsizei, 4) |
| .value.GLsizeiVal; |
| |
| maybeCaptureDrawElementsClientData(context, call, instancecount); |
| maybeCaptureCoherentBuffers(context); |
| break; |
| } |
| |
| case EntryPoint::GLCreateShaderProgramv: |
| { |
| // Refresh the cached shader sources. |
| // The command CreateShaderProgramv() creates a stand-alone program from an array of |
| // null-terminated source code strings for a single shader type, so we need update the |
| // Shader and Program sources, similar to GLCompileShader + GLLinkProgram handling. |
| gl::ShaderProgramID programID = {call.params.getReturnValue().value.GLuintVal}; |
| const ParamCapture ¶mCapture = |
| call.params.getParam("typePacked", ParamType::TShaderType, 0); |
| const ParamCapture &lineCount = call.params.getParam("count", ParamType::TGLsizei, 1); |
| const ParamCapture &strings = |
| call.params.getParam("strings", ParamType::TGLcharConstPointerPointer, 2); |
| |
| std::ostringstream sourceString; |
| for (int i = 0; i < lineCount.value.GLsizeiVal; ++i) |
| { |
| sourceString << strings.value.GLcharConstPointerPointerVal[i]; |
| } |
| |
| gl::ShaderType shaderType = paramCapture.value.ShaderTypeVal; |
| ProgramSources source; |
| source[shaderType] = sourceString.str(); |
| setProgramSources(programID, source); |
| handleGennedResource(context, programID); |
| mResourceTracker.setShaderProgramType(programID, ShaderProgramType::ProgramType); |
| break; |
| } |
| |
| case EntryPoint::GLCreateProgram: |
| { |
| // If we're capturing, track which programs have been created |
| gl::ShaderProgramID programID = {call.params.getReturnValue().value.GLuintVal}; |
| handleGennedResource(context, programID); |
| |
| mResourceTracker.setShaderProgramType(programID, ShaderProgramType::ProgramType); |
| break; |
| } |
| |
| case EntryPoint::GLDeleteProgram: |
| { |
| // If we're capturing, track which programs have been deleted |
| const ParamCapture ¶m = |
| call.params.getParam("programPacked", ParamType::TShaderProgramID, 0); |
| handleDeletedResource(context, param.value.ShaderProgramIDVal); |
| |
| // If this assert fires, it means a ShaderProgramID has changed from program to shader |
| // which is unsupported |
| ASSERT(mResourceTracker.getShaderProgramType(param.value.ShaderProgramIDVal) == |
| ShaderProgramType::ProgramType); |
| |
| break; |
| } |
| |
| case EntryPoint::GLCreateShader: |
| { |
| // If we're capturing, track which shaders have been created |
| gl::ShaderProgramID shaderID = {call.params.getReturnValue().value.GLuintVal}; |
| handleGennedResource(context, shaderID); |
| |
| mResourceTracker.setShaderProgramType(shaderID, ShaderProgramType::ShaderType); |
| break; |
| } |
| |
| case EntryPoint::GLDeleteShader: |
| { |
| // If we're capturing, track which shaders have been deleted |
| const ParamCapture ¶m = |
| call.params.getParam("shaderPacked", ParamType::TShaderProgramID, 0); |
| handleDeletedResource(context, param.value.ShaderProgramIDVal); |
| |
| // If this assert fires, it means a ShaderProgramID has changed from shader to program |
| // which is unsupported |
| ASSERT(mResourceTracker.getShaderProgramType(param.value.ShaderProgramIDVal) == |
| ShaderProgramType::ShaderType); |
| break; |
| } |
| |
| case EntryPoint::GLCompileShader: |
| { |
| // Refresh the cached shader sources. |
| gl::ShaderProgramID shaderID = |
| call.params.getParam("shaderPacked", ParamType::TShaderProgramID, 0) |
| .value.ShaderProgramIDVal; |
| const gl::Shader *shader = context->getShaderNoResolveCompile(shaderID); |
| // Shaders compiled for ProgramBinary will not have a shader created |
| if (shader) |
| { |
| setShaderSource(shaderID, shader->getSourceString()); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLLinkProgram: |
| { |
| // Refresh the cached program sources. |
| gl::ShaderProgramID programID = |
| call.params.getParam("programPacked", ParamType::TShaderProgramID, 0) |
| .value.ShaderProgramIDVal; |
| const gl::Program *program = context->getProgramResolveLink(programID); |
| // Programs linked in support of ProgramBinary will not have attached shaders |
| if (program->getState().hasAnyAttachedShader()) |
| { |
| setProgramSources(programID, GetAttachedProgramSources(context, program)); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLDeleteTextures: |
| { |
| // Free any TextureLevelDataMap entries being tracked for this texture |
| // This is to cover the scenario where a texture has been created, its |
| // levels cached, then texture deleted and recreated, receiving the same ID |
| |
| // Look up how many textures are being deleted |
| GLsizei n = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| |
| // Look up the pointer to list of textures |
| const gl::TextureID *textureIDs = |
| call.params.getParam("texturesPacked", ParamType::TTextureIDConstPointer, 1) |
| .value.TextureIDConstPointerVal; |
| |
| // For each texture listed for deletion |
| for (int32_t i = 0; i < n; ++i) |
| { |
| // If we're capturing, track what textures have been deleted |
| handleDeletedResource(context, textureIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLMapBufferOES: |
| { |
| gl::BufferBinding target = |
| call.params.getParam("targetPacked", ParamType::TBufferBinding, 0) |
| .value.BufferBindingVal; |
| |
| GLbitfield access = |
| call.params.getParam("access", ParamType::TGLenum, 1).value.GLenumVal; |
| |
| gl::Buffer *buffer = context->getState().getTargetBuffer(target); |
| |
| GLintptr offset = 0; |
| GLsizeiptr length = static_cast<GLsizeiptr>(buffer->getSize()); |
| |
| bool writable = |
| access == GL_WRITE_ONLY_OES || access == GL_WRITE_ONLY || access == GL_READ_WRITE; |
| |
| FrameCaptureShared *frameCaptureShared = |
| context->getShareGroup()->getFrameCaptureShared(); |
| frameCaptureShared->trackBufferMapping(context, &call, buffer->id(), buffer, offset, |
| length, writable, false); |
| break; |
| } |
| |
| case EntryPoint::GLUnmapBuffer: |
| case EntryPoint::GLUnmapBufferOES: |
| { |
| // See if we need to capture the buffer contents |
| captureMappedBufferSnapshot(context, call); |
| |
| // Track that the buffer was unmapped, for use during state reset |
| gl::BufferBinding target = |
| call.params.getParam("targetPacked", ParamType::TBufferBinding, 0) |
| .value.BufferBindingVal; |
| gl::Buffer *buffer = context->getState().getTargetBuffer(target); |
| mResourceTracker.setBufferUnmapped(context->id(), buffer->id().value); |
| |
| // Remove from CoherentBufferTracker |
| mCoherentBufferTracker.removeBuffer(buffer->id()); |
| break; |
| } |
| |
| case EntryPoint::GLBufferData: |
| case EntryPoint::GLBufferSubData: |
| { |
| gl::BufferBinding target = |
| call.params.getParam("targetPacked", ParamType::TBufferBinding, 0) |
| .value.BufferBindingVal; |
| |
| gl::Buffer *buffer = context->getState().getTargetBuffer(target); |
| |
| // Track that this buffer's contents have been modified |
| mResourceTracker.getTrackedResource(context->id(), ResourceIDType::Buffer) |
| .setModifiedResource(buffer->id().value); |
| |
| // BufferData is equivalent to UnmapBuffer, for what we're tracking. |
| // From the ES 3.1 spec in BufferData section: |
| // If any portion of the buffer object is mapped in the current context or any |
| // context current to another thread, it is as though UnmapBuffer (see section |
| // 6.3.1) is executed in each such context prior to deleting the existing data |
| // store. |
| // Track that the buffer was unmapped, for use during state reset |
| mResourceTracker.setBufferUnmapped(context->id(), buffer->id().value); |
| |
| break; |
| } |
| |
| case EntryPoint::GLCopyBufferSubData: |
| { |
| maybeCaptureCoherentBuffers(context); |
| break; |
| } |
| case EntryPoint::GLFinish: |
| { |
| // When using shadow memory we might need to synchronize it here. |
| if (mCoherentBufferTracker.isShadowMemoryEnabled()) |
| { |
| mCoherentBufferTracker.maybeUpdateShadowMemory(); |
| } |
| break; |
| } |
| case EntryPoint::GLDeleteFramebuffers: |
| case EntryPoint::GLDeleteFramebuffersOES: |
| { |
| // Look up how many framebuffers are being deleted |
| GLsizei n = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| |
| // Look up the pointer to list of framebuffers |
| const gl::FramebufferID *framebufferIDs = |
| call.params.getParam("framebuffersPacked", ParamType::TFramebufferIDConstPointer, 1) |
| .value.FramebufferIDConstPointerVal; |
| |
| // For each framebuffer listed for deletion |
| for (int32_t i = 0; i < n; ++i) |
| { |
| // If we're capturing, track what framebuffers have been deleted |
| handleDeletedResource(context, framebufferIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLUseProgram: |
| { |
| if (isCaptureActive()) |
| { |
| context->getFrameCapture()->getStateResetHelper().setEntryPointDirty( |
| EntryPoint::GLUseProgram); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLGenVertexArrays: |
| case EntryPoint::GLGenVertexArraysOES: |
| { |
| GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| const gl::VertexArrayID *arrayIDs = |
| call.params.getParam("arraysPacked", ParamType::TVertexArrayIDPointer, 1) |
| .value.VertexArrayIDPointerVal; |
| for (GLsizei i = 0; i < count; i++) |
| { |
| handleGennedResource(context, arrayIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLDeleteVertexArrays: |
| case EntryPoint::GLDeleteVertexArraysOES: |
| { |
| GLsizei count = call.params.getParam("n", ParamType::TGLsizei, 0).value.GLsizeiVal; |
| const gl::VertexArrayID *arrayIDs = |
| call.params.getParam("arraysPacked", ParamType::TVertexArrayIDConstPointer, 1) |
| .value.VertexArrayIDConstPointerVal; |
| for (GLsizei i = 0; i < count; i++) |
| { |
| // If we're capturing, track which vertex arrays have been deleted |
| handleDeletedResource(context, arrayIDs[i]); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLBindVertexArray: |
| case EntryPoint::GLBindVertexArrayOES: |
| { |
| if (isCaptureActive()) |
| { |
| context->getFrameCapture()->getStateResetHelper().setEntryPointDirty( |
| EntryPoint::GLBindVertexArray); |
| } |
| break; |
| } |
| case EntryPoint::GLBlendFunc: |
| { |
| if (isCaptureActive()) |
| { |
| context->getFrameCapture()->getStateResetHelper().setEntryPointDirty( |
| EntryPoint::GLBlendFunc); |
| } |
| break; |
| } |
| case EntryPoint::GLBlendFuncSeparate: |
| { |
| if (isCaptureActive()) |
| { |
| context->getFrameCapture()->getStateResetHelper().setEntryPointDirty( |
| EntryPoint::GLBlendFuncSeparate); |
| } |
| break; |
| } |
| case EntryPoint::GLBlendEquation: |
| case EntryPoint::GLBlendEquationSeparate: |
| { |
| if (isCaptureActive()) |
| { |
| context->getFrameCapture()->getStateResetHelper().setEntryPointDirty( |
| EntryPoint::GLBlendEquationSeparate); |
| } |
| break; |
| } |
| case EntryPoint::GLColorMask: |
| { |
| if (isCaptureActive()) |
| { |
| context->getFrameCapture()->getStateResetHelper().setEntryPointDirty( |
| EntryPoint::GLColorMask); |
| } |
| break; |
| } |
| case EntryPoint::GLBlendColor: |
| { |
| if (isCaptureActive()) |
| { |
| context->getFrameCapture()->getStateResetHelper().setEntryPointDirty( |
| EntryPoint::GLBlendColor); |
| } |
| break; |
| } |
| |
| case EntryPoint::GLEGLImageTargetTexture2DOES: |
| { |
| gl::TextureType target = |
| call.params.getParam("targetPacked", ParamType::TTextureType, 0) |
| .value.TextureTypeVal; |
| egl::ImageID imageID = |
| call.params.getParam("imagePacked", ParamType::TImageID, 1).value.ImageIDVal; |
| mResourceTracker.getTextureIDToImageTable().insert(std::pair<GLuint, egl::ImageID>( |
| context->getState().getTargetTexture(target)->getId(), imageID)); |
| break; |
| } |
| |
| case EntryPoint::EGLCreateImage: |
| { |
| CreateEGLImagePreCallUpdate<EGLAttrib>(call, mResourceTracker, |
| ParamType::TEGLAttribPointer, |
| egl::AttributeMap::CreateFromAttribArray); |
| if (isCaptureActive()) |
| { |
| EGLImage eglImage = call.params.getReturnValue().value.EGLImageVal; |
| egl::ImageID imageID = egl::PackParam<egl::ImageID>(eglImage); |
| handleGennedResource(context, imageID); |
| } |
| break; |
| } |
| case EntryPoint::EGLCreateImageKHR: |
| { |
| CreateEGLImagePreCallUpdate<EGLint>(call, mResourceTracker, ParamType::TEGLintPointer, |
| egl::AttributeMap::CreateFromIntArray); |
| if (isCaptureActive()) |
| { |
| EGLImageKHR eglImage = call.params.getReturnValue().value.EGLImageKHRVal; |
| egl::ImageID imageID = egl::PackParam<egl::ImageID>(eglImage); |
| handleGennedResource(context, imageID); |
| } |
| break; |
| } |
| case EntryPoint::EGLDestroyImage: |
| case EntryPoint::EGLDestroyImageKHR: |
| { |
| egl::ImageID eglImageID = |
| call.params.getParam("imagePacked", ParamType::TImageID, 1).value.ImageIDVal; |
| |
| // Clear any texture->image mappings that involve this image |
| for (auto texImageIter = mResourceTracker.getTextureIDToImageTable().begin(); |
| texImageIter != mResourceTracker.getTextureIDToImageTable().end();) |
| { |
| if (texImageIter->second == eglImageID) |
| { |
| texImageIter = mResourceTracker.getTextureIDToImageTable().erase(texImageIter); |
| } |
| else |
| { |
| ++texImageIter; |
| } |
| } |
| |
| FrameCaptureShared *frameCaptureShared = |
| context->getShareGroup()->getFrameCaptureShared(); |
| if (frameCaptureShared->isCaptureActive()) |
| { |
| handleDeletedResource(context, eglImageID); |
| } |
| break; |
| } |
| case EntryPoint::EGLCreateSync: |
| case EntryPoint::EGLCreateSyncKHR: |
| { |
| egl::SyncID eglSyncID = call.params.getReturnValue().value.egl_SyncIDVal; |
| FrameCaptureShared *frameCaptureShared = |
| context->getShareGroup()->getFrameCaptureShared(); |
| // If we're capturing, track which egl sync has been created |
| if (frameCaptureShared->isCaptureActive()) |
| { |
| handleGennedResource(context, eglSyncID); |
| } |
| break; |
| } |
| case EntryPoint::EGLDestroySync: |
| case EntryPoint::EGLDestroySyncKHR: |
| { |
| egl::SyncID eglSyncID = |
| call.params.getParam("syncPacked", ParamType::Tegl_SyncID, 1).value.egl_SyncIDVal; |
| FrameCaptureShared *frameCaptureShared = |
| context->getShareGroup()->getFrameCaptureShared(); |
| // If we're capturing, track which EGL sync has been deleted |
| if (frameCaptureShared->isCaptureActive()) |
| { |
| handleDeletedResource(context, eglSyncID); |
| } |
| break; |
| } |
| case EntryPoint::GLDispatchCompute: |
| { |
| // When using shadow memory we need to update the real memory here |
| if (mCoherentBufferTracker.isShadowMemoryEnabled()) |
| { |
| maybeCaptureCoherentBuffers(context); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| |
| if (IsTextureUpdate(call)) |
| { |
| // If this call modified texture contents, track it for possible reset |
| trackTextureUpdate(context, call); |
| } |
| |
| if (IsImageUpdate(call)) |
| { |
| // If this call modified shader image contents, track it for possible reset |
| trackImageUpdate(context, call); |
| } |
| |
| if (isCaptureActive() && GetDefaultUniformType(call) != DefaultUniformType::None) |
| { |
| trackDefaultUniformUpdate(context, call); |
| } |
| |
| if (IsVertexArrayUpdate(call)) |
| { |
| trackVertexArrayUpdate(context, call); |
| } |
| |
| updateReadBufferSize(call.params.getReadBufferSize()); |
| |
| std::vector<gl::ShaderProgramID> shaderProgramIDs; |
| if (FindResourceIDsInCall<gl::ShaderProgramID>(call, shaderProgramIDs)) |
| { |
| for (gl::ShaderProgramID shaderProgramID : shaderProgramIDs) |
| { |
| mResourceTracker.onShaderProgramAccess(shaderProgramID); |
| |
| if (isCaptureActive()) |
| { |
| // Track that this call referenced a ShaderProgram, setting it active for Setup |
| MarkResourceIDActive(ResourceIDType::ShaderProgram, shaderProgramID.value, |
| shareGroupSetupCalls, resourceIDToSetupCalls); |
| } |
| } |
| } |
| |
| std::vector<gl::TextureID> textureIDs; |
| if (FindResourceIDsInCall<gl::TextureID>(call, textureIDs)) |
| { |
| for (gl::TextureID textureID : textureIDs) |
| { |
| if (isCaptureActive()) |
| { |
| // Track that this call referenced a Texture, setting it active for Setup |
| MarkResourceIDActive(ResourceIDType::Texture, textureID.value, shareGroupSetupCalls, |
| resourceIDToSetupCalls); |
| } |
| } |
| } |
| } |
| |
| template <typename ParamValueType> |
| void FrameCaptureShared::maybeGenResourceOnBind(const gl::Context *context, CallCapture &call) |
| { |
| const char *paramName = ParamValueTrait<ParamValueType>::name; |
| const ParamType paramType = ParamValueTrait<ParamValueType>::typeID; |
| |
| const ParamCapture ¶m = call.params.getParam(paramName, paramType, 1); |
| const ParamValueType id = AccessParamValue<ParamValueType>(paramType, param.value); |
| |
| // Don't inject the default resource or resources that are already generated |
| if (id.value != 0 && !resourceIsGenerated(context, id)) |
| { |
| handleGennedResource(context, id); |
| |
| ResourceIDType resourceIDType = GetResourceIDTypeFromParamType(param.type); |
| const char *resourceName = GetResourceIDTypeName(resourceIDType); |
| |
| std::stringstream updateFuncNameStr; |
| updateFuncNameStr << "Set" << resourceName << "ID"; |
| ParamBuffer params; |
| if (IsTrackedPerContext(resourceIDType)) |
| { |
| // TODO (https://issuetracker.google.com/169868803) The '2' version can be removed after |
| // all context-local objects are tracked per-context |
| updateFuncNameStr << "2"; |
| params.addValueParam("contextID", ParamType::TGLuint, context->id().value); |
| } |
| std::string updateFuncName = updateFuncNameStr.str(); |
| params.addValueParam("id", ParamType::TGLuint, id.value); |
| mFrameCalls.emplace_back(updateFuncName, std::move(params)); |
| } |
| } |
| |
| void FrameCaptureShared::updateResourceCountsFromParamCapture(const ParamCapture ¶m, |
| ResourceIDType idType) |
| { |
| if (idType != ResourceIDType::InvalidEnum) |
| { |
| mHasResourceType.set(idType); |
| |
| // Capture resource IDs for non-pointer types. |
| if (strcmp(ParamTypeToString(param.type), "GLuint") == 0) |
| { |
| mMaxAccessedResourceIDs[idType] = |
| std::max(mMaxAccessedResourceIDs[idType], param.value.GLuintVal); |
| } |
| // Capture resource IDs for pointer types. |
| if (strstr(ParamTypeToString(param.type), "GLuint *") != nullptr) |
| { |
| if (param.data.size() == 1u) |
| { |
| const GLuint *dataPtr = reinterpret_cast<const GLuint *>(param.data[0].data()); |
| size_t numHandles = param.data[0].size() / sizeof(GLuint); |
| for (size_t handleIndex = 0; handleIndex < numHandles; ++handleIndex) |
| { |
| mMaxAccessedResourceIDs[idType] = |
| std::max(mMaxAccessedResourceIDs[idType], dataPtr[handleIndex]); |
| } |
| } |
| } |
| if (idType == ResourceIDType::Sync) |
| { |
| mMaxAccessedResourceIDs[idType] = |
| std::max(mMaxAccessedResourceIDs[idType], param.value.GLuintVal); |
| } |
| } |
| } |
| |
| void FrameCaptureShared::updateResourceCountsFromCallCapture(const CallCapture &call) |
| { |
| |
| for (const ParamCapture ¶m : call.params.getParamCaptures()) |
| { |
| ResourceIDType idType = GetResourceIDTypeFromParamType(param.type); |
| updateResourceCountsFromParamCapture(param, idType); |
| } |
| |
| // Update resource IDs in the return value. Return values types are not stored as resource IDs, |
| // but instead are stored as GLuints. Therefore we need to explicitly label the resource ID type |
| // when we call update. Currently only shader and program creation are explicitly tracked. |
| switch (call.entryPoint) |
| { |
| case EntryPoint::GLCreateShader: |
| case EntryPoint::GLCreateProgram: |
| updateResourceCountsFromParamCapture(call.params.getReturnValue(), |
| ResourceIDType::ShaderProgram); |
| break; |
| |
| case EntryPoint::GLFenceSync: |
| updateResourceCountsFromParamCapture(call.params.getReturnValue(), |
| ResourceIDType::Sync); |
| break; |
| case EntryPoint::EGLCreateSync: |
| case EntryPoint::EGLCreateSyncKHR: |
| updateResourceCountsFromParamCapture(call.params.getReturnValue(), |
| ResourceIDType::egl_Sync); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void FrameCaptureShared::captureCall(gl::Context *context, CallCapture &&inCall, bool isCallValid) |
| { |
| if (SkipCall(inCall.entryPoint)) |
| { |
| return; |
| } |
| |
| if (isCallValid) |
| { |
| // Save the call's contextID |
| inCall.contextID = context->id(); |
| |
| // Update resource counts before we override entry points with custom calls. |
| updateResourceCountsFromCallCapture(inCall); |
| |
| size_t j = mFrameCalls.size(); |
| |
| std::vector<CallCapture> outCalls; |
| maybeOverrideEntryPoint(context, inCall, outCalls); |
| |
| // Need to loop on any new calls we added during override |
| for (CallCapture &call : outCalls) |
| { |
| // During capture, consider all frame calls active |
| if (isCaptureActive()) |
| { |
| call.isActive = true; |
| } |
| |
| maybeCapturePreCallUpdates(context, call, &mShareGroupSetupCalls, |
| &mResourceIDToSetupCalls); |
| mFrameCalls.emplace_back(std::move(call)); |
| maybeCapturePostCallUpdates(context); |
| } |
| |
| // Tag all 'added' commands with this context |
| for (size_t k = j; k < mFrameCalls.size(); k++) |
| { |
| mFrameCalls[k].contextID = context->id(); |
| } |
| |
| // Evaluate the validation expression to determine if we insert a validation checkpoint. |
| // This lets the user pick a subset of calls to check instead of checking every call. |
| if (mValidateSerializedState && !mValidationExpression.empty()) |
| { |
| // Example substitution for frame #2, call #110: |
| // Before: (call == 2) && (frame >= 100) && (frame <= 120) && ((frame % 10) == 0) |
| // After: (2 == 2) && (110 >= 100) && (110 <= 120) && ((110 % 10) == 0) |
| // Evaluates to 1.0. |
| std::string expression = mValidationExpression; |
| |
| angle::ReplaceAllSubstrings(&expression, "frame", std::to_string(mFrameIndex)); |
| angle::ReplaceAllSubstrings(&expression, "call", std::to_string(mFrameCalls.size())); |
| |
| double result = ceval_result(expression); |
| if (result > 0) |
| { |
| CaptureValidateSerializedState(context, &mFrameCalls); |
| } |
| } |
| } |
| else |
| { |
| const int maxInvalidCallLogs = 3; |
| size_t &callCount = isCaptureActive() ? mInvalidCallCountsActive[inCall.entryPoint] |
| : mInvalidCallCountsInactive[inCall.entryPoint]; |
| callCount++; |
| if (callCount <= maxInvalidCallLogs) |
| { |
| std::ostringstream msg; |
| msg << "FrameCapture (capture " << (isCaptureActive() ? "active" : "inactive") |
| << "): Not capturing invalid call to " << GetEntryPointName(inCall.entryPoint); |
| if (callCount == maxInvalidCallLogs) |
| { |
| msg << " (will no longer repeat for this entry point)"; |
| } |
| INFO() << msg.str(); |
| } |
| |
| std::stringstream skipCall; |
| skipCall << "Skipping invalid call to " << GetEntryPointName(inCall.entryPoint) |
| << " with error: " |
| << gl::GLenumToString(gl::GLESEnum::ErrorCode, context->getErrorForCapture()); |
| AddComment(&mFrameCalls, skipCall.str()); |
| } |
| } |
| |
| void FrameCaptureShared::maybeCapturePostCallUpdates(const gl::Context *context) |
| { |
| // Process resource ID updates. |
| if (isCaptureActive()) |
| { |
| MaybeCaptureUpdateResourceIDs(context, &mResourceTracker, &mFrameCalls); |
| } |
| |
| CallCapture &lastCall = mFrameCalls.back(); |
| switch (lastCall.entryPoint) |
| { |
| case EntryPoint::GLCreateShaderProgramv: |
| { |
| gl::ShaderProgramID programId; |
| programId.value = lastCall.params.getReturnValue().value.GLuintVal; |
| const gl::Program *program = context->getProgramResolveLink(programId); |
| CaptureUpdateUniformLocations(program, &mFrameCalls); |
| CaptureUpdateUniformBlockIndexes(program, &mFrameCalls); |
| break; |
| } |
| case EntryPoint::GLLinkProgram: |
| { |
| const ParamCapture ¶m = |
| lastCall.params.getParam("programPacked", ParamType::TShaderProgramID, 0); |
| const gl::Program *program = |
| context->getProgramResolveLink(param.value.ShaderProgramIDVal); |
| CaptureUpdateUniformLocations(program, &mFrameCalls); |
| CaptureUpdateUniformBlockIndexes(program, &mFrameCalls); |
| break; |
| } |
| case EntryPoint::GLUseProgram: |
| CaptureUpdateCurrentProgram(lastCall, 0, &mFrameCalls); |
| break; |
| case EntryPoint::GLActiveShaderProgram: |
| CaptureUpdateCurrentProgram(lastCall, 1, &mFrameCalls); |
| break; |
| case EntryPoint::GLDeleteProgram: |
| { |
| const ParamCapture ¶m = |
| lastCall.params.getParam("programPacked", ParamType::TShaderProgramID, 0); |
| CaptureDeleteUniformLocations(param.value.ShaderProgramIDVal, &mFrameCalls); |
| break; |
| } |
| case EntryPoint::GLShaderSource: |
| { |
| lastCall.params.setValueParamAtIndex("count", ParamType::TGLsizei, 1, 1); |
| |
| ParamCapture ¶mLength = |
| lastCall.params.getParam("length", ParamType::TGLintConstPointer, 3); |
| paramLength.data.resize(1); |
| // Set the length parameter to {-1} to signal that the actual string length |
| // is to be used. Since we store the parameter blob as an array of four uint8_t |
| // values, we have to pass the binary equivalent of -1. |
| paramLength.data[0] = {0xff, 0xff, 0xff, 0xff}; |
| break; |
| } |
| case EntryPoint::GLBufferData: |
| case EntryPoint::GLBufferSubData: |
| { |
| // When using shadow memory we need to update it from real memory here |
| if (mCoherentBufferTracker.isShadowMemoryEnabled()) |
| { |
| gl::BufferBinding target = |
| lastCall.params.getParam("targetPacked", ParamType::TBufferBinding, 0) |
| .value.BufferBindingVal; |
| |
| gl::Buffer *buffer = context->getState().getTargetBuffer(target); |
| if (mCoherentBufferTracker.haveBuffer(buffer->id())) |
| { |
| std::shared_ptr<CoherentBuffer> cb = |
| mCoherentBufferTracker.mBuffers[buffer->id().value]; |
| cb->removeProtection(PageSharingType::NoneShared); |
| cb->updateShadowMemory(); |
| cb->protectAll(); |
| } |
| } |
| break; |
| } |
| |
| case EntryPoint::GLCopyBufferSubData: |
| { |
| // When using shadow memory, we need to mark the buffer shadowDirty bit to true |
| // so it will be synchronized with real memory on the next glFinish call. |
| if (mCoherentBufferTracker.isShadowMemoryEnabled()) |
| { |
| gl::BufferBinding target = |
| lastCall.params.getParam("writeTargetPacked", ParamType::TBufferBinding, 1) |
| .value.BufferBindingVal; |
| |
| gl::Buffer *buffer = context->getState().getTargetBuffer(target); |
| if (mCoherentBufferTracker.haveBuffer(buffer->id())) |
| { |
| std::shared_ptr<CoherentBuffer> cb = |
| mCoherentBufferTracker.mBuffers[buffer->id().value]; |
| // This needs to be synced on glFinish |
| cb->markShadowDirty(); |
| } |
| } |
| break; |
| } |
| case EntryPoint::GLDispatchCompute: |
| { |
| // When using shadow memory, we need to mark all buffer's shadowDirty bit to true |
| // so they will be synchronized with real memory on the next glFinish call. |
| if (mCoherentBufferTracker.isShadowMemoryEnabled()) |
| { |
| mCoherentBufferTracker.markAllShadowDirty(); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| void FrameCaptureShared::captureClientArraySnapshot(const gl::Context *context, |
| size_t vertexCount, |
| size_t instanceCount) |
| { |
| if (vertexCount == 0) |
| { |
| // Nothing to capture |
| return; |
| } |
| |
| const gl::VertexArray *vao = context->getState().getVertexArray(); |
| |
| // Capture client array data. |
| for (size_t attribIndex : context->getStateCache().getActiveClientAttribsMask()) |
| { |
| const gl::VertexAttribute &attrib = vao->getVertexAttribute(attribIndex); |
| const gl::VertexBinding &binding = vao->getVertexBinding(attrib.bindingIndex); |
| |
| int callIndex = mClientVertexArrayMap[attribIndex]; |
| |
| if (callIndex != -1) |
| { |
| size_t count = vertexCount; |
| |
| if (binding.getDivisor() > 0) |
| { |
| count = rx::UnsignedCeilDivide(static_cast<uint32_t>(instanceCount), |
| binding.getDivisor()); |
| } |
| |
| // The last capture element doesn't take up the full stride. |
| size_t bytesToCapture = (count - 1) * binding.getStride() + attrib.format->pixelBytes; |
| |
| CallCapture &call = mFrameCalls[callIndex]; |
| ParamCapture ¶m = call.params.getClientArrayPointerParameter(); |
| ASSERT(param.type == ParamType::TvoidConstPointer); |
| |
| ParamBuffer updateParamBuffer; |
| updateParamBuffer.addValueParam<GLint>("arrayIndex", ParamType::TGLint, |
| static_cast<uint32_t>(attribIndex)); |
| |
| ParamCapture updateMemory("pointer", ParamType::TvoidConstPointer); |
| CaptureMemory(param.value.voidConstPointerVal, bytesToCapture, &updateMemory); |
| updateParamBuffer.addParam(std::move(updateMemory)); |
| |
| updateParamBuffer.addValueParam<GLuint64>("size", ParamType::TGLuint64, bytesToCapture); |
| |
| mFrameCalls.emplace_back("UpdateClientArrayPointer", std::move(updateParamBuffer)); |
| |
| mClientArraySizes[attribIndex] = |
| std::max(mClientArraySizes[attribIndex], bytesToCapture); |
| } |
| } |
| } |
| |
| void FrameCaptureShared::captureCoherentBufferSnapshot(const gl::Context *context, gl::BufferID id) |
| { |
| if (!hasBufferData(id)) |
| { |
| // This buffer was not marked writable |
| return; |
| } |
| |
| const gl::State &apiState = context->getState(); |
| const gl::BufferManager &buffers = apiState.getBufferManagerForCapture(); |
| gl::Buffer *buffer = buffers.getBuffer(id); |
| if (!buffer) |
| { |
| // Could not find buffer binding |
| return; |
| } |
| |
| ASSERT(buffer->isMapped()); |
| |
| std::shared_ptr<angle::CoherentBuffer> coherentBuffer = |
| mCoherentBufferTracker.mBuffers[id.value]; |
| |
| std::vector<PageRange> dirtyPageRanges = coherentBuffer->getDirtyPageRanges(); |
| |
| if (mCoherentBufferTracker.isShadowMemoryEnabled() && !dirtyPageRanges.empty()) |
| { |
| coherentBuffer->updateBufferMemory(); |
| } |
| |
| AddressRange wholeRange = coherentBuffer->getRange(); |
| |
| for (PageRange &pageRange : dirtyPageRanges) |
| { |
| // Write protect the memory already, so the app is blocked on writing during our capture |
| coherentBuffer->protectPageRange(pageRange); |
| |
| // Create the parameters to our helper for use during replay |
| ParamBuffer dataParamBuffer; |
| |
| // Pass in the target buffer ID |
| dataParamBuffer.addValueParam("dest", ParamType::TGLuint, buffer->id().value); |
| |
| // Capture the current buffer data with a binary param |
| ParamCapture captureData("source", ParamType::TvoidConstPointer); |
| |
| AddressRange dirtyRange = coherentBuffer->getDirtyAddressRange(pageRange); |
| CaptureMemory(reinterpret_cast<void *>(dirtyRange.start), dirtyRange.size, &captureData); |
| dataParamBuffer.addParam(std::move(captureData)); |
| |
| // Also track its size for use with memcpy |
| dataParamBuffer.addValueParam<GLsizeiptr>("size", ParamType::TGLsizeiptr, |
| static_cast<GLsizeiptr>(dirtyRange.size)); |
| |
| if (wholeRange.start != dirtyRange.start) |
| { |
| // Capture with offset |
| GLsizeiptr offset = dirtyRange.start - wholeRange.start; |
| |
| ASSERT(offset > 0); |
| |
| // The dirty page range is not at the start of the buffer, track the offset. |
| dataParamBuffer.addValueParam<GLsizeiptr>("offset", ParamType::TGLsizeiptr, offset); |
| |
| // Call the helper that populates the buffer with captured data |
| mFrameCalls.emplace_back("UpdateClientBufferDataWithOffset", |
| std::move(dataParamBuffer)); |
| } |
| else |
| { |
| // Call the helper that populates the buffer with captured data |
| mFrameCalls.emplace_back("UpdateClientBufferData", std::move(dataParamBuffer)); |
| } |
| } |
| } |
| |
| void FrameCaptureShared::captureMappedBufferSnapshot(const gl::Context *context, |
| const CallCapture &call) |
| { |
| // If the buffer was mapped writable, we need to restore its data, since we have no |
| // visibility into what the client did to the buffer while mapped. |
| // This sequence will result in replay calls like this: |
| // ... |
| // gMappedBufferData[gBufferMap[42]] = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, 65536, |
| // GL_MAP_WRITE_BIT); |
| // ... |
| // UpdateClientBufferData(42, &gBinaryData[164631024], 65536); |
| // glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); |
| // ... |
| |
| // Re-map the buffer, using the info we tracked about the buffer |
| gl::BufferBinding target = |
| call.params.getParam("targetPacked", ParamType::TBufferBinding, 0).value.BufferBindingVal; |
| |
| FrameCaptureShared *frameCaptureShared = context->getShareGroup()->getFrameCaptureShared(); |
| gl::Buffer *buffer = context->getState().getTargetBuffer(target); |
| if (!frameCaptureShared->hasBufferData(buffer->id())) |
| { |
| // This buffer was not marked writable, so we did not back it up |
| return; |
| } |
| |
| std::pair<GLintptr, GLsizeiptr> bufferDataOffsetAndLength = |
| frameCaptureShared->getBufferDataOffsetAndLength(buffer->id()); |
| GLintptr offset = bufferDataOffsetAndLength.first; |
| GLsizeiptr length = bufferDataOffsetAndLength.second; |
| |
| // Map the buffer so we can copy its contents out |
| ASSERT(!buffer->isMapped()); |
| angle::Result result = buffer->mapRange(context, offset, length, GL_MAP_READ_BIT); |
| if (result != angle::Result::Continue) |
| { |
| ERR() << "Failed to mapRange of buffer" << std::endl; |
| } |
| const uint8_t *data = reinterpret_cast<const uint8_t *>(buffer->getMapPointer()); |
| |
| // Create the parameters to our helper for use during replay |
| ParamBuffer dataParamBuffer; |
| |
| // Pass in the target buffer ID |
| dataParamBuffer.addValueParam("dest", ParamType::TGLuint, buffer->id().value); |
| |
| // Capture the current buffer data with a binary param |
| ParamCapture captureData("source", ParamType::TvoidConstPointer); |
| CaptureMemory(data, length, &captureData); |
| dataParamBuffer.addParam(std::move(captureData)); |
| |
| // Also track its size for use with memcpy |
| dataParamBuffer.addValueParam<GLsizeiptr>("size", ParamType::TGLsizeiptr, length); |
| |
| // Call the helper that populates the buffer with captured data |
| mFrameCalls.emplace_back("UpdateClientBufferData", std::move(dataParamBuffer)); |
| |
| // Unmap the buffer and move on |
| GLboolean dontCare; |
| (void)buffer->unmap(context, &dontCare); |
| } |
| |
| void FrameCaptureShared::checkForCaptureTrigger() |
| { |
| // Determine if trigger has changed |
| std::string captureTriggerStr = GetCaptureTrigger(); |
| if (captureTriggerStr.empty()) |
| { |
| return; |
| } |
| |
| // If the value has changed, use the original value as the frame count |
| uint32_t captureTrigger = atoi(captureTriggerStr.c_str()); |
| if ((mCaptureTrigger > 0) && (captureTrigger == 0)) |
| { |
| // Start mid-execution capture for the current frame |
| mCaptureStartFrame = mFrameIndex + 1; |
| |
| // Use the original trigger value as the frame count |
| mCaptureEndFrame = mCaptureStartFrame + mCaptureTrigger - 1; |
| |
| INFO() << "Capture triggered after frame " << mFrameIndex << " for " << mCaptureTrigger |
| << " frames"; |
| |
| // Stop polling |
| mCaptureTrigger = 0; |
| } |
| else if (captureTrigger > 0) |
| { |
| // Update number of frames to capture in MEC |
| mCaptureTrigger = captureTrigger; |
| } |
| } |
| |
| void FrameCaptureShared::scanSetupCalls(std::vector<CallCapture> &setupCalls) |
| { |
| // Scan all the instructions in the list for tracking |
| for (CallCapture &call : setupCalls) |
| { |
| updateReadBufferSize(call.params.getReadBufferSize()); |
| updateResourceCountsFromCallCapture(call); |
| } |
| } |
| |
| void FrameCaptureShared::runMidExecutionCapture(gl::Context *mainContext) |
| { |
| // Set the capture active to ensure all GLES commands issued by the next frame are |
| // handled correctly by maybeCapturePreCallUpdates() and maybeCapturePostCallUpdates(). |
| setCaptureActive(); |
| |
| // Make sure all pending work for every Context in the share group has completed so all data |
| // (buffers, textures, etc.) has been updated and no resources are in use. |
| egl::ShareGroup *shareGroup = mainContext->getShareGroup(); |
| shareGroup->finishAllContexts(); |
| |
| const gl::State &contextState = mainContext->getState(); |
| gl::State mainContextReplayState( |
| nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, contextState.getClientVersion(), |
| false, true, true, true, false, EGL_CONTEXT_PRIORITY_MEDIUM_IMG, |
| contextState.hasRobustAccess(), contextState.hasProtectedContent(), false); |
| mainContextReplayState.initializeForCapture(mainContext); |
| |
| CaptureShareGroupMidExecutionSetup(mainContext, &mShareGroupSetupCalls, &mResourceTracker, |
| mainContextReplayState, mMaxAccessedResourceIDs); |
| |
| scanSetupCalls(mShareGroupSetupCalls); |
| |
| egl::Display *display = mainContext->getDisplay(); |
| egl::Surface *draw = mainContext->getCurrentDrawSurface(); |
| egl::Surface *read = mainContext->getCurrentReadSurface(); |
| |
| for (auto shareContext : shareGroup->getContexts()) |
| { |
| FrameCapture *frameCapture = shareContext.second->getFrameCapture(); |
| ASSERT(frameCapture->getSetupCalls().empty()); |
| |
| if (shareContext.second->id() == mainContext->id()) |
| { |
| CaptureMidExecutionSetup(shareContext.second, &frameCapture->getSetupCalls(), |
| frameCapture->getStateResetHelper(), &mShareGroupSetupCalls, |
| &mResourceIDToSetupCalls, &mResourceTracker, |
| mainContextReplayState, mValidateSerializedState); |
| scanSetupCalls(frameCapture->getSetupCalls()); |
| |
| std::stringstream protoStream; |
| std::stringstream headerStream; |
| std::stringstream bodyStream; |
| |
| protoStream << "void " |
| << FmtSetupFunction(kNoPartId, mainContext->id(), FuncUsage::Prototype); |
| std::string proto = protoStream.str(); |
| |
| WriteCppReplayFunctionWithParts(mainContext->id(), ReplayFunc::Setup, mReplayWriter, 1, |
| &mBinaryData, frameCapture->getSetupCalls(), |
| headerStream, bodyStream, &mResourceIDBufferSize); |
| |
| mReplayWriter.addPrivateFunction(proto, headerStream, bodyStream); |
| } |
| else |
| { |
| const gl::State &shareContextState = shareContext.second->getState(); |
| gl::State auxContextReplayState(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, |
| shareContextState.getClientVersion(), false, true, true, |
| true, false, EGL_CONTEXT_PRIORITY_MEDIUM_IMG, |
| shareContextState.hasRobustAccess(), |
| shareContextState.hasProtectedContent(), false); |
| auxContextReplayState.initializeForCapture(shareContext.second); |
| |
| egl::Error error = shareContext.second->makeCurrent(display, draw, read); |
| if (error.isError()) |
| { |
| INFO() << "MEC unable to make secondary context current"; |
| } |
| |
| CaptureMidExecutionSetup(shareContext.second, &frameCapture->getSetupCalls(), |
| frameCapture->getStateResetHelper(), &mShareGroupSetupCalls, |
| &mResourceIDToSetupCalls, &mResourceTracker, |
| auxContextReplayState, mValidateSerializedState); |
| |
| scanSetupCalls(frameCapture->getSetupCalls()); |
| |
| WriteAuxiliaryContextCppSetupReplay( |
| mReplayWriter, mCompression, mOutDirectory, shareContext.second, mCaptureLabel, 1, |
| frameCapture->getSetupCalls(), &mBinaryData, mSerializeStateEnabled, *this, |
| &mResourceIDBufferSize); |
| } |
| // Track that this context was created before MEC started |
| mActiveContexts.insert(shareContext.first); |
| } |
| |
| egl::Error error = mainContext->makeCurrent(display, draw, read); |
| if (error.isError()) |
| { |
| INFO() << "MEC unable to make main context current again"; |
| } |
| } |
| |
| void FrameCaptureShared::onEndFrame(gl::Context *context) |
| { |
| if (!enabled() || mFrameIndex > mCaptureEndFrame) |
| { |
| setCaptureInactive(); |
| mCoherentBufferTracker.onEndFrame(); |
| if (enabled()) |
| { |
| resetMidExecutionCapture(context); |
| } |
| resetCaptureStartEndFrames(); |
| mFrameIndex++; |
| return; |
| } |
| |
| FrameCapture *frameCapture = context->getFrameCapture(); |
| |
| // Count resource IDs. This is also done on every frame. It could probably be done by |
| // checking the GL state instead of the calls. |
| for (const CallCapture &call : mFrameCalls) |
| { |
| for (const ParamCapture ¶m : call.params.getParamCaptures()) |
| { |
| ResourceIDType idType = GetResourceIDTypeFromParamType(param.type); |
| if (idType != ResourceIDType::InvalidEnum) |
| { |
| mHasResourceType.set(idType); |
| } |
| } |
| } |
| |
| mWindowSurfaceContextID = context->id(); |
| |
| // On Android, we can trigger a capture during the run |
| checkForCaptureTrigger(); |
| |
| // Check for MEC. Done after checkForCaptureTrigger(), since that can modify mCaptureStartFrame. |
| if (mFrameIndex < mCaptureStartFrame) |
| { |
| if (mFrameIndex == mCaptureStartFrame - 1) |
| { |
| // Update output directory location |
| getOutputDirectory(); |
| // Trigger MEC. |
| runMidExecutionCapture(context); |
| } |
| mFrameIndex++; |
| reset(); |
| return; |
| } |
| |
| ASSERT(isCaptureActive()); |
| |
| if (!mFrameCalls.empty()) |
| { |
| mActiveFrameIndices.push_back(getReplayFrameIndex()); |
| } |
| |
| // Make sure all pending work for every Context in the share group has completed so all data |
| // (buffers, textures, etc.) has been updated and no resources are in use. |
| egl::ShareGroup *shareGroup = context->getShareGroup(); |
| shareGroup->finishAllContexts(); |
| |
| // Only validate the first frame for now to save on retracing time. |
| if (mValidateSerializedState && mFrameIndex == mCaptureStartFrame) |
| { |
| CaptureValidateSerializedState(context, &mFrameCalls); |
| } |
| |
| writeMainContextCppReplay(context, frameCapture->getSetupCalls(), |
| frameCapture->getStateResetHelper()); |
| |
| if (mFrameIndex == mCaptureEndFrame) |
| { |
| // Write shared MEC after frame sequence so we can eliminate unused assets like programs |
| WriteShareGroupCppSetupReplay(mReplayWriter, mCompression, mOutDirectory, mCaptureLabel, 1, |
| 1, mShareGroupSetupCalls, &mResourceTracker, &mBinaryData, |
| mSerializeStateEnabled, mWindowSurfaceContextID, |
| &mResourceIDBufferSize); |
| |
| // Save the index files after the last frame. |
| writeCppReplayIndexFiles(context, false); |
| SaveBinaryData(mCompression, mOutDirectory, kSharedContextId, mCaptureLabel, mBinaryData); |
| mBinaryData.clear(); |
| mWroteIndexFile = true; |
| INFO() << "Finished recording graphics API capture"; |
| } |
| |
| reset(); |
| mFrameIndex++; |
| } |
| |
| void FrameCaptureShared::onDestroyContext(const gl::Context *context) |
| { |
| if (!mEnabled) |
| { |
| return; |
| } |
| if (!mWroteIndexFile && mFrameIndex > mCaptureStartFrame) |
| { |
| // If context is destroyed before end frame is reached and at least |
| // 1 frame has been recorded, then write the index files. |
| // It doesn't make sense to write the index files when no frame has been recorded |
| mFrameIndex -= 1; |
| mCaptureEndFrame = mFrameIndex; |
| writeCppReplayIndexFiles(context, true); |
| SaveBinaryData(mCompression, mOutDirectory, kSharedContextId, mCaptureLabel, mBinaryData); |
| mBinaryData.clear(); |
| mWroteIndexFile = true; |
| } |
| } |
| |
| void FrameCaptureShared::onMakeCurrent(const gl::Context *context, const egl::Surface *drawSurface) |
| { |
| if (!drawSurface) |
| { |
| return; |
| } |
| |
| // Track the width, height and color space of the draw surface as provided to makeCurrent |
| SurfaceParams ¶ms = mDrawSurfaceParams[context->id()]; |
| params.extents = gl::Extents(drawSurface->getWidth(), drawSurface->getHeight(), 1); |
| params.colorSpace = egl::FromEGLenum<egl::ColorSpace>(drawSurface->getGLColorspace()); |
| } |
| |
| void StateResetHelper::setDefaultResetCalls(const gl::Context *context, |
| angle::EntryPoint entryPoint) |
| { |
| static const gl::BlendState kDefaultBlendState; |
| |
| // Populate default reset calls for entrypoints to support looping to beginning |
| switch (entryPoint) |
| { |
| case angle::EntryPoint::GLUseProgram: |
| { |
| if (context->getActiveLinkedProgram() && |
| context->getActiveLinkedProgram()->id().value != 0) |
| { |
| Capture(&mResetCalls[angle::EntryPoint::GLUseProgram], |
| gl::CaptureUseProgram(context->getState(), true, {0})); |
| } |
| break; |
| } |
| case angle::EntryPoint::GLBindVertexArray: |
| { |
| if (context->getState().getVertexArray()->id().value != 0) |
| { |
| VertexArrayCaptureFuncs vertexArrayFuncs(context->isGLES1()); |
| Capture(&mResetCalls[angle::EntryPoint::GLBindVertexArray], |
| vertexArrayFuncs.bindVertexArray(context->getState(), true, {0})); |
| } |
| break; |
| } |
| case angle::EntryPoint::GLBlendFunc: |
| { |
| Capture(&mResetCalls[angle::EntryPoint::GLBlendFunc], |
| CaptureBlendFunc(context->getState(), true, kDefaultBlendState.sourceBlendRGB, |
| kDefaultBlendState.destBlendRGB)); |
| break; |
| } |
| case angle::EntryPoint::GLBlendFuncSeparate: |
| { |
| Capture(&mResetCalls[angle::EntryPoint::GLBlendFuncSeparate], |
| CaptureBlendFuncSeparate( |
| context->getState(), true, kDefaultBlendState.sourceBlendRGB, |
| kDefaultBlendState.destBlendRGB, kDefaultBlendState.sourceBlendAlpha, |
| kDefaultBlendState.destBlendAlpha)); |
| break; |
| } |
| case angle::EntryPoint::GLBlendEquation: |
| { |
| UNREACHABLE(); // GLBlendEquationSeparate is always used instead |
| break; |
| } |
| case angle::EntryPoint::GLBlendEquationSeparate: |
| { |
| Capture(&mResetCalls[angle::EntryPoint::GLBlendEquationSeparate], |
| CaptureBlendEquationSeparate(context->getState(), true, |
| kDefaultBlendState.blendEquationRGB, |
| kDefaultBlendState.blendEquationAlpha)); |
| break; |
| } |
| case angle::EntryPoint::GLColorMask: |
| { |
| Capture(&mResetCalls[angle::EntryPoint::GLColorMask], |
| CaptureColorMask(context->getState(), true, |
| gl::ConvertToGLBoolean(kDefaultBlendState.colorMaskRed), |
| gl::ConvertToGLBoolean(kDefaultBlendState.colorMaskGreen), |
| gl::ConvertToGLBoolean(kDefaultBlendState.colorMaskBlue), |
| gl::ConvertToGLBoolean(kDefaultBlendState.colorMaskAlpha))); |
| break; |
| } |
| case angle::EntryPoint::GLBlendColor: |
| { |
| Capture(&mResetCalls[angle::EntryPoint::GLBlendColor], |
| CaptureBlendColor(context->getState(), true, 0, 0, 0, 0)); |
| break; |
| } |
| default: |
| ERR() << "Unhandled entry point in setDefaultResetCalls: " |
| << GetEntryPointName(entryPoint); |
| UNREACHABLE(); |
| break; |
| } |
| } |
| |
| void ResourceTracker::setDeletedFenceSync(gl::SyncID sync) |
| { |
| ASSERT(sync.value != 0); |
| if (mStartingFenceSyncs.find(sync) == mStartingFenceSyncs.end()) |
| { |
| // This is a fence sync created after MEC was initialized. Ignore it. |
| return; |
| } |
| |
| // In this case, the app is deleting a fence sync we started with, we need to regen on loop. |
| mFenceSyncsToRegen.insert(sync); |
| } |
| |
| void ResourceTracker::setModifiedDefaultUniform(gl::ShaderProgramID programID, |
| gl::UniformLocation location) |
| { |
| // Pull up or create the list of uniform locations for this program and mark one dirty |
| mDefaultUniformsToReset[programID].insert(location); |
| } |
| |
| void ResourceTracker::setDefaultUniformBaseLocation(gl::ShaderProgramID programID, |
| gl::UniformLocation location, |
| gl::UniformLocation baseLocation) |
| { |
| // Track the base location used to populate arrayed uniforms in Setup |
| mDefaultUniformBaseLocations[{programID, location}] = baseLocation; |
| } |
| |
| TrackedResource &ResourceTracker::getTrackedResource(gl::ContextID contextID, ResourceIDType type) |
| { |
| if (IsSharedObjectResource(type)) |
| { |
| // No need to index with context if shared |
| return mTrackedResourcesShared[static_cast<uint32_t>(type)]; |
| } |
| else |
| { |
| // For per-context objects, track the resource per-context |
| return mTrackedResourcesPerContext[contextID][static_cast<uint32_t>(type)]; |
| } |
| } |
| |
| void ResourceTracker::getContextIDs(std::set<gl::ContextID> &idsOut) |
| { |
| for (const auto &trackedResourceIterator : mTrackedResourcesPerContext) |
| { |
| gl::ContextID contextID = trackedResourceIterator.first; |
| idsOut.insert(contextID); |
| } |
| } |
| |
| void TrackedResource::setGennedResource(GLuint id) |
| { |
| if (mStartingResources.find(id) == mStartingResources.end()) |
| { |
| // This is a resource created after MEC was initialized, track it |
| mNewResources.insert(id); |
| } |
| else |
| { |
| // In this case, the app is genning a resource with starting ID after previously deleting it |
| ASSERT(mResourcesToRegen.find(id) != mResourcesToRegen.end()); |
| |
| // For this, we need to delete it again to recreate it. |
| mResourcesToDelete.insert(id); |
| } |
| } |
| |
| bool TrackedResource::resourceIsGenerated(GLuint id) |
| { |
| return mStartingResources.find(id) != mStartingResources.end() || |
| mNewResources.find(id) != mNewResources.end(); |
| } |
| |
| void TrackedResource::setDeletedResource(GLuint id) |
| { |
| if (id == 0) |
| { |
| // Ignore ID 0 |
| return; |
| } |
| |
| if (mNewResources.find(id) != mNewResources.end()) |
| { |
| // This is a resource created after MEC was initialized, just clear it, since there will be |
| // no actions required for it to return to starting state. |
| mNewResources.erase(id); |
| return; |
| } |
| |
| if (mStartingResources.find(id) != mStartingResources.end()) |
| { |
| // In this case, the app is deleting a resource we started with, we need to regen on loop |
| |
| // Mark that we don't need to delete this |
| mResourcesToDelete.erase(id); |
| |
| // Generate the resource again |
| mResourcesToRegen.insert(id); |
| |
| // Also restore its contents |
| mResourcesToRestore.insert(id); |
| } |
| |
| // If none of the above is true, the app is deleting a resource that was never genned. |
| } |
| |
| void TrackedResource::setModifiedResource(GLuint id) |
| { |
| // If this was a starting resource, we need to track it for restore |
| if (mStartingResources.find(id) != mStartingResources.end()) |
| { |
| mResourcesToRestore.insert(id); |
| } |
| } |
| |
| void ResourceTracker::setBufferMapped(gl::ContextID contextID, GLuint id) |
| { |
| // If this was a starting buffer, we may need to restore it to original state during Reset. |
| // Skip buffers that were deleted after the starting point. |
| const TrackedResource &trackedBuffers = getTrackedResource(contextID, ResourceIDType::Buffer); |
| const ResourceSet &startingBuffers = trackedBuffers.getStartingResources(); |
| const ResourceSet &buffersToRegen = trackedBuffers.getResourcesToRegen(); |
| if (startingBuffers.find(id) != startingBuffers.end() && |
| buffersToRegen.find(id) == buffersToRegen.end()) |
| { |
| // Track that its current state is mapped (true) |
| mStartingBuffersMappedCurrent[id] = true; |
| } |
| } |
| |
| void ResourceTracker::setBufferUnmapped(gl::ContextID contextID, GLuint id) |
| { |
| // If this was a starting buffer, we may need to restore it to original state during Reset. |
| // Skip buffers that were deleted after the starting point. |
| const TrackedResource &trackedBuffers = getTrackedResource(contextID, ResourceIDType::Buffer); |
| const ResourceSet &startingBuffers = trackedBuffers.getStartingResources(); |
| const ResourceSet &buffersToRegen = trackedBuffers.getResourcesToRegen(); |
| if (startingBuffers.find(id) != startingBuffers.end() && |
| buffersToRegen.find(id) == buffersToRegen.end()) |
| { |
| // Track that its current state is unmapped (false) |
| mStartingBuffersMappedCurrent[id] = false; |
| } |
| } |
| |
| bool ResourceTracker::getStartingBuffersMappedCurrent(GLuint id) const |
| { |
| const auto &foundBool = mStartingBuffersMappedCurrent.find(id); |
| ASSERT(foundBool != mStartingBuffersMappedCurrent.end()); |
| return foundBool->second; |
| } |
| |
| bool ResourceTracker::getStartingBuffersMappedInitial(GLuint id) const |
| { |
| const auto &foundBool = mStartingBuffersMappedInitial.find(id); |
| ASSERT(foundBool != mStartingBuffersMappedInitial.end()); |
| return foundBool->second; |
| } |
| |
| void ResourceTracker::onShaderProgramAccess(gl::ShaderProgramID shaderProgramID) |
| { |
| mMaxShaderPrograms = std::max(mMaxShaderPrograms, shaderProgramID.value + 1); |
| } |
| |
| // Serialize trace metadata into a JSON file. The JSON file will be named "trace_prefix.json". |
| // |
| // As of writing, it will have the format like so: |
| // { |
| // "TraceMetadata": |
| // { |
| // "AreClientArraysEnabled" : 1, "CaptureRevision" : 16631, "ConfigAlphaBits" : 8, |
| // "ConfigBlueBits" : 8, "ConfigDepthBits" : 24, "ConfigGreenBits" : 8, |
| // ... etc ... |
| void FrameCaptureShared::writeJSON(const gl::Context *context) |
| { |
| const gl::ContextID contextId = context->id(); |
| const SurfaceParams &surfaceParams = mDrawSurfaceParams.at(contextId); |
| const gl::State &glState = context->getState(); |
| const egl::Config *config = context->getConfig(); |
| const egl::AttributeMap &displayAttribs = context->getDisplay()->getAttributeMap(); |
| |
| unsigned int frameCount = getFrameCount(); |
| |
| JsonSerializer json; |
| json.startGroup("TraceMetadata"); |
| json.addScalar("CaptureRevision", GetANGLERevision()); |
| json.addScalar("ContextClientMajorVersion", context->getClientMajorVersion()); |
| json.addScalar("ContextClientMinorVersion", context->getClientMinorVersion()); |
| json.addHexValue("DisplayPlatformType", displayAttribs.getAsInt(EGL_PLATFORM_ANGLE_TYPE_ANGLE)); |
| json.addHexValue("DisplayDeviceType", |
| displayAttribs.getAsInt(EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE)); |
| json.addScalar("FrameStart", 1); |
| json.addScalar("FrameEnd", frameCount); |
| json.addScalar("DrawSurfaceWidth", surfaceParams.extents.width); |
| json.addScalar("DrawSurfaceHeight", surfaceParams.extents.height); |
| json.addHexValue("DrawSurfaceColorSpace", ToEGLenum(surfaceParams.colorSpace)); |
| if (config) |
| { |
| json.addScalar("ConfigRedBits", config->redSize); |
| json.addScalar("ConfigGreenBits", config->greenSize); |
| json.addScalar("ConfigBlueBits", config->blueSize); |
| json.addScalar("ConfigAlphaBits", config->alphaSize); |
| json.addScalar("ConfigDepthBits", config->depthSize); |
| json.addScalar("ConfigStencilBits", config->stencilSize); |
| } |
| else |
| { |
| json.addScalar("ConfigRedBits", EGL_DONT_CARE); |
| json.addScalar("ConfigGreenBits", EGL_DONT_CARE); |
| json.addScalar("ConfigBlueBits", EGL_DONT_CARE); |
| json.addScalar("ConfigAlphaBits", EGL_DONT_CARE); |
| json.addScalar("ConfigDepthBits", EGL_DONT_CARE); |
| json.addScalar("ConfigStencilBits", EGL_DONT_CARE); |
| } |
| json.addBool("IsBinaryDataCompressed", mCompression); |
| json.addBool("AreClientArraysEnabled", glState.areClientArraysEnabled()); |
| json.addBool("IsBindGeneratesResourcesEnabled", glState.isBindGeneratesResourceEnabled()); |
| json.addBool("IsWebGLCompatibilityEnabled", glState.isWebGL()); |
| json.addBool("IsRobustResourceInitEnabled", glState.isRobustResourceInitEnabled()); |
| json.endGroup(); |
| |
| { |
| const std::vector<std::string> &traceFiles = mReplayWriter.getAndResetWrittenFiles(); |
| json.addVectorOfStrings("TraceFiles", traceFiles); |
| } |
| |
| json.addScalar("WindowSurfaceContextID", contextId.value); |
| |
| { |
| std::stringstream jsonFileNameStream; |
| jsonFileNameStream << mOutDirectory << FmtCapturePrefix(kNoContextId, mCaptureLabel) |
| << ".json"; |
| std::string jsonFileName = jsonFileNameStream.str(); |
| |
| SaveFileHelper saveData(jsonFileName); |
| saveData.write(reinterpret_cast<const uint8_t *>(json.data()), json.length()); |
| } |
| } |
| |
| void FrameCaptureShared::writeCppReplayIndexFiles(const gl::Context *context, |
| bool writeResetContextCall) |
| { |
| // Ensure the last frame is written. This will no-op if the frame is already written. |
| mReplayWriter.saveFrame(); |
| |
| const gl::ContextID contextId = context->id(); |
| |
| { |
| std::stringstream header; |
| |
| header << "#pragma once\n"; |
| header << "\n"; |
| header << "#include <EGL/egl.h>\n"; |
| header << "#include <stdint.h>\n"; |
| |
| std::string includes = header.str(); |
| mReplayWriter.setHeaderPrologue(includes); |
| } |
| |
| { |
| std::stringstream source; |
| |
| source << "#include \"" << FmtCapturePrefix(contextId, mCaptureLabel) << ".h\"\n"; |
| source << "#include \"trace_fixture.h\"\n"; |
| source << "#include \"angle_trace_gl.h\"\n"; |
| |
| std::string sourcePrologue = source.str(); |
| mReplayWriter.setSourcePrologue(sourcePrologue); |
| } |
| |
| { |
| std::string proto = "void InitReplay(void)"; |
| |
| std::stringstream source; |
| source << proto << "\n"; |
| source << "{\n"; |
| WriteInitReplayCall(mCompression, source, context->id(), mCaptureLabel, |
| MaxClientArraySize(mClientArraySizes), mReadBufferSize, |
| mResourceIDBufferSize, mMaxAccessedResourceIDs); |
| source << "}\n"; |
| |
| mReplayWriter.addPrivateFunction(proto, std::stringstream(), source); |
| } |
| |
| { |
| std::string proto = "void ReplayFrame(uint32_t frameIndex)"; |
| |
| std::stringstream source; |
| |
| source << proto << "\n"; |
| source << "{\n"; |
| source << " switch (frameIndex)\n"; |
| source << " {\n"; |
| for (uint32_t frameIndex : mActiveFrameIndices) |
| { |
| source << " case " << frameIndex << ":\n"; |
| source << " " << FmtReplayFunction(contextId, FuncUsage::Call, frameIndex) |
| << ";\n"; |
| source << " break;\n"; |
| } |
| source << " default:\n"; |
| source << " break;\n"; |
| source << " }\n"; |
| source << "}\n"; |
| |
| mReplayWriter.addPublicFunction(proto, std::stringstream(), source); |
| } |
| |
| if (writeResetContextCall) |
| { |
| std::string proto = "void ResetReplay(void)"; |
| |
| std::stringstream source; |
| |
| source << proto << "\n"; |
| source << "{\n"; |
| source << " // Reset context is empty because context is destroyed before end " |
| "frame is reached\n"; |
| source << "}\n"; |
| |
| mReplayWriter.addPublicFunction(proto, std::stringstream(), source); |
| } |
| |
| if (mSerializeStateEnabled) |
| { |
| std::string proto = "const char *GetSerializedContextState(uint32_t frameIndex)"; |
| |
| std::stringstream source; |
| |
| source << proto << "\n"; |
| source << "{\n"; |
| source << " switch (frameIndex)\n"; |
| source << " {\n"; |
| for (uint32_t frameIndex = 1; frameIndex <= getFrameCount(); ++frameIndex) |
| { |
| source << " case " << frameIndex << ":\n"; |
| source << " return " |
| << FmtGetSerializedContextStateFunction(contextId, FuncUsage::Call, frameIndex) |
| << ";\n"; |
| } |
| source << " default:\n"; |
| source << " return NULL;\n"; |
| source << " }\n"; |
| source << "}\n"; |
| |
| mReplayWriter.addPublicFunction(proto, std::stringstream(), source); |
| } |
| |
| { |
| std::stringstream fnameStream; |
| fnameStream << mOutDirectory << FmtCapturePrefix(contextId, mCaptureLabel); |
| std::string fnamePattern = fnameStream.str(); |
| |
| mReplayWriter.setFilenamePattern(fnamePattern); |
| } |
| |
| mReplayWriter.saveIndexFilesAndHeader(); |
| |
| writeJSON(context); |
| } |
| |
| void FrameCaptureShared::writeMainContextCppReplay(const gl::Context *context, |
| const std::vector<CallCapture> &setupCalls, |
| StateResetHelper &stateResetHelper) |
| { |
| ASSERT(mWindowSurfaceContextID == context->id()); |
| |
| { |
| std::stringstream header; |
| |
| header << "#include \"" << FmtCapturePrefix(context->id(), mCaptureLabel) << ".h\"\n"; |
| header << "#include \"angle_trace_gl.h\"\n"; |
| |
| std::string headerString = header.str(); |
| mReplayWriter.setSourcePrologue(headerString); |
| } |
| |
| uint32_t frameCount = getFrameCount(); |
| uint32_t frameIndex = getReplayFrameIndex(); |
| |
| if (frameIndex == 1) |
| { |
| { |
| std::string proto = "void SetupReplay(void)"; |
| |
| std::stringstream out; |
| |
| out << proto << "\n"; |
| out << "{\n"; |
| |
| // Setup all of the shared objects. |
| out << " InitReplay();\n"; |
| if (usesMidExecutionCapture()) |
| { |
| out << " " << FmtSetupFunction(kNoPartId, kSharedContextId, FuncUsage::Call) |
| << ";\n"; |
| out << " " |
| << FmtSetupInactiveFunction(kNoPartId, kSharedContextId, FuncUsage::Call) |
| << "\n"; |
| // Make sure that the current context is mapped correctly |
| out << " SetCurrentContextID(" << context->id() << ");\n"; |
| } |
| |
| // Setup each of the auxiliary contexts. |
| egl::ShareGroup *shareGroup = context->getShareGroup(); |
| const egl::ContextMap &shareContextMap = shareGroup->getContexts(); |
| for (auto shareContext : shareContextMap) |
| { |
| if (shareContext.first == context->id().value) |
| { |
| if (usesMidExecutionCapture()) |
| { |
| // Setup the presentation (this) context first. |
| out << " " << FmtSetupFunction(kNoPartId, context->id(), FuncUsage::Call) |
| << ";\n"; |
| out << "\n"; |
| } |
| |
| continue; |
| } |
| |
| // The SetupReplayContextXX() calls only exist if this is a mid-execution capture |
| // and we can only call them if they exist, so only output the calls if this is a |
| // MEC. |
| if (usesMidExecutionCapture()) |
| { |
| // Only call SetupReplayContext for secondary contexts that were current before |
| // MEC started |
| if (mActiveContexts.find(shareContext.first) != mActiveContexts.end()) |
| { |
| // TODO(http://anglebug.com/42264418): Support capture/replay of |
| // eglCreateContext() so this block can be moved into SetupReplayContextXX() |
| // by injecting them into the beginning of the setup call stream. |
| out << " CreateContext(" << shareContext.first << ");\n"; |
| |
| out << " " |
| << FmtSetupFunction(kNoPartId, shareContext.second->id(), |
| FuncUsage::Call) |
| << ";\n"; |
| } |
| } |
| } |
| |
| // If there are other contexts that were initialized, we need to make the main context |
| // current again. |
| if (shareContextMap.size() > 1) |
| { |
| out << "\n"; |
| out << " eglMakeCurrent(NULL, NULL, NULL, gContextMap2[" << context->id() |
| << "]);\n"; |
| } |
| |
| out << "}\n"; |
| |
| mReplayWriter.addPublicFunction(proto, std::stringstream(), out); |
| } |
| } |
| |
| // Emit code to reset back to starting state |
| if (frameIndex == frameCount) |
| { |
| std::stringstream resetProtoStream; |
| std::stringstream resetHeaderStream; |
| std::stringstream resetBodyStream; |
| |
| resetProtoStream << "void ResetReplay(void)"; |
| |
| resetBodyStream << resetProtoStream.str() << "\n"; |
| resetBodyStream << "{\n"; |
| |
| // Grab the list of contexts to be reset |
| std::set<gl::ContextID> contextIDs; |
| mResourceTracker.getContextIDs(contextIDs); |
| |
| // TODO(http://anglebug.com/42264418): Look at moving this into the shared context file |
| // since it's resetting shared objects. |
| |
| // TODO(http://anglebug.com/42263204): Support function parts when writing Reset functions |
| |
| // Track whether anything was written during Reset |
| bool anyResourceReset = false; |
| |
| // Track whether we changed contexts during Reset |
| bool contextChanged = false; |
| |
| // First emit shared object reset, including opaque and context state |
| { |
| std::stringstream protoStream; |
| std::stringstream headerStream; |
| std::stringstream bodyStream; |
| |
| protoStream << "void " |
| << FmtResetFunction(kNoPartId, kSharedContextId, FuncUsage::Prototype); |
| bodyStream << protoStream.str() << "\n"; |
| bodyStream << "{\n"; |
| |
| for (ResourceIDType resourceType : AllEnums<ResourceIDType>()) |
| { |
| if (!IsSharedObjectResource(resourceType)) |
| { |
| continue; |
| } |
| // Use current context for shared reset |
| MaybeResetResources(context->getDisplay(), context->id(), resourceType, |
| mReplayWriter, bodyStream, headerStream, &mResourceTracker, |
| &mBinaryData, anyResourceReset, &mResourceIDBufferSize); |
| } |
| |
| // Reset opaque type objects that don't have IDs, so are not ResourceIDTypes. |
| MaybeResetOpaqueTypeObjects(mReplayWriter, bodyStream, headerStream, context, |
| &mResourceTracker, &mBinaryData, &mResourceIDBufferSize); |
| |
| bodyStream << "}\n"; |
| |
| mReplayWriter.addPrivateFunction(protoStream.str(), headerStream, bodyStream); |
| } |
| |
| // Emit the call to shared object reset |
| resetBodyStream << " " << FmtResetFunction(kNoPartId, kSharedContextId, FuncUsage::Call) |
| << ";\n"; |
| |
| // Reset our output tracker (Note: This was unused during shared reset) |
| anyResourceReset = false; |
| |
| // Walk through all contexts that need Reset |
| for (const gl::ContextID &contextID : contextIDs) |
| { |
| // Create a function to reset each context's non-shared objects |
| { |
| std::stringstream protoStream; |
| std::stringstream headerStream; |
| std::stringstream bodyStream; |
| |
| protoStream << "void " |
| << FmtResetFunction(kNoPartId, contextID, FuncUsage::Prototype); |
| bodyStream << protoStream.str() << "\n"; |
| bodyStream << "{\n"; |
| |
| // Build the Reset calls in a separate stream so we can insert before them |
| std::stringstream resetStream; |
| |
| for (ResourceIDType resourceType : AllEnums<ResourceIDType>()) |
| { |
| if (IsSharedObjectResource(resourceType)) |
| { |
| continue; |
| } |
| MaybeResetResources(context->getDisplay(), contextID, resourceType, |
| mReplayWriter, resetStream, headerStream, &mResourceTracker, |
| &mBinaryData, anyResourceReset, &mResourceIDBufferSize); |
| } |
| |
| // Only call eglMakeCurrent if anything was actually reset in the function and the |
| // context differs from current |
| if (anyResourceReset && contextID != context->id()) |
| { |
| contextChanged = true; |
| bodyStream << " eglMakeCurrent(NULL, NULL, NULL, gContextMap2[" |
| << contextID.value << "]);\n\n"; |
| } |
| |
| // Then append the Reset calls |
| bodyStream << resetStream.str(); |
| |
| bodyStream << "}\n"; |
| mReplayWriter.addPrivateFunction(protoStream.str(), headerStream, bodyStream); |
| } |
| |
| // Emit a call to reset each context's non-shared objects |
| resetBodyStream << " " << FmtResetFunction(kNoPartId, contextID, FuncUsage::Call) |
| << ";\n"; |
| } |
| |
| // Bind the main context again if we bound any additional contexts |
| if (contextChanged) |
| { |
| resetBodyStream << " eglMakeCurrent(NULL, NULL, NULL, gContextMap2[" |
| << context->id().value << "]);\n"; |
| } |
| |
| // Now that we're back on the main context, reset any additional state |
| resetBodyStream << "\n // Reset main context state\n"; |
| MaybeResetContextState(mReplayWriter, resetBodyStream, resetHeaderStream, &mResourceTracker, |
| context, &mBinaryData, stateResetHelper, &mResourceIDBufferSize); |
| |
| resetBodyStream << "}\n"; |
| |
| mReplayWriter.addPublicFunction(resetProtoStream.str(), resetHeaderStream, resetBodyStream); |
| } |
| |
| if (!mFrameCalls.empty()) |
| { |
| std::stringstream protoStream; |
| protoStream << "void " |
| << FmtReplayFunction(context->id(), FuncUsage::Prototype, frameIndex); |
| std::string proto = protoStream.str(); |
| std::stringstream headerStream; |
| std::stringstream bodyStream; |
| |
| if (context->getShareGroup()->getContexts().size() > 1) |
| { |
| // Only ReplayFunc::Replay trace file output functions are affected by multi-context |
| // call grouping so they can safely be special-cased here. |
| WriteCppReplayFunctionWithPartsMultiContext( |
| context->id(), ReplayFunc::Replay, mReplayWriter, frameIndex, &mBinaryData, |
| mFrameCalls, headerStream, bodyStream, &mResourceIDBufferSize); |
| } |
| else |
| { |
| WriteCppReplayFunctionWithParts(context->id(), ReplayFunc::Replay, mReplayWriter, |
| frameIndex, &mBinaryData, mFrameCalls, headerStream, |
| bodyStream, &mResourceIDBufferSize); |
| } |
| mReplayWriter.addPrivateFunction(proto, headerStream, bodyStream); |
| } |
| |
| if (mSerializeStateEnabled) |
| { |
| std::string serializedContextString; |
| if (SerializeContextToString(const_cast<gl::Context *>(context), |
| &serializedContextString) == Result::Continue) |
| { |
| std::stringstream protoStream; |
| protoStream << "const char *" |
| << FmtGetSerializedContextStateFunction(context->id(), FuncUsage::Prototype, |
| frameIndex); |
| std::string proto = protoStream.str(); |
| |
| std::stringstream bodyStream; |
| bodyStream << proto << "\n"; |
| bodyStream << "{\n"; |
| bodyStream << " return " << FmtMultiLineString(serializedContextString) << ";\n"; |
| bodyStream << "}\n"; |
| |
| mReplayWriter.addPrivateFunction(proto, std::stringstream(), bodyStream); |
| } |
| } |
| |
| { |
| std::stringstream fnamePatternStream; |
| fnamePatternStream << mOutDirectory << FmtCapturePrefix(context->id(), mCaptureLabel); |
| std::string fnamePattern = fnamePatternStream.str(); |
| |
| mReplayWriter.setFilenamePattern(fnamePattern); |
| } |
| |
| if (mFrameIndex == mCaptureEndFrame) |
| { |
| mReplayWriter.saveFrame(); |
| } |
| else |
| { |
| mReplayWriter.saveFrameIfFull(); |
| } |
| } |
| |
| const std::string &FrameCaptureShared::getShaderSource(gl::ShaderProgramID id) const |
| { |
| const auto &foundSources = mCachedShaderSource.find(id); |
| ASSERT(foundSources != mCachedShaderSource.end()); |
| return foundSources->second; |
| } |
| |
| void FrameCaptureShared::setShaderSource(gl::ShaderProgramID id, std::string source) |
| { |
| mCachedShaderSource[id] = source; |
| } |
| |
| const ProgramSources &FrameCaptureShared::getProgramSources(gl::ShaderProgramID id) const |
| { |
| const auto &foundSources = mCachedProgramSources.find(id); |
| ASSERT(foundSources != mCachedProgramSources.end()); |
| return foundSources->second; |
| } |
| |
| void FrameCaptureShared::setProgramSources(gl::ShaderProgramID id, ProgramSources sources) |
| { |
| mCachedProgramSources[id] = sources; |
| } |
| |
| void FrameCaptureShared::markResourceSetupCallsInactive(std::vector<CallCapture> *setupCalls, |
| ResourceIDType type, |
| GLuint id, |
| gl::Range<size_t> range) |
| { |
| ASSERT(mResourceIDToSetupCalls[type].find(id) == mResourceIDToSetupCalls[type].end()); |
| |
| // Mark all of the calls that were used to initialize this resource as INACTIVE |
| for (size_t index : range) |
| { |
| (*setupCalls)[index].isActive = false; |
| } |
| |
| mResourceIDToSetupCalls[type][id] = range; |
| } |
| |
| void CaptureStringLimit(const GLchar *str, uint32_t limit, ParamCapture *paramCapture) |
| { |
| // Write the incoming string up to limit, including null terminator |
| size_t length = strlen(str) + 1; |
| |
| if (length > limit) |
| { |
| // If too many characters, resize the string to fit in the limit |
| std::string newStr = str; |
| newStr.resize(limit - 1); |
| CaptureString(newStr.c_str(), paramCapture); |
| } |
| else |
| { |
| CaptureMemory(str, length, paramCapture); |
| } |
| } |
| |
| void CaptureVertexPointerGLES1(const gl::State &glState, |
| gl::ClientVertexArrayType type, |
| const void *pointer, |
| ParamCapture *paramCapture) |
| { |
| paramCapture->value.voidConstPointerVal = pointer; |
| if (!glState.getTargetBuffer(gl::BufferBinding::Array)) |
| { |
| paramCapture->arrayClientPointerIndex = |
| gl::GLES1Renderer::VertexArrayIndex(type, glState.gles1()); |
| } |
| } |
| |
| gl::Program *GetProgramForCapture(const gl::State &glState, gl::ShaderProgramID handle) |
| { |
| gl::Program *program = glState.getShaderProgramManagerForCapture().getProgram(handle); |
| return program; |
| } |
| |
| void CaptureGetActiveUniformBlockivParameters(const gl::State &glState, |
| gl::ShaderProgramID handle, |
| gl::UniformBlockIndex uniformBlockIndex, |
| GLenum pname, |
| ParamCapture *paramCapture) |
| { |
| int numParams = 1; |
| |
| // From the OpenGL ES 3.0 spec: |
| // If pname is UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, then a list of the |
| // active uniform indices for the uniform block identified by uniformBlockIndex is |
| // returned. The number of elements that will be written to params is the value of |
| // UNIFORM_BLOCK_ACTIVE_UNIFORMS for uniformBlockIndex |
| if (pname == GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES) |
| { |
| gl::Program *program = GetProgramForCapture(glState, handle); |
| if (program) |
| { |
| gl::QueryActiveUniformBlockiv(program, uniformBlockIndex, |
| GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &numParams); |
| } |
| } |
| |
| paramCapture->readBufferSizeBytes = sizeof(GLint) * numParams; |
| } |
| |
| void CaptureGetParameter(const gl::State &glState, |
| GLenum pname, |
| size_t typeSize, |
| ParamCapture *paramCapture) |
| { |
| // kMaxReportedCapabilities is the biggest array we'll need to hold data from glGet calls. |
| // This value needs to be updated if any new extensions are introduced that would allow for |
| // more compressed texture formats. The current value is taken from: |
| // http://opengles.gpuinfo.org/displaycapability.php?name=GL_NUM_COMPRESSED_TEXTURE_FORMATS&esversion=2 |
| constexpr unsigned int kMaxReportedCapabilities = 69; |
| paramCapture->readBufferSizeBytes = typeSize * kMaxReportedCapabilities; |
| } |
| |
| void CaptureGenHandlesImpl(GLsizei n, GLuint *handles, ParamCapture *paramCapture) |
| { |
| paramCapture->readBufferSizeBytes = sizeof(GLuint) * n; |
| CaptureMemory(handles, paramCapture->readBufferSizeBytes, paramCapture); |
| } |
| |
| void CaptureShaderStrings(GLsizei count, |
| const GLchar *const *strings, |
| const GLint *length, |
| ParamCapture *paramCapture) |
| { |
| // Concat the array elements of the string into one data vector, |
| // append the terminating zero and use this as the captured shader |
| // string. The string count and the length array are adjusted |
| // accordingly in the capture post-processing |
| |
| std::vector<uint8_t> data; |
| size_t offset = 0; |
| for (GLsizei index = 0; index < count; ++index) |
| { |
| size_t len = ((length && length[index] >= 0) ? length[index] : strlen(strings[index])); |
| |
| // Count trailing zeros |
| uint32_t i = 1; |
| while (i < len && strings[index][len - i] == 0) |
| { |
| i++; |
| } |
| |
| // Don't copy trailing zeros |
| len -= (i - 1); |
| |
| data.resize(offset + len); |
| std::copy(strings[index], strings[index] + len, data.begin() + offset); |
| offset += len; |
| } |
| |
| data.push_back(0); |
| paramCapture->data.emplace_back(std::move(data)); |
| } |
| |
| } // namespace angle |
| |
| namespace egl |
| { |
| angle::ParamCapture CaptureAttributeMap(const egl::AttributeMap &attribMap) |
| { |
| switch (attribMap.getType()) |
| { |
| case AttributeMapType::Attrib: |
| { |
| angle::ParamCapture paramCapture("attrib_list", angle::ParamType::TEGLAttribPointer); |
| if (attribMap.isEmpty()) |
| { |
| paramCapture.value.EGLAttribPointerVal = nullptr; |
| } |
| else |
| { |
| std::vector<EGLAttrib> attribs; |
| for (const auto &[key, value] : attribMap) |
| { |
| attribs.push_back(key); |
| attribs.push_back(value); |
| } |
| attribs.push_back(EGL_NONE); |
| |
| angle::CaptureMemory(attribs.data(), attribs.size() * sizeof(EGLAttrib), |
| ¶mCapture); |
| } |
| return paramCapture; |
| } |
| |
| case AttributeMapType::Int: |
| { |
| angle::ParamCapture paramCapture("attrib_list", angle::ParamType::TEGLintPointer); |
| if (attribMap.isEmpty()) |
| { |
| paramCapture.value.EGLintPointerVal = nullptr; |
| } |
| else |
| { |
| std::vector<EGLint> attribs; |
| for (const auto &[key, value] : attribMap) |
| { |
| attribs.push_back(static_cast<EGLint>(key)); |
| attribs.push_back(static_cast<EGLint>(value)); |
| } |
| attribs.push_back(EGL_NONE); |
| |
| angle::CaptureMemory(attribs.data(), attribs.size() * sizeof(EGLint), |
| ¶mCapture); |
| } |
| return paramCapture; |
| } |
| |
| default: |
| UNREACHABLE(); |
| return angle::ParamCapture(); |
| } |
| } |
| } // namespace egl |