From a25d8ee0e738a41bbca6fe5be9957dde53a55e31 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 17 Jul 2026 11:43:13 -0400 Subject: [PATCH] [Fix] (MG_Backend/DirectGLES): GLES clamps glClearColor to [0,1], zeroing the -FLT_MAX MAX-blend identity; route out-of-range color clears through glClearBufferfv --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index db2f1a6d..7f17d18d 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -1487,7 +1487,102 @@ namespace MobileGL::MG_Backend::DirectGLES { BindCurrentFBO(FramebufferTarget::Draw); - g_GLESFuncs.glClear(mask); + // Debug-only diagnostics: is each offscreen clear complete and unscissored? +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG + { + static int diagCount = 0; + GLint fboId = 0; + g_GLESFuncs.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); + if (fboId != 0 && diagCount++ < 900) { + GLint color0 = 0, depthName = 0, box[4] = {0}; + GLboolean scissor = g_GLESFuncs.glIsEnabled(GL_SCISSOR_TEST); + GLboolean cmask[4] = {0}; + GLfloat cc[4] = {0}; + g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &color0); + g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthName); + g_GLESFuncs.glGetIntegerv(GL_SCISSOR_BOX, box); + g_GLESFuncs.glGetBooleanv(GL_COLOR_WRITEMASK, cmask); + g_GLESFuncs.glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); + MGLOG_D("CLEAR fbo=%d color0=%d depth=%d mask=0x%x scissor=%d box=(%d,%d,%d,%d) cmask=%d%d%d%d cc=(%g,%g,%g,%g)", + fboId, color0, depthName, mask, (int)scissor, box[0], box[1], box[2], box[3], (int)cmask[0], + (int)cmask[1], (int)cmask[2], (int)cmask[3], cc[0], cc[1], cc[2], cc[3]); + } + } +#endif + + // GLES clamps the glClearColor state to [0,1] (desktop GL keeps it unclamped + // for float color buffers). Minecraft's OIT clears its depth-bounds RGBA32F + // target to -FLT_MAX as the MAX-blend identity, so an out-of-range clear + // color must go through glClearBufferfv, which GLES does not clamp. + GLbitfield remainingMask = mask; + if ((mask & GL_COLOR_BUFFER_BIT) != 0) { + const FloatVec4& cc = MG_State::pGLContext->GetRenderStateParameters().ClearColor; + const Bool outOfRange = cc.x() < 0.f || cc.x() > 1.f || cc.y() < 0.f || cc.y() > 1.f || cc.z() < 0.f || + cc.z() > 1.f || cc.w() < 0.f || cc.w() > 1.f; + GLint clearDrawFbo = 0; + g_GLESFuncs.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &clearDrawFbo); + if (outOfRange && clearDrawFbo != 0) { + const GLfloat value[4] = {cc.x(), cc.y(), cc.z(), cc.w()}; + GLint maxDrawBuffers = 0; + GLint clearedCount = 0; + GLint firstDb = -1; + g_GLESFuncs.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); + for (GLint i = 0; i < maxDrawBuffers; ++i) { + GLint db = GL_NONE; + g_GLESFuncs.glGetIntegerv(GL_DRAW_BUFFER0 + static_cast(i), &db); + if (i == 0) firstDb = db; + if (db != GL_NONE) { + g_GLESFuncs.glClearBufferfv(GL_COLOR, i, value); + ++clearedCount; + } + } + remainingMask &= ~static_cast(GL_COLOR_BUFFER_BIT); + (void)clearedCount; + (void)firstDb; + // Debug-only diagnostics: verify the unclamped clear actually landed. +#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG + { + static int diagCount = 0; + if (diagCount++ < 20) { + const GLenum clrErr = g_GLESFuncs.glGetError(); + GLint prevRead = 0, prevPbo = 0; + g_GLESFuncs.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prevRead); + g_GLESFuncs.glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &prevPbo); + g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + g_GLESFuncs.glBindFramebuffer(GL_READ_FRAMEBUFFER, (GLuint)clearDrawFbo); + GLint prevReadBuf = GL_COLOR_ATTACHMENT0; + g_GLESFuncs.glGetIntegerv(GL_READ_BUFFER, &prevReadBuf); + g_GLESFuncs.glReadBuffer(GL_COLOR_ATTACHMENT0); + GLfloat rb[4] = {0}; + g_GLESFuncs.glReadPixels(100, 100, 1, 1, GL_RGBA, GL_FLOAT, rb); + const GLenum rbErr = g_GLESFuncs.glGetError(); + const auto& feFbo = + MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + int feDb0 = -1, feDb1 = -1; + Uint feIdx = 0, feVer = 0; + if (feFbo) { + feIdx = feFbo->GetExternalIndex(); + feVer = feFbo->GetObjectVersion(); + feDb0 = (int)feFbo->GetDrawBuffers()[0]; + feDb1 = (int)feFbo->GetDrawBuffers()[1]; + } + MGLOG_D("CLEARV fbo=%d clrErr=0x%x rbErr=0x%x cc.x=%g cleared=%d firstDb=0x%x prevReadBuf=0x%x " + "feFbo=%u feVer=%u feDb=[%d,%d] stored=(%g,%g,%g,%g)", + clearDrawFbo, clrErr, rbErr, cc.x(), clearedCount, firstDb, prevReadBuf, feIdx, feVer, + feDb0, feDb1, rb[0], rb[1], rb[2], rb[3]); + g_GLESFuncs.glReadBuffer(static_cast(prevReadBuf)); + g_GLESFuncs.glBindFramebuffer(GL_READ_FRAMEBUFFER, (GLuint)prevRead); + g_GLESFuncs.glBindBuffer(GL_PIXEL_PACK_BUFFER, (GLuint)prevPbo); + } + } +#endif + } + } + if (remainingMask != 0) { + g_GLESFuncs.glClear(remainingMask); + } } // GLES core supports only GL_PRIMITIVE_RESTART_FIXED_INDEX (fixed all-ones value). If the app