[Fix] (Espryt): query buffer age only after a damage region is declared

This commit is contained in:
2026-10-02 17:11:31 -04:00
parent 233e2f8ced
commit bb4e3e0ec6
19 changed files with 224 additions and 25 deletions
+23 -3
View File
@@ -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
@@ -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);
@@ -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
+66 -9
View File
@@ -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<decltype(support.SetDamageRegion)>(
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<std::mutex> lock(g_nativeRegistryMutex);
const auto* entry = FindSessionSurfaceLocked(session, surface);
if (entry == nullptr || entry->Window == static_cast<NativeWindowType>(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<EGLint>(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<unsigned>(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<std::mutex> 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();
+9 -3
View File
@@ -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);
@@ -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<std::recursive_mutex> lock(m_eglStateMutex);
return pVulkanRenderer ? pVulkanRenderer->CurrentDrawBufferAge() : 0;
}
@@ -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,
+52 -1
View File
@@ -28,6 +28,7 @@
#include <mutex>
#include <sstream>
#include <type_traits>
#include <unordered_set>
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<EGLSurface>& DamageRegionSurfaces() {
static auto* surfaces = new std::unordered_set<EGLSurface>();
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<std::recursive_mutex> 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) {
+1
View File
@@ -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);
@@ -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;
@@ -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);
@@ -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<void*>(eglQueryDmaBufFormatsEXT)},
{"eglSwapBuffersWithDamageKHR", reinterpret_cast<void*>(eglSwapBuffersWithDamageKHR)},
{"eglSwapBuffersWithDamageEXT", reinterpret_cast<void*>(eglSwapBuffersWithDamageEXT)},
{"eglSetDamageRegionKHR", reinterpret_cast<void*>(eglSetDamageRegionKHR)},
{"eglQueryDmaBufModifiersEXT", reinterpret_cast<void*>(eglQueryDmaBufModifiersEXT)},
{"eglCreatePlatformWindowSurface", reinterpret_cast<void*>(eglCreatePlatformWindowSurface)},
{"eglCreatePlatformPixmapSurface", reinterpret_cast<void*>(eglCreatePlatformPixmapSurface)},
+2
View File
@@ -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);
+7 -1
View File
@@ -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;
@@ -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<EGLint>(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;
@@ -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
+6 -1
View File
@@ -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;
+10
View File
@@ -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;
+8
View File
@@ -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: