From 66b47a101bd1c5823e64f69cf1b5e1929438b2a9 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 4 Oct 2026 04:54:53 -0400 Subject: [PATCH] [Fix] (Remote): glFinish waits for the GPU, Magma bounds unsubmitted clear objects, ES contexts advertise OES_mapbuffer/rgb8_rgba8/depth24 --- .../DirectVulkan/Renderer/WireFramebuffer.inc | 7 +++++ MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 27 ++++++++++++++----- MobileGL/MG_Impl/GetProcAddress.cpp | 4 +++ MobileGL/MG_Remote/Client/ClientSession.cpp | 4 ++- MobileGL/MG_Remote/Client/EmitTables.cpp | 17 ++++++++++++ MobileGL/MG_Remote/Client/EmitTables.h | 2 ++ 6 files changed, 54 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc index a2953f5e..8f3b4829 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc @@ -233,6 +233,13 @@ void VulkanRenderer::ClearWireFramebuffer(const MG_Pipe::MGPFramebufferState& fbo, const ClearAttachmentPayload& payload, GLint drawbuffer) { RetireWireDrawPass(); + // A stream that neither presents nor finishes (an off-screen loop) never submits, so its + // recording and the per-clear objects retired below would grow until host memory runs + // out. Past a bound, submit what is recorded: the objects then retire with that work. + constexpr SizeT kRetiredObjectsBeforeSubmit = 1024; + if (m_wireRetiredObjects.size() >= kRetiredObjectsBeforeSubmit && m_wirePreparationDepth == 0 && + HasPendingRecordedWork()) + (void)FlushPendingCommands(); if (MGB_CTX->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) return; const auto clearSurface = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect, Uint32 colorIndex) { diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index f72b3476..45bd7e00 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -610,7 +610,7 @@ namespace MobileGL::MG_Impl::GLImpl { // GL_OES_EGL_image_external(_essl3) only where the server samples YUV images: an // external texture is a 2D texture holding the image converted to RGBA, which needs // the server to hold the YUV image in the first place (EGLImpl::SharedImageYuvAvailable). - static const auto build = [](Bool images, Bool external, Bool es) { + static const auto build = [](Bool images, Bool external, Bool es, Bool esApi) { Vector list; if (images) list.push_back("GL_OES_EGL_image"); if (images && external) { @@ -627,15 +627,30 @@ namespace MobileGL::MG_Impl::GLImpl { list.push_back("GL_EXT_texture_format_BGRA8888"); list.push_back("GL_EXT_read_format_bgra"); } + // Any context created for the OpenGL ES API, whatever it calls itself: the ES 2.0 + // names of what desktop GL has in core - RGB8/RGBA8 and DEPTH_COMPONENT24 + // renderbuffers, and glMapBuffer (exported under its OES names as well). + if (esApi) { + list.push_back("GL_OES_rgb8_rgba8"); + list.push_back("GL_OES_depth24"); + list.push_back("GL_OES_mapbuffer"); + } return list; }; - static const Vector lists[8] = { - build(false, false, false), build(false, false, true), build(false, true, false), - build(false, true, true), build(true, false, false), build(true, false, true), - build(true, true, false), build(true, true, true)}; + static const Vector lists[16] = { + build(false, false, false, false), build(false, false, false, true), + build(false, false, true, false), build(false, false, true, true), + build(false, true, false, false), build(false, true, false, true), + build(false, true, true, false), build(false, true, true, true), + build(true, false, false, false), build(true, false, false, true), + build(true, false, true, false), build(true, false, true, true), + build(true, true, false, false), build(true, true, false, true), + build(true, true, true, false), build(true, true, true, true)}; const Bool images = MG_Impl::EGLImpl::SharedImagesAvailable(); const Bool external = images && MG_Impl::EGLImpl::SharedImageYuvAvailable(); - return lists[(images ? 4 : 0) + (external ? 2 : 0) + (EsIdentityActive() ? 1 : 0)]; + const Bool esApi = MG_State::pEGLContext && + MG_State::pEGLContext->GetCurrentContextClientAPI() == EGL_OPENGL_ES_API; + return lists[(images ? 8 : 0) + (external ? 4 : 0) + (EsIdentityActive() ? 2 : 0) + (esApi ? 1 : 0)]; } // Robust buffer access as GL 4.5 / KHR_robust_buffer_access_behavior define it: the backend diff --git a/MobileGL/MG_Impl/GetProcAddress.cpp b/MobileGL/MG_Impl/GetProcAddress.cpp index c7bd03b7..6073cf26 100644 --- a/MobileGL/MG_Impl/GetProcAddress.cpp +++ b/MobileGL/MG_Impl/GetProcAddress.cpp @@ -246,6 +246,10 @@ namespace MobileGL::MG_Impl { GETPROC(glBufferSubData, name); GETPROC(glGetBufferSubData, name); GETPROC(glMapBuffer, name); + // GL_OES_mapbuffer (ES contexts): the same three calls under their OES names. + if (strcmp(name, "glMapBufferOES") == 0) return (void*)glMapBuffer; + if (strcmp(name, "glUnmapBufferOES") == 0) return (void*)glUnmapBuffer; + if (strcmp(name, "glGetBufferPointervOES") == 0) return (void*)glGetBufferPointerv; GETPROC(glUnmapBuffer, name); GETPROC(glGetBufferParameteriv, name); GETPROC(glGetBufferPointerv, name); diff --git a/MobileGL/MG_Remote/Client/ClientSession.cpp b/MobileGL/MG_Remote/Client/ClientSession.cpp index 6aeb1b3c..c27ae4ec 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.cpp +++ b/MobileGL/MG_Remote/Client/ClientSession.cpp @@ -2551,8 +2551,10 @@ namespace MobileGL::MG_Remote::Client { // glFinish. Nothing goes on the wire - Flush and Finish have no record (ARCHITECTURE // ยง11) - and under lockstep there was nothing to do, because the client had already // waited out every command it issued. Under run-ahead "the commands issued so far have - // completed" is a promise the queue can break, so this is where it is kept. + // completed" is a promise the queue can break, so this is where it is kept - and + // "completed" includes the GPU, so a fence round trip follows the apply. WaitForApplyToCatchUp("glFinish"); + EmitFinishWait(); } Uint64 ClientSession::AcquirePresentCredit() { diff --git a/MobileGL/MG_Remote/Client/EmitTables.cpp b/MobileGL/MG_Remote/Client/EmitTables.cpp index 2ac3e6b6..f6a8d7c2 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.cpp +++ b/MobileGL/MG_Remote/Client/EmitTables.cpp @@ -2085,6 +2085,23 @@ namespace MobileGL::MG_Remote::Client { } // namespace + // glFinish's GPU half: a server fence created after everything the client issued, waited + // with GL_SYNC_FLUSH_COMMANDS_BIT, then destroyed. Waiting for the apply thread alone left + // the GPU queue unbounded behind a "finished" glFinish. + void EmitFinishWait() { + ClientSession* session = ClientSession::Active(); + if (session == nullptr || !session->Started() || session->DeviceLost()) return; + const std::lock_guard lock(g_fenceMutex); + const MG_Pipe::MGPipeHandle handle = MG_Pipe::MGPipeSlots().Allocate(MG_Pipe::MGPipeKind::Fence); + const MG_Pipe::MGPHandleOnly desc{handle, static_cast(MG_Pipe::MGPipeKind::Fence), 0}; + session->EmitAndWait(MG_Pipe::MGPWireOp::FenceCreate, &desc, sizeof(desc), nullptr, 0, nullptr, 0, nullptr); + constexpr GLuint64 kFinishTimeoutNs = 10ull * 1000 * 1000 * 1000; + const MG_Pipe::MGPFenceWait request{handle, kFinishTimeoutNs, GL_SYNC_FLUSH_COMMANDS_BIT, 0}; + (void)ReadFenceReply(*session, MG_Pipe::MGPWireOp::FenceWait, &request, sizeof(request)); + session->EmitAndWait(MG_Pipe::MGPWireOp::FenceDestroy, &desc, sizeof(desc), nullptr, 0, nullptr, 0, nullptr); + MG_Pipe::MGPipeSlots().Free(MG_Pipe::MGPipeKind::Fence, handle); + } + void NoteFenceSignaledByServer(MG_Pipe::MGPipeHandle fence) { RememberFenceSignaled(fence); g_fenceServerReports.fetch_add(1, std::memory_order_relaxed); diff --git a/MobileGL/MG_Remote/Client/EmitTables.h b/MobileGL/MG_Remote/Client/EmitTables.h index cac4a8b2..f038f72c 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.h +++ b/MobileGL/MG_Remote/Client/EmitTables.h @@ -291,6 +291,8 @@ namespace MobileGL::MG_Remote::Client { // // The event drain's half: the server reported `fence` signaled (kEventFenceSignaled). Every // later glClientWaitSync / glGetSynciv(GL_SYNC_STATUS) on it answers without a round trip. + // glFinish's wait for the GPU (a server fence created, waited and destroyed). + void EmitFinishWait(); void NoteFenceSignaledByServer(MG_Pipe::MGPipeHandle fence); // What the fence polls cost, for the lanes. `LocalAnswers` = polls answered with no record