[Fix] (EGL, Wayland): a swap that runs into a device loss reports it and still commits the window, instead of showing an empty frame.

This commit is contained in:
2026-10-03 07:17:08 -04:00
parent cf763b4081
commit 36190b7cc0
3 changed files with 64 additions and 12 deletions
+29 -4
View File
@@ -692,7 +692,14 @@ namespace MobileGL::MG_Impl::EGLImpl {
}
#if MOBILEGL_BUILD_DISAGGREGATED
// A swap of a lost context's frame: EGL_CONTEXT_LOST (EGL 1.5 3.2), and nothing presented.
// A Wayland window is still committed, with no new buffer (WindowSurface::CommitPendingState):
// Qt asks for a frame callback before every swap and draws the window again only once it
// has fired, so a swap that never committed froze the window for good - a Plasma panel
// that hit the loss in its own swap never redrew, not even to rebuild its context.
if (ContextIsLost(state, state->GetCurrentContext())) {
#if MOBILEGL_WAYLAND_WINDOWS
if (auto it = WaylandSurfaces().find(draw); it != WaylandSurfaces().end()) it->second->CommitPendingState();
#endif
state->SetError(EGL_CONTEXT_LOST);
return EGL_FALSE;
}
@@ -720,10 +727,28 @@ namespace MobileGL::MG_Impl::EGLImpl {
#if MOBILEGL_WAYLAND_WINDOWS
// A Wayland window shows what is attached to it: the frame is read back and attached
// BEFORE the swap, while it is still the drawable's content.
if (auto it = WaylandSurfaces().find(draw);
it != WaylandSurfaces().end() && !it->second->Present(damage, state->GetSwapInterval(dpy))) {
state->SetError(EGL_BAD_SURFACE);
return EGL_FALSE;
if (auto it = WaylandSurfaces().find(draw); it != WaylandSurfaces().end()) {
#if MOBILEGL_BUILD_DISAGGREGATED
// A loss that this very present runs into is reported here, as the one above: a
// frame of the lost device shown as if it were the window's would leave the toolkit
// with a transparent window it learns only afterwards it has to redraw.
const auto sessionLost = +[]() -> Bool {
auto* current = GetState();
return current != nullptr && ContextIsLost(current, current->GetCurrentContext());
};
#else
Bool (*const sessionLost)() = nullptr;
#endif
switch (it->second->Present(damage, state->GetSwapInterval(dpy), sessionLost)) {
case Wayland::WindowSurface::PresentResult::kPresented: break;
case Wayland::WindowSurface::PresentResult::kLost:
it->second->CommitPendingState();
state->SetError(EGL_CONTEXT_LOST);
return EGL_FALSE;
case Wayland::WindowSurface::PresentResult::kNotShown:
state->SetError(EGL_BAD_SURFACE);
return EGL_FALSE;
}
}
#endif
const Bool swapped = damage.IsFull() ? backendObject->SwapEGLBuffers(dpy, draw)
+20 -5
View File
@@ -822,9 +822,19 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
impl.useDmabuf = impl.dmabufWanted && impl.dmabuf != nullptr;
}
Bool WindowSurface::Present(const MG_Util::Damage::Region& damage, Int swapInterval) {
void WindowSurface::CommitPendingState() {
Impl& impl = *m_impl;
if (impl.window == nullptr) return false;
if (impl.window == nullptr || impl.window->surface == nullptr) return;
const Api& api = WaylandApi();
wl_proxy* surface = impl.window->surface;
api.marshalFlags(surface, kSurfaceCommit, nullptr, api.getVersion(surface), 0);
api.flush(impl.display);
}
WindowSurface::PresentResult WindowSurface::Present(const MG_Util::Damage::Region& damage, Int swapInterval,
Bool (*sessionLost)()) {
Impl& impl = *m_impl;
if (impl.window == nullptr) return PresentResult::kNotShown;
if (swapInterval != impl.loggedInterval) {
MGLOG_I("Wayland: window surface %dx%d swaps with interval %d (%s)", impl.width, impl.height,
static_cast<int>(swapInterval),
@@ -846,9 +856,12 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
if (backendObject->PresentToSharedImage(buffer->imageId, copy)) {
impl.Commit(buffer->buffer, damage);
buffer->busy = true;
return true;
return PresentResult::kPresented;
}
}
// The image went with a lost server: the window keeps its shared images (a fresh
// session replaces them, OnSessionReplaced) and shows no frame of the lost device.
if (sessionLost != nullptr && sessionLost()) return PresentResult::kLost;
MGLOG_E_ONCE("Wayland: a frame could not be presented through a linux-dmabuf shared image; the window "
"falls back to wl_shm");
impl.FallBackToShm();
@@ -857,15 +870,17 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
ShmBuffer* buffer = impl.NextFree();
if (buffer == nullptr) {
MGLOG_E_ONCE("Wayland: no wl_shm buffer could be taken for the frame; it is not shown");
return false;
return PresentResult::kNotShown;
}
// GL's rows run bottom-up and RGBA; a wl_shm buffer's run top-down and B, G, R, A.
ReadBackFrameBGRA(impl.width, impl.height, impl.scratch, static_cast<Uint8*>(buffer->map),
static_cast<SizeT>(buffer->stride));
// A read-back the device was lost under answered zeros: the buffer stays free.
if (sessionLost != nullptr && sessionLost()) return PresentResult::kLost;
impl.Commit(buffer->buffer, MG_Util::Damage::Region::Full());
buffer->busy = true;
return true;
return PresentResult::kPresented;
}
} // namespace MobileGL::MG_Impl::EGLImpl::Wayland
+15 -3
View File
@@ -75,8 +75,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
// Puts the current context's default-framebuffer frame on the window: copied into a
// shared image (or read back into a wl_shm buffer, the application's read-framebuffer and
// pack state put back exactly as they were), then attach, damage, commit. A shared-image
// present that fails moves the window to wl_shm for good. False (logged) when the frame
// did not reach the window; the swap itself goes on regardless.
// present that fails moves the window to wl_shm for good. kNotShown (logged) when the
// frame did not reach the window; the swap itself goes on regardless.
//
// `damage` (GL window coordinates, clipped to the window; Full = all of it) is what the
// frame changed. A shared image is copied only where it differs from the frame - the
@@ -85,13 +85,25 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
//
// `swapInterval` above 0 paces the swaps by the compositor's frame callbacks: this waits
// for the previous frame's before taking a buffer (FrameThrottle.h).
Bool Present(const MG_Util::Damage::Region& damage, Int swapInterval);
//
// `sessionLost` (may be null) says whether the device was lost under the frame. A frame
// that failed - or was read back - on a lost device is nothing but zeros: kLost, with no
// buffer attached and the window left where it was, so the caller reports the loss at
// this swap instead of showing a transparent window that the toolkit never redraws.
enum class PresentResult { kPresented, kNotShown, kLost };
PresentResult Present(const MG_Util::Damage::Region& damage, Int swapInterval, Bool (*sessionLost)());
// wl_egl_window_resize's effect, taken: true (with the new size) when the application gave
// its wl_egl_window a size this presentation is not at yet. The presentation adopts it -
// its next buffer is that size - and the caller resizes the drawable behind it.
Bool TakeResize(EGLint* width, EGLint* height);
// A swap that presents nothing (its context was lost) still commits the application's
// wl_surface, with no new buffer: the frame callback a toolkit asked for just before the
// swap is surface state that only a commit applies, and a toolkit that waits for it before
// drawing again would never draw again - not even to find out that it has to rebuild.
void CommitPendingState();
// The server session that allocated this window's shared images was lost and a fresh one
// replaced it: the images went with their server. Their wl_buffers are destroyed here,
// nothing is released (the fresh server never issued those ids), and the window goes