diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 094f0399..cd7f9166 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -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) diff --git a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp index 8943ee8c..2a258762 100644 --- a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp +++ b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp @@ -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(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(buffer->map), static_cast(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 diff --git a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h index 04e7ac69..05f7aac8 100644 --- a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h +++ b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h @@ -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