From 233e2f8ced97588b96e76b4e794cf4057dcc7b10 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 2 Oct 2026 16:27:16 -0400 Subject: [PATCH] [Fix] (Espryt): swap a pbuffer without damage rectangles --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 19 ++++++++++++------- MobileGL/MG_Test/Util/DamageTest.cpp | 15 +++++++++++++++ MobileGL/MG_Util/Damage/Damage.h | 9 +++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 748bbada..9e6bf53c 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -18159,21 +18159,26 @@ namespace MobileGL::MG_Backend::DirectGLES { // // The damage is the client's, from the present record (CurrentPresentDamage): GL window // coordinates, which is what the native call takes. Whole surface = no rectangles. - const EGLSurface drawSurface = ActiveNativeSession().Draw; + // + // ONLY A WINDOW SURFACE TAKES RECTANGLES: the platform's swap-with-damage hands them to the + // surface's native window, and a pbuffer has none (Android's dereferences it regardless). + NativeSessionState& session = ActiveNativeSession(); // before the lock: it takes it too + const EGLSurface drawSurface = session.Draw; + Bool windowSurface = false; + if (drawSurface != EGL_NO_SURFACE) { + const std::lock_guard lock(g_nativeRegistryMutex); + if (const auto* entry = FindSessionSurfaceLocked(session, drawSurface)) + windowSurface = entry->Window != static_cast(0); + } if (g_EGLFuncs.eglSwapBuffersWithDamageEXT != nullptr) { const MG_Util::Damage::Region& damage = MG_Backend::CurrentPresentDamage(); EGLint rects[MG_Util::Damage::kMaxRects * 4] = {}; - EGLint count = 0; - if (!damage.IsFull() && !damage.IsEmpty()) { - count = static_cast(MG_Util::Damage::PackRects(damage, rects, MG_Util::Damage::kMaxRects)); - } + const EGLint count = static_cast(MG_Util::Damage::NativeSwapRects(windowSurface, damage, rects)); g_EGLFuncs.eglSwapBuffersWithDamageEXT(g_Display, drawSurface, count > 0 ? rects : nullptr, count); } else { g_EGLFuncs.eglSwapBuffers(g_Display, drawSurface); } if (drawSurface != EGL_NO_SURFACE) { - // Resolved before the lock: ActiveNativeSession takes it too. - NativeSessionState& session = ActiveNativeSession(); const std::lock_guard lock(g_nativeRegistryMutex); if (auto* entry = FindSessionSurfaceLocked(session, drawSurface)) ++entry->Presents; } diff --git a/MobileGL/MG_Test/Util/DamageTest.cpp b/MobileGL/MG_Test/Util/DamageTest.cpp index 2c07ee4c..a97c6a86 100644 --- a/MobileGL/MG_Test/Util/DamageTest.cpp +++ b/MobileGL/MG_Test/Util/DamageTest.cpp @@ -137,6 +137,21 @@ TEST(Damage, PackedRectsRoundTripThroughARecord) { EXPECT_EQ(Region::FromEglRects(out, 1), Of({{1, 2, 56, 66}})); } +// The native swap gets rectangles only for a window surface: Android's swap-with-damage on a +// pbuffer (a client window's drawable) dereferences the native window it does not have. +TEST(Damage, OnlyAWindowSurfaceSwapsWithRectangles) { + Int32 out[kMaxRects * 4] = {}; + const Region partial = Of({{10, 20, 30, 40}}); + EXPECT_EQ(NativeSwapRects(false, partial, out), 0u) << "a pbuffer swaps plainly"; + EXPECT_EQ(NativeSwapRects(true, Region::Full(), out), 0u); + EXPECT_EQ(NativeSwapRects(true, Region(), out), 0u) << "nothing damaged has no rectangle spelling"; + ASSERT_EQ(NativeSwapRects(true, partial, out), 1u); + EXPECT_EQ(out[0], 10); + EXPECT_EQ(out[1], 20); + EXPECT_EQ(out[2], 30); + EXPECT_EQ(out[3], 40); +} + // Three shared images rotate; each write must bring over what that image missed. TEST(Damage, EachBufferCopiesTheDamageItMissedSinceItsLastWrite) { constexpr Int32 W = 200, H = 100; diff --git a/MobileGL/MG_Util/Damage/Damage.h b/MobileGL/MG_Util/Damage/Damage.h index 10d99678..546141c5 100644 --- a/MobileGL/MG_Util/Damage/Damage.h +++ b/MobileGL/MG_Util/Damage/Damage.h @@ -266,6 +266,15 @@ namespace MobileGL::MG_Util::Damage { return static_cast(rects.size()); } + // The rectangles a native swap-with-damage gets for `damage` (into `out`, room for kMaxRects), + // and their count; 0 = a plain swap. Only a window surface takes any: the platform hands them to + // the surface's native window, and a pbuffer has none (Android's EGL dereferences it anyway). + // An empty damage has no spelling (no rectangles means the whole surface), so it swaps plainly. + inline Uint32 NativeSwapRects(Bool windowSurface, const Region& damage, Int32* out) { + if (!windowSurface || damage.IsFull() || damage.IsEmpty()) return 0; + return PackRects(damage, out, kMaxRects); + } + // Per buffer of a rotating set: the damage each has missed since it was last written. class BufferDamageTracker { public: