diff --git a/Common/GPU/OpenGL/GLFrameData.cpp b/Common/GPU/OpenGL/GLFrameData.cpp index fa5a051d30..91300da9d5 100644 --- a/Common/GPU/OpenGL/GLFrameData.cpp +++ b/Common/GPU/OpenGL/GLFrameData.cpp @@ -3,6 +3,21 @@ #include "Common/GPU/OpenGL/GLRenderManager.h" #include "Common/Log.h" +void GLCachedReadback::Destroy(bool skipGLCalls) { + if (buffer && !skipGLCalls) { + glDeleteBuffers(1, &buffer); + } + buffer = 0; +} + +void GLFrameData::Destroy(bool skipGLCalls) { + readbacks_.IterateMut([=](const GLReadbackKey &key, GLCachedReadback *value) { + value->Destroy(skipGLCalls); + delete value; + }); + readbacks_.Clear(); +} + void GLDeleter::Take(GLDeleter &other) { _assert_msg_(IsEmpty(), "Deleter already has stuff"); shaders = std::move(other.shaders); diff --git a/Common/GPU/OpenGL/GLFrameData.h b/Common/GPU/OpenGL/GLFrameData.h index a50fbe7d55..3dc5487162 100644 --- a/Common/GPU/OpenGL/GLFrameData.h +++ b/Common/GPU/OpenGL/GLFrameData.h @@ -6,6 +6,7 @@ #include #include "Common/GPU/OpenGL/GLCommon.h" +#include "Common/Data/Collections/Hashmaps.h" class GLRShader; class GLRBuffer; @@ -35,8 +36,24 @@ public: std::vector pushBuffers; }; +// TODO: To be safe, should probably add some more stuff here, like format and even readback count, maybe. +struct GLReadbackKey { + const GLRFramebuffer *framebuf; + int width; + int height; +}; + +struct GLCachedReadback { + GLuint buffer; // PBO + size_t bufferSize; + + void Destroy(bool skipGLCalls); +}; + // Per-frame data, round-robin so we can overlap submission with execution of the previous frame. struct GLFrameData { + GLFrameData() : readbacks_(8) {} + bool skipSwap = false; std::mutex fenceMutex; @@ -49,4 +66,8 @@ struct GLFrameData { GLDeleter deleter; GLDeleter deleter_prev; std::set activePushBuffers; + + DenseHashMap readbacks_; + + void Destroy(bool skipGLCalls); }; diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp index b10caa2541..e6766868da 100644 --- a/Common/GPU/OpenGL/GLRenderManager.cpp +++ b/Common/GPU/OpenGL/GLRenderManager.cpp @@ -47,7 +47,9 @@ GLRenderManager::~GLRenderManager() { for (int i = 0; i < MAX_INFLIGHT_FRAMES; i++) { _assert_(frameData_[i].deleter.IsEmpty()); _assert_(frameData_[i].deleter_prev.IsEmpty()); + frameData_[i].Destroy(skipGLCalls_); } + // Was anything deleted during shutdown? deleter_.Perform(this, skipGLCalls_); _assert_(deleter_.IsEmpty()); @@ -108,6 +110,7 @@ void GLRenderManager::ThreadEnd() { // Since we're in shutdown, we should skip the GL calls on Android. frameData_[i].deleter.Perform(this, skipGLCalls_); frameData_[i].deleter_prev.Perform(this, skipGLCalls_); + frameData_[i].Destroy(skipGLCalls_); } deleter_.Perform(this, skipGLCalls_); for (int i = 0; i < (int)steps_.size(); i++) { @@ -292,6 +295,8 @@ void GLRenderManager::BlitFramebuffer(GLRFramebuffer *src, GLRect2D srcRect, GLR bool GLRenderManager::CopyFramebufferToMemory(GLRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, Draw::ReadbackMode mode, const char *tag) { _assert_(pixels); + curRenderStep_ = nullptr; + GLRStep *step = new GLRStep{ GLRStepType::READBACK }; step->readback.src = src; step->readback.srcRect = { x, y, w, h }; @@ -301,8 +306,9 @@ bool GLRenderManager::CopyFramebufferToMemory(GLRFramebuffer *src, int aspectBit step->tag = tag; steps_.push_back(step); - curRenderStep_ = nullptr; - FlushSync(); + if (mode == Draw::ReadbackMode::BLOCK) { + FlushSync(); + } Draw::DataFormat srcFormat; if (aspectBits & GL_COLOR_BUFFER_BIT) { @@ -316,13 +322,17 @@ bool GLRenderManager::CopyFramebufferToMemory(GLRFramebuffer *src, int aspectBit } else { return false; } - queueRunner_.CopyFromReadbackBuffer(src, w, h, srcFormat, destFormat, pixelStride, pixels); + + queueRunner_.CopyFromReadbackBuffer(mode == Draw::ReadbackMode::OLD_DATA_OK ? src : nullptr, w, h, srcFormat, destFormat, pixelStride, pixels); return true; } void GLRenderManager::CopyImageToMemorySync(GLRTexture *texture, int mipLevel, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride, const char *tag) { _assert_(texture); _assert_(pixels); + + curRenderStep_ = nullptr; + GLRStep *step = new GLRStep{ GLRStepType::READBACK_IMAGE }; step->readback_image.texture = texture; step->readback_image.mipLevel = mipLevel; @@ -330,7 +340,6 @@ void GLRenderManager::CopyImageToMemorySync(GLRTexture *texture, int mipLevel, i step->tag = tag; steps_.push_back(step); - curRenderStep_ = nullptr; FlushSync(); queueRunner_.CopyFromReadbackBuffer(nullptr, w, h, Draw::DataFormat::R8G8B8A8_UNORM, destFormat, pixelStride, pixels); diff --git a/Common/GPU/Vulkan/VulkanFrameData.h b/Common/GPU/Vulkan/VulkanFrameData.h index 0e1344f24e..2d27f1c2d1 100644 --- a/Common/GPU/Vulkan/VulkanFrameData.h +++ b/Common/GPU/Vulkan/VulkanFrameData.h @@ -28,6 +28,7 @@ struct QueueProfileContext { class VKRFramebuffer; +// TODO: To be safe, should probably add some more stuff here, like format and even readback count, maybe. struct ReadbackKey { const VKRFramebuffer *framebuf; int width; diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 3db2d7b8db..e355ebbc91 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -581,9 +581,10 @@ struct DeviceCaps { bool multiViewSupported; bool isTilingGPU; // This means that it benefits from correct store-ops, msaa without backing memory, etc. bool sampleRateShadingSupported; - bool verySlowShaderCompiler; + bool pboSupported; // Only makes sense in OpenGL. + // From the other backends, we can detect if D3D9 support is known bad (like on Xe) and disable it. bool supportsD3D9;