feat(window): a destructor for the native backends

A Window could be created but never destroyed: the constructor's command
buffers, semaphores, fences, swapchain and Vulkan surface, and on Wayland
the viewport, fractional scale, decoration, xdg toplevel, xdg surface and
wl_surface stayed alive, and Device::windows kept a dangling pointer the
pointer listeners still compared against. An app that hides its window
and shows it again — a resident messaging app closing to a notification
— needs the window to go for real.

The destructor waits for the device, frees and destroys the Vulkan objects
in dependency order (the swapchain before the surface), releases the
Wayland objects from the cursor down to the wl_surface, flushes, and
removes the window from Device::windows and the focus pointers. The
decoration object the constructor used to leak is a member now. The frame
callback member is left alone: wl_surface_frame_done destroys the callback
it is handed and re-arms through its own local, so the member may name a
freed one.
This commit is contained in:
Jorijn van der Graaf 2026-10-18 00:40:25 +02:00
commit b412640fac
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
2 changed files with 55 additions and 5 deletions

View file

@ -110,16 +110,19 @@ export namespace Crafter {
Window(Window&) = delete;
Window(Window&&) = delete;
Window& operator=(const Window&) = delete;
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
// DOM mode keeps a process-global pointer to the live Window so
// the JS bridge can deliver document/window events back. If the
// Window is destroyed (e.g. it was stack-allocated in main and
// main returned), the global must be cleared so subsequent
// browser callbacks become no-ops instead of dereferencing
// freed memory. Native builds have nothing to clean up at the
// Window level (Vulkan resources teardown is the user's job).
// freed memory. Native builds release what the constructor made:
// after a device wait, the command buffers, semaphores and fences,
// the swapchain and the Vulkan surface, then (Wayland) the cursor,
// viewport, fractional scale, decoration, xdg toplevel, xdg surface
// and wl_surface, and the window leaves Device::windows and the
// focus pointers — so a window can be closed and another created,
// which a resident app does on every hide and show.
~Window();
#endif
// Enter the platform event loop and never return. In DOM mode this
// hands the loop to requestAnimationFrame and _Exit(0)s so the wasm
@ -191,6 +194,7 @@ export namespace Crafter {
std::int32_t pendingLogicalWidth = 0;
std::int32_t pendingLogicalHeight = 0;
xdg_toplevel* xdgToplevel = nullptr;
zxdg_toplevel_decoration_v1* decoration = nullptr;
wp_viewport* wpViewport = nullptr;
wl_surface* surface = nullptr;
xdg_surface* xdgSurface = nullptr;