From bb4e3e0ec6f8f2a1eb49e3b9fb0e62921a6df83e Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 2 Oct 2026 17:11:31 -0400 Subject: [PATCH] [Fix] (Espryt): query buffer age only after a damage region is declared --- MobileGL/MG_Backend/BackendObject.h | 26 ++++++- .../DirectGLES/BackendObject_DirectGLES.cpp | 8 +- .../DirectGLES/BackendObject_DirectGLES.h | 4 +- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 75 ++++++++++++++++--- MobileGL/MG_Backend/DirectGLES/DirectGLES.h | 12 ++- .../BackendObject_DirectVulkan.cpp | 5 +- .../DirectVulkan/BackendObject_DirectVulkan.h | 2 +- MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp | 53 ++++++++++++- MobileGL/MG_Impl/EGLImpl/EGLImpl.h | 1 + .../MG_Impl/EGLImpl/EGLPlatformExtensions.h | 3 +- .../MG_Impl/EGLImpl/Exporting/Definitions.cpp | 7 ++ .../MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp | 2 + MobileGL/MG_Impl/GetProcAddress.cpp | 2 + MobileGL/MG_Pipe/MGPipeTypes.h | 8 +- .../MG_Remote/Client/BackendObject_Remote.cpp | 13 +++- .../MG_Remote/Client/BackendObject_Remote.h | 3 +- MobileGL/MG_Remote/Server/PipeApplier.cpp | 7 +- MobileGL/MG_Test/Util/DamageTest.cpp | 10 +++ MobileGL/MG_Util/Damage/Damage.h | 8 ++ 19 files changed, 224 insertions(+), 25 deletions(-) diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index a2c92af9..f1a4f256 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -651,7 +651,21 @@ namespace MobileGL { // thread: how many presents ago the buffer the next frame draws into held the frame then // presented; 0 = unknown (the application repaints everything). Asking says the client // relies on the answer, so from then on the backend keeps what it presents. - virtual Int32 QueryCurrentBufferAge() { return 0; } + // + // `damageRegionFollows`: the client declares the region it draws (SetCurrentDamageRegion) + // before drawing. A driver whose only buffer age is EGL_KHR_partial_update's keeps a + // buffer's content only outside that region, so without one coming it is not asked. + virtual Int32 QueryCurrentBufferAge(Bool damageRegionFollows) { + (void)damageRegionFollows; + return 0; + } + // eglSetDamageRegionKHR for that draw surface: the frame draws `region` (GL window + // coordinates; Full = all of it) and nothing outside it. A backend without the notion + // ignores it. + virtual Bool SetCurrentDamageRegion(const MG_Util::Damage::Region& region) { + (void)region; + return true; + } // ---- shared images, CLIENT side (BackendObject_Remote implements these) ---------- // @@ -680,8 +694,14 @@ namespace MobileGL { } // In the GL stream: EGL_BUFFER_AGE_EXT of the current draw surface (QueryCurrentBufferAge // on the server). False: the backend has no answer (the caller reports 0). - virtual Bool QueryBufferAge(EGLint* age) { - (void)age; + virtual Bool QueryBufferAge(Bool damageRegionFollows, EGLint* age) { + (void)damageRegionFollows, (void)age; + return false; + } + // In the GL stream: eglSetDamageRegionKHR for the current draw surface + // (SetCurrentDamageRegion on the server). + virtual Bool SetDamageRegion(const MG_Util::Damage::Region& region) { + (void)region; return false; } // In the GL stream: the texture whose lifetime id is `textureLifetimeId` takes the image as diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 8a25e997..1a80a8ee 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -1222,7 +1222,13 @@ namespace MobileGL::MG_Backend::DirectGLES { #endif } - Int32 BackendObject_DirectGLES::QueryCurrentBufferAge() { return DirectGLES::CurrentDrawBufferAge(); } + Int32 BackendObject_DirectGLES::QueryCurrentBufferAge(Bool damageRegionFollows) { + return DirectGLES::CurrentDrawBufferAge(damageRegionFollows); + } + + Bool BackendObject_DirectGLES::SetCurrentDamageRegion(const MG_Util::Damage::Region& region) { + return DirectGLES::SetCurrentDrawDamageRegion(region); + } Bool BackendObject_DirectGLES::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) { return BackendObject::SwapEGLBuffers(dpy, draw); diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h index d1570c84..2e99b6bc 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h @@ -48,7 +48,9 @@ namespace MobileGL::MG_Backend::DirectGLES { const MG_Util::Damage::Region& region) override; // A window surface's age is the driver's (EGL_EXT_buffer_age); a pbuffer keeps its content, // so it is 1 once it has been presented (DirectGLES::CurrentDrawBufferAge). - Int32 QueryCurrentBufferAge() override; + Int32 QueryCurrentBufferAge(Bool damageRegionFollows) override; + // eglSetDamageRegionKHR on a window surface whose driver has EGL_KHR_partial_update. + Bool SetCurrentDamageRegion(const MG_Util::Damage::Region& region) override; void ReleaseEGLSurface(EGLSurface surface) override; void ReleaseEGLResources() override; // The server window going and coming back: each window surface on it swaps its native EGL diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 9e6bf53c..0c6bf4c9 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -17112,20 +17112,44 @@ namespace MobileGL::MG_Backend::DirectGLES { // EGL_BUFFER_AGE_EXT (= EGL_BUFFER_AGE_KHR), spelled out: this file sees only the core EGL header. constexpr EGLint kEglBufferAge = 0x313D; - Bool NativeBufferAgeSupported() { + // How the driver answers a buffer age, if at all. EGL_EXT_buffer_age promises the whole + // buffer's content for the age it reports. EGL_KHR_partial_update alone promises it only + // OUTSIDE the frame's damage region, which is the whole surface until eglSetDamageRegionKHR + // narrows it - a frame that asks the age and then draws part of the surface without setting + // the region finds the rest undefined (on a tiler: tiles never loaded, noise on screen). + struct NativeBufferAge { + Bool Ext = false; // EGL_EXT_buffer_age + Bool Partial = false; // EGL_KHR_partial_update + EGLBoolean (*SetDamageRegion)(EGLDisplay, EGLSurface, EGLint*, EGLint) = nullptr; + }; + const NativeBufferAge& NativeBufferAgeSupport() { static EGLDisplay asked = EGL_NO_DISPLAY; - static Bool supported = false; - if (g_Display == EGL_NO_DISPLAY || !g_EGLFuncs.eglQueryString) return false; + static NativeBufferAge support; + if (g_Display == EGL_NO_DISPLAY || !g_EGLFuncs.eglQueryString) { + static const NativeBufferAge none; + return none; + } if (asked != g_Display) { asked = g_Display; const char* extensions = g_EGLFuncs.eglQueryString(g_Display, EGL_EXTENSIONS); - supported = extensions != nullptr && (std::strstr(extensions, "EGL_EXT_buffer_age") != nullptr || - std::strstr(extensions, "EGL_KHR_partial_update") != nullptr); - MGLOG_I("DirectGLES: the driver %s EGL_EXT_buffer_age", supported ? "has" : "lacks"); + support = {}; + support.Ext = extensions != nullptr && std::strstr(extensions, "EGL_EXT_buffer_age") != nullptr; + support.Partial = extensions != nullptr && std::strstr(extensions, "EGL_KHR_partial_update") != nullptr; + if (support.Partial && g_EGLFuncs.eglGetProcAddress) { + support.SetDamageRegion = reinterpret_cast( + g_EGLFuncs.eglGetProcAddress("eglSetDamageRegionKHR")); + } + if (support.SetDamageRegion == nullptr) support.Partial = false; + MGLOG_I("DirectGLES: native buffer age: EGL_EXT_buffer_age %s, EGL_KHR_partial_update %s", + support.Ext ? "yes" : "no", support.Partial ? "yes" : "no"); } - return supported; + return support; } + // The window surface whose age the driver was asked in the frame being drawn on this (the + // session's apply) thread; the present ends the frame. + thread_local EGLSurface t_ageAskedSurface = EGL_NO_SURFACE; + // The calling session's record of `surface`; the registry lock is held by the caller (another // session's window bring-up may erase stale entries of this one). NativeSessionState::SurfaceEntry* FindSessionSurfaceLocked(NativeSessionState& session, EGLSurface surface) { @@ -17136,7 +17160,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } } // namespace - Int32 CurrentDrawBufferAge() { + Int32 CurrentDrawBufferAge(Bool damageRegionFollows) { NativeSessionState& session = ActiveNativeSession(); const EGLSurface surface = session.Draw; if (surface == EGL_NO_SURFACE || g_Display == EGL_NO_DISPLAY) return 0; @@ -17152,12 +17176,43 @@ namespace MobileGL::MG_Backend::DirectGLES { if (!window) return presents > 0 ? 1 : 0; // The driver's answer. On Android the query dequeues the buffer the next frame draws into - // what the frame's first draw would do anyway, and the client asks before it draws. - if (presents == 0 || !NativeBufferAgeSupported() || !g_EGLFuncs.eglQuerySurface) return 0; + const NativeBufferAge& support = NativeBufferAgeSupport(); + const Bool askable = MG_Util::Damage::NativeAgeAskable(support.Ext, support.Partial, damageRegionFollows); + if (presents == 0 || !askable || !g_EGLFuncs.eglQuerySurface) return 0; EGLint age = 0; if (!g_EGLFuncs.eglQuerySurface(g_Display, surface, kEglBufferAge, &age) || age < 0) return 0; + t_ageAskedSurface = surface; return age; } + Bool SetCurrentDrawDamageRegion(const MG_Util::Damage::Region& region) { + NativeSessionState& session = ActiveNativeSession(); + const EGLSurface surface = session.Draw; + if (surface == EGL_NO_SURFACE || g_Display == EGL_NO_DISPLAY) return true; + { + const std::lock_guard lock(g_nativeRegistryMutex); + const auto* entry = FindSessionSurfaceLocked(session, surface); + if (entry == nullptr || entry->Window == static_cast(0)) return true; + } + const NativeBufferAge& support = NativeBufferAgeSupport(); + if (!support.Partial) return true; + // EGL_KHR_partial_update wants the age asked in this frame before the region is set; a client + // that was answered 0 without the driver being asked (its first frame) repaints everything. + if (t_ageAskedSurface != surface) { + EGLint ignored = 0; + (void)g_EGLFuncs.eglQuerySurface(g_Display, surface, kEglBufferAge, &ignored); + t_ageAskedSurface = surface; + } + EGLint rects[MG_Util::Damage::kMaxRects * 4] = {}; + const EGLint count = + region.IsFull() ? 0 : static_cast(MG_Util::Damage::PackRects(region, rects, MG_Util::Damage::kMaxRects)); + if (support.SetDamageRegion(g_Display, surface, count > 0 ? rects : nullptr, count) != EGL_TRUE) { + MGLOG_W_ONCE("DirectGLES: eglSetDamageRegionKHR refused a %d-rectangle region (EGL error 0x%x)", count, + g_EGLFuncs.eglGetError ? static_cast(g_EGLFuncs.eglGetError()) : 0u); + } + return true; + } + Bool SelectSessionSurface(EGLSurface surface) { NativeSessionState& session = ActiveNativeSession(); if (!SessionOwnsSurface(session, surface)) return false; @@ -18182,6 +18237,8 @@ namespace MobileGL::MG_Backend::DirectGLES { const std::lock_guard lock(g_nativeRegistryMutex); if (auto* entry = FindSessionSurfaceLocked(session, drawSurface)) ++entry->Presents; } + // A frame boundary: the next frame's age is asked anew. + t_ageAskedSurface = EGL_NO_SURFACE; if (canFence) PollFrameFences(); diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 7d109ab7..e78b14a9 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -117,9 +117,15 @@ namespace MobileGL::MG_Backend::DirectGLES { // The surface the calling thread's ACTIVE context last drew to (EGL_NO_SURFACE for none). EGLSurface CurrentContextDrawSurface(); // EGL_BUFFER_AGE_EXT of the session's current draw surface for the next frame: the driver's - // answer for a window surface (0 where the driver has no EGL_EXT_buffer_age), 1 for a pbuffer - // that has been presented (a pbuffer's content stays across swaps), 0 otherwise. - Int32 CurrentDrawBufferAge(); + // answer for a window surface, 1 for a pbuffer that has been presented (a pbuffer's content stays + // across swaps), 0 otherwise. A driver whose buffer age is only EGL_KHR_partial_update's keeps + // nothing inside the frame's damage region - the whole surface unless one is set - so it is asked + // only when `damageRegionFollows` (SetCurrentDrawDamageRegion before the first draw). + Int32 CurrentDrawBufferAge(Bool damageRegionFollows); + // eglSetDamageRegionKHR(region) on the session's current window surface (a pbuffer, or a driver + // without EGL_KHR_partial_update, ignores it). The driver wants the age asked first in the frame; + // when the client was answered without asking it, it is asked here. + Bool SetCurrentDrawDamageRegion(const MG_Util::Damage::Region& region); // Makes an EXISTING native surface of the calling thread's session the one it draws to, and // binds it to the active context. False for a surface the session does not own. Bool BindSessionSurface(EGLSurface surface); diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index d8205b24..d36d65b5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -585,7 +585,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { #endif } - Int32 BackendObject_DirectVulkan::QueryCurrentBufferAge() { + // A swapchain image keeps its content through a present whatever region is drawn next: the + // damage region the client may declare is not needed here. + Int32 BackendObject_DirectVulkan::QueryCurrentBufferAge(Bool damageRegionFollows) { + (void)damageRegionFollows; const std::lock_guard lock(m_eglStateMutex); return pVulkanRenderer ? pVulkanRenderer->CurrentDrawBufferAge() : 0; } diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h index c2f469ef..80c57d75 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h @@ -41,7 +41,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool BlitDefaultFramebufferToSharedImage(const SharedImageView& image, const MG_Util::Damage::Region& region) override; // The active surface target's swapchain image ages (VulkanRenderer::CurrentDrawBufferAge). - Int32 QueryCurrentBufferAge() override; + Int32 QueryCurrentBufferAge(Bool damageRegionFollows) override; void ReleaseEGLSurface(EGLSurface surface) override; void ReleaseEGLResources() override; // The server window going and coming back: the window surface's target (VkSurfaceKHR, diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index ea936acb..9a200338 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace MobileGL::MG_Impl::EGLImpl { namespace { @@ -194,6 +195,14 @@ namespace MobileGL::MG_Impl::EGLImpl { } } + // EGL_KHR_partial_update: the surfaces whose application has declared a damage region. Their + // buffer-age query tells the server the region follows (a driver whose age is only + // partial_update's is then asked). Guarded by EGLOperationMutex; a destroyed surface leaves. + std::unordered_set& DamageRegionSurfaces() { + static auto* surfaces = new std::unordered_set(); + return *surfaces; + } + #if MOBILEGL_WAYLAND_WINDOWS // Every Wayland window surface's presentation, by surface. Guarded by EGLOperationMutex, // which every entry point that reaches it holds. @@ -654,6 +663,7 @@ namespace MobileGL::MG_Impl::EGLImpl { if (!state->DestroySurface(dpy, surface)) { return EGL_FALSE; } + DamageRegionSurfaces().erase(surface); #if MOBILEGL_WAYLAND_WINDOWS WaylandSurfaces().erase(surface); #endif @@ -778,7 +788,8 @@ namespace MobileGL::MG_Impl::EGLImpl { #endif EGLint age = 0; auto* backendObject = MG_Backend::pActiveBackendObject.get(); - if (backendObject == nullptr || !backendObject->QueryBufferAge(&age)) age = 0; + const Bool regionFollows = DamageRegionSurfaces().count(surface) != 0; + if (backendObject == nullptr || !backendObject->QueryBufferAge(regionFollows, &age)) age = 0; *value = age; return EGL_TRUE; } @@ -791,6 +802,46 @@ namespace MobileGL::MG_Impl::EGLImpl { return state->QuerySurface(display, surface, attribute, value) ? EGL_TRUE : EGL_FALSE; } + // EGL_KHR_partial_update: the region this frame draws, before it draws (GL window coordinates; + // none = the whole surface). Forwarded in the stream to the server's native surface. More than + // kMaxRects rectangles are merged, so an application declaring that many draws their bounds. + EGLBoolean SetDamageRegion(EGLDisplay dpy, EGLSurface surface, const EGLint* rects, EGLint n_rects) { + const std::lock_guard operationLock(EGLOperationMutex()); + auto* state = GetState(); + if (!state) return EGL_FALSE; + if (!SharedImagesAvailable()) { + state->SetError(EGL_BAD_DISPLAY); + return EGL_FALSE; + } + if (!state->ValidateSurfaceOnDisplay(dpy, surface)) { + state->SetError(EGL_BAD_SURFACE); + return EGL_FALSE; + } + if (state->GetCurrentSurface(EGL_DRAW) != surface) { + state->SetError(EGL_BAD_MATCH); + return EGL_FALSE; + } + if (n_rects < 0 || (n_rects > 0 && rects == nullptr)) { + state->SetError(EGL_BAD_PARAMETER); + return EGL_FALSE; + } + MG_Util::Damage::Region region = MG_Util::Damage::Region::FromEglRects(rects, n_rects); + if (!region.IsFull()) { + EGLint width = 0; + EGLint height = 0; + if (state->QuerySurface(dpy, surface, EGL_WIDTH, &width) && state->QuerySurface(dpy, surface, EGL_HEIGHT, &height)) + region.Normalize(width, height); + else + region.SetFull(); + } +#if MOBILEGL_BUILD_DISAGGREGATED + if (StreamGateActive()) StreamBindCallingThreadLocked(); +#endif + DamageRegionSurfaces().insert(surface); + if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) (void)backendObject->SetDamageRegion(region); + return EGL_TRUE; + } + char const* QueryString(EGLDisplay display, EGLint name) { auto* state = GetStateEnsureInitialized(); if (!state) { diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.h b/MobileGL/MG_Impl/EGLImpl/EGLImpl.h index 58412786..6cb06857 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.h +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.h @@ -17,6 +17,7 @@ namespace MobileGL::MG_Impl::EGLImpl { const EGLint* attrib_list); EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw); EGLBoolean SwapBuffersWithDamage(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, EGLint n_rects); + EGLBoolean SetDamageRegion(EGLDisplay dpy, EGLSurface surface, const EGLint* rects, EGLint n_rects); EGLBoolean ChooseConfig(EGLDisplay dpy, const EGLint* attrib_list, EGLConfig* configs, EGLint config_size, EGLint* num_config); EGLContext CreateContext(EGLDisplay dpy, EGLConfig config, EGLContext shareCtx, const EGLint* attrib_list); diff --git a/MobileGL/MG_Impl/EGLImpl/EGLPlatformExtensions.h b/MobileGL/MG_Impl/EGLImpl/EGLPlatformExtensions.h index 236cbadc..b60fb8f7 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLPlatformExtensions.h +++ b/MobileGL/MG_Impl/EGLImpl/EGLPlatformExtensions.h @@ -29,7 +29,8 @@ namespace MobileGL::MG_Impl::EGLImpl { "EGL_KHR_create_context EGL_EXT_platform_base EGL_KHR_platform_base " "EGL_MESA_platform_surfaceless" MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS " EGL_KHR_image_base EGL_EXT_image_dma_buf_import EGL_EXT_image_dma_buf_import_modifiers" - " EGL_EXT_buffer_age EGL_KHR_swap_buffers_with_damage EGL_EXT_swap_buffers_with_damage"; + " EGL_EXT_buffer_age EGL_KHR_partial_update EGL_KHR_swap_buffers_with_damage" + " EGL_EXT_swap_buffers_with_damage"; inline constexpr const char* kClientExtensionString = "EGL_EXT_client_extensions EGL_KHR_create_context EGL_EXT_platform_base " "EGL_KHR_platform_base EGL_MESA_platform_surfaceless" MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS; diff --git a/MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp index bd8f1b8b..d062b0b7 100644 --- a/MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp @@ -175,6 +175,13 @@ MOBILEGL_EGL_API EGLBoolean eglSwapBuffersWithDamageEXT(EGLDisplay dpy, EGLSurfa return MobileGL::MG_Impl::EGLImpl::SwapBuffersWithDamage(dpy, draw, rects, n_rects); } +// EGL_KHR_partial_update. +MOBILEGL_EGL_API EGLBoolean eglSetDamageRegionKHR(EGLDisplay dpy, EGLSurface surface, EGLint* rects, EGLint n_rects) { + const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock; + MGLOG_D("eglSetDamageRegionKHR(dpy=%p, surface=%p, n_rects=%d)", dpy, surface, n_rects); + return MobileGL::MG_Impl::EGLImpl::SetDamageRegion(dpy, surface, rects, n_rects); +} + MOBILEGL_EGL_API EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) { const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock; MGLOG_D("eglCreatePbufferSurface(dpy=%p, config=%p, attrib_list=%p)", dpy, config, attrib_list); diff --git a/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp b/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp index fa0ef9df..8c474135 100644 --- a/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp +++ b/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp @@ -40,6 +40,7 @@ extern "C" EGLBoolean eglSwapBuffersWithDamageKHR(EGLDisplay dpy, EGLSurface dra EGLint n_rects); extern "C" EGLBoolean eglSwapBuffersWithDamageEXT(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, EGLint n_rects); +extern "C" EGLBoolean eglSetDamageRegionKHR(EGLDisplay dpy, EGLSurface surface, EGLint* rects, EGLint n_rects); extern "C" EGLBoolean eglQueryDmaBufFormatsEXT(EGLDisplay dpy, EGLint max_formats, EGLint* formats, EGLint* num_formats); extern "C" EGLBoolean eglQueryDmaBufModifiersEXT(EGLDisplay dpy, EGLint format, EGLint max_modifiers, @@ -240,6 +241,7 @@ namespace { {"eglQueryDmaBufFormatsEXT", reinterpret_cast(eglQueryDmaBufFormatsEXT)}, {"eglSwapBuffersWithDamageKHR", reinterpret_cast(eglSwapBuffersWithDamageKHR)}, {"eglSwapBuffersWithDamageEXT", reinterpret_cast(eglSwapBuffersWithDamageEXT)}, + {"eglSetDamageRegionKHR", reinterpret_cast(eglSetDamageRegionKHR)}, {"eglQueryDmaBufModifiersEXT", reinterpret_cast(eglQueryDmaBufModifiersEXT)}, {"eglCreatePlatformWindowSurface", reinterpret_cast(eglCreatePlatformWindowSurface)}, {"eglCreatePlatformPixmapSurface", reinterpret_cast(eglCreatePlatformPixmapSurface)}, diff --git a/MobileGL/MG_Impl/GetProcAddress.cpp b/MobileGL/MG_Impl/GetProcAddress.cpp index 29ecb924..03f2deff 100644 --- a/MobileGL/MG_Impl/GetProcAddress.cpp +++ b/MobileGL/MG_Impl/GetProcAddress.cpp @@ -22,6 +22,7 @@ extern "C" EGLBoolean eglSwapBuffersWithDamageKHR(EGLDisplay dpy, EGLSurface dra EGLint n_rects); extern "C" EGLBoolean eglSwapBuffersWithDamageEXT(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, EGLint n_rects); +extern "C" EGLBoolean eglSetDamageRegionKHR(EGLDisplay dpy, EGLSurface surface, EGLint* rects, EGLint n_rects); extern "C" EGLBoolean eglQueryDmaBufFormatsEXT(EGLDisplay dpy, EGLint max_formats, EGLint* formats, EGLint* num_formats); extern "C" EGLBoolean eglQueryDmaBufModifiersEXT(EGLDisplay dpy, EGLint format, EGLint max_modifiers, @@ -82,6 +83,7 @@ namespace MobileGL::MG_Impl { GETPROC(eglQueryDmaBufFormatsEXT, name); GETPROC(eglSwapBuffersWithDamageKHR, name); GETPROC(eglSwapBuffersWithDamageEXT, name); + GETPROC(eglSetDamageRegionKHR, name); GETPROC(eglQueryDmaBufModifiersEXT, name); GETPROC(eglGetPlatformDisplay, name); GETPROC(eglGetPlatformDisplayEXT, name); diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index c1b7b6c1..267cb948 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -2008,7 +2008,11 @@ namespace MobileGL::MG_Pipe { // QueryBufferAge EGL_BUFFER_AGE_EXT of the current context's draw surface, answered in the // reply's BufferAge: how many presents ago the buffer the next frame draws into held // the frame then presented, 0 = unknown. Asked in the stream, so it is answered - // for the buffer that frame's records will draw into. + // for the buffer that frame's records will draw into. Format carries + // kMGPBufferAgeDamageRegionFollows when the application declares the region it draws + // (EGL_KHR_partial_update) before it draws. + // SetDamageRegion eglSetDamageRegionKHR for the current draw surface: Damage is the region + // the frame is about to draw (GL window coordinates; 0 rectangles = all of it). // // A Present carries the region of the frame to copy (DamageCount rectangles of Damage, GL // window coordinates, origin bottom-left; 0 = all of it): the client keeps per image what the @@ -2023,7 +2027,9 @@ namespace MobileGL::MG_Pipe { kMGPSharedImagePresent = 4, kMGPSharedImageAttach = 5, kMGPSharedImageQueryBufferAge = 6, + kMGPSharedImageSetDamageRegion = 7, }; + inline constexpr Uint32 kMGPBufferAgeDamageRegionFollows = 1u; struct MGPSharedImageOp { Uint32 Op; diff --git a/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp b/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp index 4edd2ccb..a989b7a3 100644 --- a/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp +++ b/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp @@ -514,17 +514,28 @@ namespace MobileGL::MG_Remote::Client { } // In the stream, so the server answers for the buffer the records after it draw into. - Bool BackendObject_Remote::QueryBufferAge(EGLint* age) { + Bool BackendObject_Remote::QueryBufferAge(Bool damageRegionFollows, EGLint* age) { ClientSession* session = ClientSession::Active(); if (session == nullptr || age == nullptr) return false; MG_Pipe::MGPSharedImageOp op{}; op.Op = MG_Pipe::kMGPSharedImageQueryBufferAge; + op.Format = damageRegionFollows ? MG_Pipe::kMGPBufferAgeDamageRegionFollows : 0u; MG_Pipe::MGPSharedImageReply reply{}; if (!session->EmitSharedImage(op, -1, &reply, nullptr)) return false; *age = std::max(reply.BufferAge, 0); return true; } + // In the stream too: it has to reach the server before the frame's first draw. + Bool BackendObject_Remote::SetDamageRegion(const MG_Util::Damage::Region& region) { + ClientSession* session = ClientSession::Active(); + if (session == nullptr) return false; + MG_Pipe::MGPSharedImageOp op{}; + op.Op = MG_Pipe::kMGPSharedImageSetDamageRegion; + op.DamageCount = MG_Util::Damage::PackRects(region, op.Damage, MG_Pipe::kMGPMaxDamageRects); + return session->EmitSharedImage(op, -1, nullptr, nullptr); + } + Bool BackendObject_Remote::AttachSharedImageToTexture(Uint64 textureLifetimeId, Uint64 id) { ClientSession* session = ClientSession::Active(); if (session == nullptr) return false; diff --git a/MobileGL/MG_Remote/Client/BackendObject_Remote.h b/MobileGL/MG_Remote/Client/BackendObject_Remote.h index beb0c154..68aca0a8 100644 --- a/MobileGL/MG_Remote/Client/BackendObject_Remote.h +++ b/MobileGL/MG_Remote/Client/BackendObject_Remote.h @@ -88,7 +88,8 @@ namespace MobileGL::MG_Remote::Client { Bool ImportSharedImage(int fd, Uint32 width, Uint32 height, Uint32 fourcc, Uint64* outId) override; Bool ReleaseSharedImage(Uint64 id) override; Bool PresentToSharedImage(Uint64 id, const MG_Util::Damage::Region& region) override; - Bool QueryBufferAge(EGLint* age) override; + Bool QueryBufferAge(Bool damageRegionFollows, EGLint* age) override; + Bool SetDamageRegion(const MG_Util::Damage::Region& region) override; Bool AttachSharedImageToTexture(Uint64 textureLifetimeId, Uint64 id) override; // Copies the caps mirror's FormatCapabilityCache into the base class's diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index 5d3af20a..659d1ceb 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -1578,9 +1578,14 @@ namespace MobileGL::MG_Remote::Server { case MG_Pipe::kMGPSharedImageQueryBufferAge: { MG_Backend::BackendObject* backend = ServerLoopInstance().Backend(); if (backend == nullptr) return false; - reply.BufferAge = backend->QueryCurrentBufferAge(); + reply.BufferAge = backend->QueryCurrentBufferAge((op.Format & MG_Pipe::kMGPBufferAgeDamageRegionFollows) != 0); return true; } + case MG_Pipe::kMGPSharedImageSetDamageRegion: { + MG_Backend::BackendObject* backend = ServerLoopInstance().Backend(); + if (backend == nullptr) return false; + return backend->SetCurrentDamageRegion(DamageOfRecord(op.DamageCount, op.Damage)); + } case MG_Pipe::kMGPSharedImageAttach: { SI::ImageRef image = holder.Get(op.ImageId); if (image == nullptr) return false; diff --git a/MobileGL/MG_Test/Util/DamageTest.cpp b/MobileGL/MG_Test/Util/DamageTest.cpp index a97c6a86..f23a82d5 100644 --- a/MobileGL/MG_Test/Util/DamageTest.cpp +++ b/MobileGL/MG_Test/Util/DamageTest.cpp @@ -152,6 +152,16 @@ TEST(Damage, OnlyAWindowSurfaceSwapsWithRectangles) { EXPECT_EQ(out[3], 40); } +// A driver whose buffer age is only EGL_KHR_partial_update's keeps nothing inside the default damage +// region (the whole surface): asking it the age for a client that sets no region shows noise. +TEST(Damage, APartialUpdateOnlyDriverIsAskedOnlyWhenARegionFollows) { + EXPECT_TRUE(NativeAgeAskable(true, false, false)) << "EGL_EXT_buffer_age keeps the whole buffer"; + EXPECT_TRUE(NativeAgeAskable(true, true, false)); + EXPECT_FALSE(NativeAgeAskable(false, true, false)) << "partial_update alone, no region coming"; + EXPECT_TRUE(NativeAgeAskable(false, true, true)); + EXPECT_FALSE(NativeAgeAskable(false, false, true)) << "no buffer age at all"; +} + // 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 546141c5..b49eb9cd 100644 --- a/MobileGL/MG_Util/Damage/Damage.h +++ b/MobileGL/MG_Util/Damage/Damage.h @@ -275,6 +275,14 @@ namespace MobileGL::MG_Util::Damage { return PackRects(damage, out, kMaxRects); } + // Whether a window surface's driver may be asked its buffer age for a frame. EGL_EXT_buffer_age + // keeps the whole buffer for the age it reports; EGL_KHR_partial_update alone keeps only what + // lies outside the frame's damage region - the whole surface unless the client sets one - so + // asking it is right only when the client declares that region before it draws. + constexpr Bool NativeAgeAskable(Bool extBufferAge, Bool partialUpdate, Bool damageRegionFollows) { + return extBufferAge || (partialUpdate && damageRegionFollows); + } + // Per buffer of a rotating set: the damage each has missed since it was last written. class BufferDamageTracker { public: