From bc2d698b3e07c49f4e80ea44ad1d8faf15061152 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 21 Jul 2026 12:18:55 -0400 Subject: [PATCH] [Fix] (DirectGLES): apply the read buffer when one FBO is bound as both draw and read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SyncCurrentFBO skips the READ-target pass when the same GL FBO is bound as both draw and read (the common GL_FRAMEBUFFER case), but the read buffer (glReadBuffer) is only applied inside SyncToBackend's READ path — so the skip silently dropped every glReadBuffer change, leaving the backend read buffer stuck at COLOR_ATTACHMENT0. - Extract the read-buffer application into BackendFramebufferObject:: SyncReadBufferToBackend and invoke it from the skip branch (target == Read) as well as from SyncToBackend, so reads always target the right attachment. - Bind the backend FBO as READ inside the helper before glReadBuffer, since the skip path only bound it as DRAW. - Fixes KHR-GL3x.draw_buffers.draw_buffers_1 (reading COLOR_ATTACHMENT1 while the FBO stays GL_FRAMEBUFFER-bound returned attachment 0's value); the render was already correct, only the readback resolved the wrong attachment. --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 12 +++++++ MobileGL/MG_Backend/DirectGLES/Managers.cpp | 34 +++++++++++++------ MobileGL/MG_Backend/DirectGLES/Managers.h | 4 +++ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index b19dff69..1ef1318d 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -531,6 +531,18 @@ namespace MobileGL::MG_Backend::DirectGLES { if (currentFBO.get() == lastUpdatedFBO) { MGLOG_D("Draw FBO and read FBO are the same, skipping sync."); + // The attachment/draw-buffer work was already done for this GL FBO as the DRAW + // target, but the read buffer (glReadBuffer) is READ-target-specific and would + // be dropped by this skip. Apply it so reads target the right attachment (e.g. + // KHR-GL33.draw_buffers reads each COLOR_ATTACHMENT while the FBO stays bound as + // GL_FRAMEBUFFER — without this every glReadBuffer is a no-op and all reads hit + // COLOR_ATTACHMENT0). + if (target == FramebufferTarget::Read) { + const auto& syncedFBOIt = g_backendFramebufferObjects.find(currentFBO.get()); + if (syncedFBOIt != g_backendFramebufferObjects.end() && syncedFBOIt->second) { + syncedFBOIt->second->SyncReadBufferToBackend(currentFBO); + } + } g_fboSyncedObjectVersions[SizeT(target)] = objectVersion; g_fboSyncedObjects[SizeT(target)] = currentPtr; continue; diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 893ef445..7e08a69a 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -2535,6 +2535,28 @@ namespace MobileGL::MG_Backend::DirectGLES { return false; } + void BackendFramebufferObject::SyncReadBufferToBackend( + const SharedPtr& stateFBOObject) { + if (!stateFBOObject) { + return; + } + auto frontendReadBuf = stateFBOObject->GetReadBuffer(); + if (frontendReadBuf == m_frontendReadBuffer) { + return; + } + m_frontendReadBuffer = frontendReadBuf; + + GLenum glBackendReadBuffer = GetBackendAttachmentType(frontendReadBuf); + if (m_backendReadBuffer != glBackendReadBuffer) { + m_backendReadBuffer = glBackendReadBuffer; + // glReadBuffer targets whatever FBO is bound to GL_READ_FRAMEBUFFER. When this is + // reached from SyncCurrentFBO's "same FBO as draw" skip path the backend FBO was + // only bound as DRAW, so bind it as READ first to route the read buffer correctly. + Bind(FramebufferTarget::Read); + g_GLESFuncs.glReadBuffer(glBackendReadBuffer); + } + } + void BackendFramebufferObject::SyncToBackend( const SharedPtr& stateFBOObject, FramebufferTarget asTarget) { #ifdef TRACY_ENABLE @@ -2616,16 +2638,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // 2. Remap read buffer. glReadBuffer writes the READ-bound FBO's state, so // only apply (and stamp the memo) when this object is bound as READ. - auto frontendReadBuf = stateFBOObject->GetReadBuffer(); - if (frontendReadBuf != m_frontendReadBuffer && asTarget == FramebufferTarget::Read) { - m_frontendReadBuffer = frontendReadBuf; - - GLenum glBackendReadBuffer = GetBackendAttachmentType(frontendReadBuf); - - if (m_backendReadBuffer != glBackendReadBuffer) { - m_backendReadBuffer = glBackendReadBuffer; - g_GLESFuncs.glReadBuffer(glBackendReadBuffer); - } + if (asTarget == FramebufferTarget::Read) { + SyncReadBufferToBackend(stateFBOObject); } // -------------------- Attach texture to backend FBO ----------------------- diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 9210387c..a493f73a 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -375,6 +375,10 @@ namespace MobileGL::MG_Backend::DirectGLES { BackendFramebufferObject(); void SyncToBackend(const SharedPtr& stateFBOObject, FramebufferTarget asTarget); + // Apply only this FBO's read buffer (glReadBuffer) to the backend. Split out so it can + // still run when SyncCurrentFBO skips the READ-target sync because the same GL FBO is + // bound as both draw and read (otherwise glReadBuffer changes would be silently dropped). + void SyncReadBufferToBackend(const SharedPtr& stateFBOObject); void InvalidateSyncedState(); Uint GetBackendFramebufferId() const { return m_backendFBOId; } void Bind(FramebufferTarget target) const;