From 7d2c16a90e470686407b68247db18d8c68b78e9d Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 20 Aug 2026 04:32:14 -0400 Subject: [PATCH] [Fix] (DirectGLES): bound every driver error drain so a lost context cannot spin forever --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 71eef18d..3e4086d9 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -3867,11 +3867,28 @@ namespace MobileGL::MG_Backend::DirectGLES { sizeof(DrawArraysIndirectCommand), "DrawArraysIndirect"); } - static void DrainBlitErrors() { - while (g_GLESFuncs.glGetError() != GL_NO_ERROR) { + // Empties the ES driver's error queue, BOUNDED. A driver that never answers GL_NO_ERROR - a + // lost context is the usual way, and GL_CONTEXT_LOST is allowed to keep coming back - would + // otherwise spin an unbounded drain forever inside whichever GL entry point happened to be + // cleaning up, which is how a GPU reset reads as an unkillable process whose log simply + // stops. A healthy context cannot queue anywhere near the cap, so reaching it IS the + // diagnostic. Every drain in this backend goes through here so the bound cannot drift apart + // between them. + static constexpr Int kMaxDrainedGLErrors = 32; + + static void DrainDriverErrors(const char* site) { + Int drained = 0; + while (drained < kMaxDrainedGLErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR) { + ++drained; + } + if (drained == kMaxDrainedGLErrors) { + MGLOG_E_ONCE("%s: the ES driver still reported errors after %d drains - the context is most likely lost", + site, kMaxDrainedGLErrors); } } + static void DrainBlitErrors() { DrainDriverErrors("BlitFramebuffer"); } + // Sized internal format of the currently bound READ framebuffer's read colour // attachment, 0 when it cannot be determined. static GLenum QueryReadColorAttachmentInternalFormat() { @@ -5042,9 +5059,7 @@ namespace MobileGL::MG_Backend::DirectGLES { return false; } - static void ClearGLErrors() { - while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {} - } + static void ClearGLErrors() { DrainDriverErrors("DirectGLES"); } // Binds a guaranteed-complete 1x1 scratch framebuffer at both targets for the // scope (GenerateMipmap must respecify texture storage while no incomplete @@ -7102,10 +7117,7 @@ namespace MobileGL::MG_Backend::DirectGLES { data = std::move(expanded); } - static void DrainESErrors() { - for (Int i = 0; i < 32 && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) { - } - } + static void DrainESErrors() { DrainDriverErrors("ReadPixels"); } static GLenum QueryReadAttachmentComponentType() { GLint framebufferId = 0;