diff --git a/Common/GPU/OpenGL/GLFrameData.cpp b/Common/GPU/OpenGL/GLFrameData.cpp index 91300da9d5..a96cad7801 100644 --- a/Common/GPU/OpenGL/GLFrameData.cpp +++ b/Common/GPU/OpenGL/GLFrameData.cpp @@ -1,6 +1,7 @@ #include "Common/GPU/OpenGL/GLCommon.h" #include "Common/GPU/OpenGL/GLFrameData.h" #include "Common/GPU/OpenGL/GLRenderManager.h" +#include "Common/GPU/DataFormat.h" #include "Common/Log.h" void GLCachedReadback::Destroy(bool skipGLCalls) { @@ -10,11 +11,57 @@ void GLCachedReadback::Destroy(bool skipGLCalls) { buffer = 0; } +void GLFrameData::PerformReadbacks() { + // TODO: Shorten the lock by doing some queueing tricks here. + std::lock_guard guard(readbackMutex); + readbacks_.IterateMut([=](const GLReadbackKey &key, GLCachedReadback *cached) { + if (!cached->pending) { + return; + } + + glBindBuffer(GL_PIXEL_PACK_BUFFER, cached->buffer); + GLubyte *ptr = (GLubyte *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY); + if (!ptr) { + int error = glGetError(); + ERROR_LOG(G3D, "mapbuffer error: error %d buffer %d (%dx%d)", error, cached->buffer, key.width, key.height); + cached->pending = false; + return; + } + int bpp = (int)Draw::DataFormatSizeInBytes(key.dstFormat); + int dataSize = key.width * key.height * bpp; + _dbg_assert_(dataSize != 0); + if (cached->dataSize < dataSize) { + delete[] cached->data; + cached->data = new uint8_t[dataSize]; + cached->dataSize = dataSize; + } + int pixelStride = key.width; + if (cached->convert) { + Draw::ConvertFromRGBA8888(cached->data, ptr, pixelStride, pixelStride, key.width, key.height, key.dstFormat); + } else { + for (int y = 0; y < key.height; y++) { + memcpy(cached->data + y * pixelStride * bpp, ptr + y * key.width * bpp, key.width * bpp); + } + } + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + cached->pending = false; + }); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); +} + +void GLFrameData::EndFrame() { + for (auto &rb : queuedReadbacks_) { + // What should we do here? cleanup? + } + queuedReadbacks_.clear(); +} + void GLFrameData::Destroy(bool skipGLCalls) { - readbacks_.IterateMut([=](const GLReadbackKey &key, GLCachedReadback *value) { - value->Destroy(skipGLCalls); - delete value; - }); + std::lock_guard guard(readbackMutex); + readbacks_.IterateMut([=](const GLReadbackKey &key, GLCachedReadback *cached) { + cached->Destroy(skipGLCalls); + delete cached; + }); readbacks_.Clear(); } diff --git a/Common/GPU/OpenGL/GLFrameData.h b/Common/GPU/OpenGL/GLFrameData.h index 3dc5487162..af591ec879 100644 --- a/Common/GPU/OpenGL/GLFrameData.h +++ b/Common/GPU/OpenGL/GLFrameData.h @@ -7,6 +7,7 @@ #include "Common/GPU/OpenGL/GLCommon.h" #include "Common/Data/Collections/Hashmaps.h" +#include "Common/GPU/thin3d.h" class GLRShader; class GLRBuffer; @@ -39,6 +40,7 @@ public: // TODO: To be safe, should probably add some more stuff here, like format and even readback count, maybe. struct GLReadbackKey { const GLRFramebuffer *framebuf; + Draw::DataFormat dstFormat; int width; int height; }; @@ -47,9 +49,21 @@ struct GLCachedReadback { GLuint buffer; // PBO size_t bufferSize; + // pending data + uint8_t *data; + size_t dataSize; + + bool pending; + bool convert; + void Destroy(bool skipGLCalls); }; +// These are transformed to GLCachedReadback at the end of the frame. +struct GLQueuedReadback { + +}; + // Per-frame data, round-robin so we can overlap submission with execution of the previous frame. struct GLFrameData { GLFrameData() : readbacks_(8) {} @@ -67,7 +81,11 @@ struct GLFrameData { GLDeleter deleter_prev; std::set activePushBuffers; + std::mutex readbackMutex; DenseHashMap readbacks_; + std::vector queuedReadbacks_; + void PerformReadbacks(); + void EndFrame(); void Destroy(bool skipGLCalls); }; diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index 4908fcd12d..d8f4ea9ef1 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -122,7 +122,7 @@ std::string GLQueueRunner::GetStereoBufferLayout(const char *uniformName) { else return "undefined"; } -void GLQueueRunner::RunInitSteps(const std::vector &steps, bool skipGLCalls) { +void GLQueueRunner::RunInitSteps(const std::vector &steps, GLFrameData &frameData, bool skipGLCalls) { if (skipGLCalls) { // Some bookkeeping still needs to be done. for (size_t i = 0; i < steps.size(); i++) { @@ -646,7 +646,7 @@ retry_depth: currentReadHandle_ = fbo->handle; } -void GLQueueRunner::RunSteps(const std::vector &steps, bool skipGLCalls, bool keepSteps, bool useVR) { +void GLQueueRunner::RunSteps(const std::vector &steps, GLFrameData &frameData, bool skipGLCalls, bool keepSteps, bool useVR) { if (skipGLCalls) { if (keepSteps) { return; @@ -720,7 +720,7 @@ void GLQueueRunner::RunSteps(const std::vector &steps, bool skipGLCal PerformBlit(step); break; case GLRStepType::READBACK: - PerformReadback(step); + PerformReadback(step, frameData); break; case GLRStepType::READBACK_IMAGE: PerformReadbackImage(step); @@ -1463,11 +1463,11 @@ void GLQueueRunner::PerformCopy(const GLRStep &step) { CHECK_GL_ERROR_IF_DEBUG(); } -void GLQueueRunner::PerformReadback(const GLRStep &pass) { +void GLQueueRunner::PerformReadback(const GLRStep &step, GLFrameData &frameData) { using namespace Draw; CHECK_GL_ERROR_IF_DEBUG(); - GLRFramebuffer *fb = pass.readback.src; + GLRFramebuffer *fb = step.readback.src; fbo_bind_fb_target(true, fb ? fb->handle : 0); @@ -1483,20 +1483,20 @@ void GLQueueRunner::PerformReadback(const GLRStep &pass) { int srcAlignment = 4; #ifndef USING_GLES2 - if (pass.readback.aspectMask & GL_DEPTH_BUFFER_BIT) { + if (step.readback.aspectMask & GL_DEPTH_BUFFER_BIT) { format = GL_DEPTH_COMPONENT; type = GL_FLOAT; srcAlignment = 4; - } else if (pass.readback.aspectMask & GL_STENCIL_BUFFER_BIT) { + } else if (step.readback.aspectMask & GL_STENCIL_BUFFER_BIT) { format = GL_STENCIL_INDEX; type = GL_UNSIGNED_BYTE; srcAlignment = 1; } #endif - readbackAspectMask_ = pass.readback.aspectMask; + readbackAspectMask_ = step.readback.aspectMask; - int pixelStride = pass.readback.srcRect.w; + int pixelStride = step.readback.srcRect.w; // Apply the correct alignment. glPixelStorei(GL_PACK_ALIGNMENT, srcAlignment); if (!gl_extensions.IsGLES || gl_extensions.GLES3) { @@ -1504,22 +1504,65 @@ void GLQueueRunner::PerformReadback(const GLRStep &pass) { glPixelStorei(GL_PACK_ROW_LENGTH, pixelStride); } - GLRect2D rect = pass.readback.srcRect; - + GLRect2D rect = step.readback.srcRect; int readbackSize = srcAlignment * rect.w * rect.h; - if (readbackSize > readbackBufferSize_) { - delete[] readbackBuffer_; - readbackBuffer_ = new uint8_t[readbackSize]; - readbackBufferSize_ = readbackSize; - } - glReadPixels(rect.x, rect.y, rect.w, rect.h, format, type, readbackBuffer_); - #ifdef DEBUG_READ_PIXELS + uint8_t *readbackBuffer = nullptr; + + if (step.readback.delayed) { + GLReadbackKey key; + key.framebuf = step.readback.src; + key.width = step.readback.srcRect.w; + key.height = step.readback.srcRect.h; + key.dstFormat = step.readback.dstFormat; + + // See if there's already a buffer we can reuse + GLCachedReadback *cached; + { + std::lock_guard lock(frameData.readbackMutex); + cached = frameData.readbacks_.Get(key); + if (!cached) { + cached = new GLCachedReadback(); + cached->bufferSize = 0; + frameData.readbacks_.Insert(key, cached); + } + } + + if (cached->bufferSize < readbackSize) { + cached->bufferSize = readbackSize; + if (cached->buffer) { + glDeleteBuffers(1, &cached->buffer); + } + glGenBuffers(1, &cached->buffer); + glBindBuffer(GL_PIXEL_PACK_BUFFER, cached->buffer); + glBufferData(GL_PIXEL_PACK_BUFFER, readbackSize, nullptr, GL_STREAM_READ); + _assert_(glGetError() == 0); + } else { + glBindBuffer(GL_PIXEL_PACK_BUFFER, cached->buffer); + } + cached->pending = true; + } else { + // Just do a plain blocking read without involving PBOs. + if (readbackSize > readbackBufferSize_) { + delete[] readbackBuffer_; + readbackBuffer_ = new uint8_t[readbackSize]; + readbackBufferSize_ = readbackSize; + } + readbackBuffer = readbackBuffer_; + } + glReadPixels(rect.x, rect.y, rect.w, rect.h, format, type, readbackBuffer); + _assert_(glGetError() == 0); + +#ifdef DEBUG_READ_PIXELS LogReadPixelsError(glGetError()); #endif if (!gl_extensions.IsGLES || gl_extensions.GLES3) { glPixelStorei(GL_PACK_ROW_LENGTH, 0); } + + if (step.readback.delayed) { + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } CHECK_GL_ERROR_IF_DEBUG(); } @@ -1599,15 +1642,8 @@ void GLQueueRunner::PerformBindFramebufferAsRenderTarget(const GLRStep &pass) { CHECK_GL_ERROR_IF_DEBUG(); } -void GLQueueRunner::CopyFromReadbackBuffer(GLRFramebuffer *framebuffer, int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels) { - // TODO: Maybe move data format conversion here, and always read back 8888. Drivers - // don't usually provide very optimized conversion implementations, though some do. - // Just need to be careful about dithering, which may break Danganronpa. +bool GLQueueRunner::CopyFromReadbackBuffer(GLFrameData &frameData, GLRFramebuffer *src, int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels) { int bpp = (int)Draw::DataFormatSizeInBytes(destFormat); - if (!readbackBuffer_ || bpp <= 0 || !pixels) { - // Something went wrong during the read and no readback buffer was allocated, probably. - return; - } // Always read back in 8888 format for the color aspect. GLuint internalFormat = GL_RGBA; @@ -1618,16 +1654,51 @@ void GLQueueRunner::CopyFromReadbackBuffer(GLRFramebuffer *framebuffer, int widt internalFormat = GL_STENCIL_INDEX; } #endif - bool convert = internalFormat == GL_RGBA && destFormat != Draw::DataFormat::R8G8B8A8_UNORM; - if (convert) { - // srcStride is width because we read back "packed" (with no gaps) from GL. - ConvertFromRGBA8888(pixels, readbackBuffer_, pixelStride, width, width, height, destFormat); - } else { - for (int y = 0; y < height; y++) { - memcpy(pixels + y * pixelStride * bpp, readbackBuffer_ + y * width * bpp, width * bpp); + + + if (!src) { + // This path is trivial and doesn't make use of PBOs or anything, since the full blocking read happened + // in PerformReadbackImage. + + // TODO: Maybe move data format conversion here, and always read back 8888. Drivers + // don't usually provide very optimized conversion implementations, though some do. + // Just need to be careful about dithering, which may break Danganronpa. + if (!readbackBuffer_ || bpp <= 0 || !pixels) { + // Something went wrong during the read and no readback buffer was allocated, probably. + return false; } + if (convert) { + ConvertFromRGBA8888(pixels, readbackBuffer_, pixelStride, width, width, height, destFormat); + } else { + for (int y = 0; y < height; y++) { + memcpy(pixels + y * pixelStride * bpp, readbackBuffer_ + y * width * bpp, width * bpp); + } + } + return true; } + + // OK, we're reading back from cache. Pretty simple. + GLReadbackKey key; + key.framebuf = src; + key.width = width; + key.height = height; + key.dstFormat = destFormat; + GLCachedReadback *cached = frameData.readbacks_.Get(key); + if (!cached) { + // Didn't have a cached image ready yet. Should we write black or white instead maybe? + return false; + } + if (cached->pending) { + INFO_LOG(G3D, "Trying to read back still pending image, ignoring"); + return true; + } + // We already performed the actual readback at the beginning of the frame. Now time to do the copy. + // We delay it to here for safety, although it would probably be alright to perform the full thing at the start + // of the frame in most cases. + _assert_(cached->data); + memcpy(pixels, cached->data, width * height * bpp); + return true; } GLuint GLQueueRunner::AllocTextureName() { diff --git a/Common/GPU/OpenGL/GLQueueRunner.h b/Common/GPU/OpenGL/GLQueueRunner.h index 9ef6614bfc..5a43870aea 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.h +++ b/Common/GPU/OpenGL/GLQueueRunner.h @@ -337,6 +337,7 @@ struct GLRStep { GLRFramebuffer *src; GLRect2D srcRect; Draw::DataFormat dstFormat; + bool delayed; } readback; struct { GLRTexture *texture; @@ -362,9 +363,9 @@ public: int GetStereoBufferIndex(const char *uniformName); std::string GetStereoBufferLayout(const char *uniformName); - void RunInitSteps(const std::vector &steps, bool skipGLCalls); + void RunInitSteps(const std::vector &steps, GLFrameData &frameData, bool skipGLCalls); - void RunSteps(const std::vector &steps, bool skipGLCalls, bool keepSteps, bool useVR); + void RunSteps(const std::vector &steps, GLFrameData &frameData, bool skipGLCalls, bool keepSteps, bool useVR); void LogSteps(const std::vector &steps); void CreateDeviceObjects(); @@ -374,7 +375,7 @@ public: return (int)depth * 3 + (int)color; } - void CopyFromReadbackBuffer(GLRFramebuffer *framebuffer, int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels); + bool CopyFromReadbackBuffer(GLFrameData &frameData, GLRFramebuffer *framebuffer, int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels); void Resize(int width, int height) { targetWidth_ = width; @@ -397,7 +398,7 @@ private: void PerformRenderPass(const GLRStep &pass, bool first, bool last); void PerformCopy(const GLRStep &pass); void PerformBlit(const GLRStep &pass); - void PerformReadback(const GLRStep &pass); + void PerformReadback(const GLRStep &pass, GLFrameData &frameData); void PerformReadbackImage(const GLRStep &pass); void fbo_ext_create(const GLRInitStep &step); diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp index e6766868da..8927be9db6 100644 --- a/Common/GPU/OpenGL/GLRenderManager.cpp +++ b/Common/GPU/OpenGL/GLRenderManager.cpp @@ -302,6 +302,7 @@ bool GLRenderManager::CopyFramebufferToMemory(GLRFramebuffer *src, int aspectBit step->readback.srcRect = { x, y, w, h }; step->readback.aspectMask = aspectBits; step->readback.dstFormat = destFormat; + step->readback.delayed = mode == Draw::ReadbackMode::OLD_DATA_OK; step->dependencies.insert(src); step->tag = tag; steps_.push_back(step); @@ -323,7 +324,9 @@ bool GLRenderManager::CopyFramebufferToMemory(GLRFramebuffer *src, int aspectBit return false; } - queueRunner_.CopyFromReadbackBuffer(mode == Draw::ReadbackMode::OLD_DATA_OK ? src : nullptr, w, h, srcFormat, destFormat, pixelStride, pixels); + // If non-blocking, we're really copying here from the images this frameData_ collected on the + // last time around the loop. + queueRunner_.CopyFromReadbackBuffer(frameData_[curFrame_], mode == Draw::ReadbackMode::OLD_DATA_OK ? src : nullptr, w, h, srcFormat, destFormat, pixelStride, pixels); return true; } @@ -342,7 +345,7 @@ void GLRenderManager::CopyImageToMemorySync(GLRTexture *texture, int mipLevel, i FlushSync(); - queueRunner_.CopyFromReadbackBuffer(nullptr, w, h, Draw::DataFormat::R8G8B8A8_UNORM, destFormat, pixelStride, pixels); + queueRunner_.CopyFromReadbackBuffer(frameData_[curFrame_], nullptr, w, h, Draw::DataFormat::R8G8B8A8_UNORM, destFormat, pixelStride, pixels); } void GLRenderManager::BeginFrame() { @@ -380,6 +383,7 @@ void GLRenderManager::Finish() { VLOG("PUSH: Finish, pushing task. curFrame = %d", curFrame); GLRRenderThreadTask task; task.frame = curFrame; + task.nextFrame = (curFrame + 1) % inflightFrames_; task.runType = GLRRunType::PRESENT; { @@ -411,7 +415,7 @@ bool GLRenderManager::Run(GLRRenderThreadTask &task) { } // queueRunner_.LogSteps(stepsOnThread); - queueRunner_.RunInitSteps(task.initSteps, skipGLCalls_); + queueRunner_.RunInitSteps(task.initSteps, frameData, skipGLCalls_); // Run this after RunInitSteps so any fresh GLRBuffers for the pushbuffers can get created. if (!skipGLCalls_) { @@ -425,11 +429,11 @@ bool GLRenderManager::Run(GLRRenderThreadTask &task) { int passes = GetVRPassesCount(); for (int i = 0; i < passes; i++) { PreVRFrameRender(i); - queueRunner_.RunSteps(task.steps, skipGLCalls_, i < passes - 1, true); + queueRunner_.RunSteps(task.steps, frameData, skipGLCalls_, i < passes - 1, true); PostVRFrameRender(); } } else { - queueRunner_.RunSteps(task.steps, skipGLCalls_, false, false); + queueRunner_.RunSteps(task.steps, frameData, skipGLCalls_, false, false); } if (!skipGLCalls_) { @@ -461,6 +465,12 @@ bool GLRenderManager::Run(GLRRenderThreadTask &task) { VLOG(" PULL: SwapRequested"); swapRequest = true; } + + // End of the frame. Now we do the copies for readback for the upcoming frame, + // unfortunately there's no better place to do it since we have to do it + // on the OpenGL thread, and it has to happen before we start recording the next frame. + GLFrameData &nextFrameData = frameData_[task.nextFrame]; + nextFrameData.PerformReadbacks(); } else { frameData.skipSwap = false; } @@ -502,6 +512,7 @@ void GLRenderManager::FlushSync() { GLRRenderThreadTask task; task.frame = curFrame_; + task.nextFrame = (curFrame_ + 1) % inflightFrames_; task.runType = GLRRunType::SYNC; std::unique_lock lock(pushMutex_); diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index 281a42353f..464d000198 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -381,6 +381,7 @@ struct GLRRenderThreadTask { std::vector initSteps; int frame; + int nextFrame; GLRRunType runType; }; diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index ca539f1f17..d5932141cb 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -568,9 +568,11 @@ OpenGLContext::OpenGLContext() { if (gl_extensions.IsGLES) { caps_.clipDistanceSupported = gl_extensions.EXT_clip_cull_distance || gl_extensions.APPLE_clip_distance; caps_.cullDistanceSupported = gl_extensions.EXT_clip_cull_distance; + caps_.pboSupported = gl_extensions.GLES3; } else { caps_.clipDistanceSupported = gl_extensions.VersionGEThan(3, 0); caps_.cullDistanceSupported = gl_extensions.ARB_cull_distance; + caps_.pboSupported = true; } caps_.textureNPOTFullySupported = (!gl_extensions.IsGLES && gl_extensions.VersionGEThan(2, 0, 0)) ||