diff --git a/qt/src/GhosttySurface.cpp b/qt/src/GhosttySurface.cpp index 16283e99b..d42af997a 100644 --- a/qt/src/GhosttySurface.cpp +++ b/qt/src/GhosttySurface.cpp @@ -12,6 +12,7 @@ #include "vulkan/Host.h" #endif #include "opengl/EglDmabufTarget.h" +#include "wayland/DmabufRegistry.h" #include "wayland/SubsurfacePresenter.h" // Qt private Wayland headers — give us QtWaylandClient::QWaylandWindow, @@ -138,6 +139,14 @@ GhosttySurface::GhosttySurface(ghostty_app_t app, MainWindow *owner, "of libghostty has no OpenGL fallback — exiting.\n"); std::abort(); } + // Prime the compositor dmabuf modifier registry on THIS thread + // (the GUI thread — surface ctors run there). The renderer + // thread will read it lock-free via the + // `get_supported_modifiers` platform callback. Idempotent if + // another surface already primed it. Same lifetime guarantee + // we used to achieve inside `Host::instance`'s `call_once`, + // but kept on the wayland side of the layering boundary. + ::wayland::primeDmabufModifierRegistry(); m_useVulkan = true; sc.platform_tag = GHOSTTY_PLATFORM_VULKAN; sc.platform.vulkan = vk_host->asPlatform(this); diff --git a/qt/src/vulkan/Host.cpp b/qt/src/vulkan/Host.cpp index 909f70d1a..fe05e86ff 100644 --- a/qt/src/vulkan/Host.cpp +++ b/qt/src/vulkan/Host.cpp @@ -9,7 +9,7 @@ #include #include -#include "../wayland/SubsurfacePresenter.h" +#include "../wayland/DmabufRegistry.h" namespace vulkan { @@ -249,15 +249,13 @@ Host *Host::instance() { } // candidate's destructor runs on init failure and cleans up // any partial state. - - // Eagerly prime the dmabuf modifier registry while we're - // guaranteed to be on the GUI thread (Host::instance is called - // from GhosttySurface's ctor before the renderer thread spawns). - // From here on, `wayland::supportedDmabufModifiers` is a - // lock-free read of an immutable table, safe to call from the - // renderer thread via `cbGetSupportedModifiers`. - ::wayland::primeDmabufModifierRegistry(); }); + // The dmabuf modifier registry priming used to happen here too, + // inside this `call_once`. It moved out to `GhosttySurface`'s + // ctor: registry priming is a Wayland-protocol concern, not a + // Vulkan one, and `Host::instance()` is logically about Vulkan + // setup. Co-locating both in one trampoline coupled `Host` to a + // wayland-side concern that doesn't need it. return host.get(); } diff --git a/qt/src/vulkan/Host.h b/qt/src/vulkan/Host.h index 6c9e0ea6e..add3ecf41 100644 --- a/qt/src/vulkan/Host.h +++ b/qt/src/vulkan/Host.h @@ -14,10 +14,14 @@ // dmabuf-as-importable-image export path libghostty's Vulkan // renderer uses to hand frames back to the host. // -// On first use Host::instance() also primes the process-wide -// Wayland dmabuf modifier registry (see SubsurfacePresenter) on -// the calling thread, so the renderer-thread `get_supported_modifiers` -// callback can read it without further synchronization. +// The compositor dmabuf modifier registry that this host's +// `get_supported_modifiers` callback reads is primed elsewhere +// (in `GhosttySurface`'s ctor on the GUI thread, via +// `wayland::primeDmabufModifierRegistry` from +// `qt/src/wayland/DmabufRegistry.h`). That priming is a Wayland +// concern and used to leak into `Host::instance`'s `call_once` — +// which made `Host` (a Vulkan object) responsible for a +// Wayland-protocol concern it doesn't otherwise touch. #pragma once diff --git a/qt/src/wayland/DmabufRegistry.h b/qt/src/wayland/DmabufRegistry.h new file mode 100644 index 000000000..725325e35 --- /dev/null +++ b/qt/src/wayland/DmabufRegistry.h @@ -0,0 +1,55 @@ +// Compositor dmabuf modifier registry. +// +// Process-wide read-only table of `(drm_format, [modifier])` pairs the +// compositor advertises via `zwp_linux_dmabuf_v1`. libghostty's Vulkan +// renderer queries this through the +// `ghostty_platform_vulkan_s.get_supported_modifiers` callback when +// picking a modifier the compositor will accept on attach — without +// that intersection, drivers that don't expose `COLOR_ATTACHMENT_BIT` +// for `LINEAR` (NVIDIA) can't get into Target's direct-export mode at +// all and have to fall back to the legacy CPU-readback path. +// +// Why a header of its own instead of living on +// `wayland::SubsurfacePresenter`? The presenter is per-widget; the +// registry is process-wide and read-only after a one-shot prime. They +// share `globalState()` machinery internally +// (`SubsurfacePresenter.cpp`) but their public surfaces are unrelated +// concerns. +// +// Wayland-only by project decision (the Qt frontend is Wayland-only; +// see `feedback-qt-no-x11` memory). On non-Wayland QPA both functions +// are no-ops — `primeDmabufModifierRegistry` returns immediately and +// `supportedDmabufModifiers` returns 0 — so callers can stay +// runtime-agnostic. + +#pragma once + +#include +#include + +namespace wayland { + +// Eagerly discover the compositor's dmabuf modifier list on the +// CALLING THREAD. MUST be called from the GUI thread before any +// `supportedDmabufModifiers` reader runs (typically the libghostty +// renderer thread). Safe to call multiple times — discovery happens +// exactly once via the underlying `globalState`'s latched `searched` +// flag. +// +// Idempotent no-op if the QPA isn't Wayland or the +// QPlatformNativeInterface lookup fails. +void primeDmabufModifierRegistry(); + +// Read the cached compositor-supported DRM modifiers for the given +// DRM_FORMAT_* fourcc. Returns the number of modifiers actually +// written to `out` (capped at `capacity`). Pass `out=nullptr, +// capacity=0` to query the total count. +// +// Thread-safe for readers once `primeDmabufModifierRegistry` has +// returned. Returns 0 if the registry hasn't been primed yet or the +// format isn't advertised. +std::size_t supportedDmabufModifiers(std::uint32_t drm_format, + std::uint64_t *out, + std::size_t capacity); + +} // namespace wayland diff --git a/qt/src/wayland/SubsurfacePresenter.cpp b/qt/src/wayland/SubsurfacePresenter.cpp index 681b2f2f7..64174316d 100644 --- a/qt/src/wayland/SubsurfacePresenter.cpp +++ b/qt/src/wayland/SubsurfacePresenter.cpp @@ -1,4 +1,5 @@ #include "SubsurfacePresenter.h" +#include "DmabufRegistry.h" #include #include diff --git a/qt/src/wayland/SubsurfacePresenter.h b/qt/src/wayland/SubsurfacePresenter.h index 3c1d3a081..8f534c8bd 100644 --- a/qt/src/wayland/SubsurfacePresenter.h +++ b/qt/src/wayland/SubsurfacePresenter.h @@ -6,14 +6,11 @@ // subsurface. The compositor scans the buffers out directly — no // mmap, no memcpy, no QImage, no QPainter blit on the present path. // -// Also exposes the process-wide compositor modifier registry -// (`primeDmabufModifierRegistry` / `supportedDmabufModifiers`) -// learned from zwp_linux_dmabuf_v1's format/modifier events. -// libghostty's Vulkan renderer queries this via the -// `get_supported_modifiers` platform callback to pick a modifier -// the compositor will actually accept — without that intersection, -// drivers that don't expose COLOR_ATTACHMENT for LINEAR (NVIDIA) -// can't get into Target's direct-export mode at all. +// The process-wide compositor modifier registry that used to share +// this header now lives in `DmabufRegistry.h`. The implementations +// share `globalState()` machinery in `SubsurfacePresenter.cpp` but +// the API surfaces are disjoint: presenter is per-widget, registry +// is process-wide and read-only. // // Wayland-only by project decision (the Qt frontend is Wayland-only; // see `feedback-qt-no-x11` memory). If the host isn't on a Wayland @@ -37,28 +34,6 @@ class QWindow; namespace wayland { -// Eagerly discover the compositor's globals (incl. the -// zwp_linux_dmabuf_v1 format/modifier list) on the calling thread. -// MUST be called from the GUI thread before any -// `supportedDmabufModifiers` reader runs (the renderer thread). Safe -// to call multiple times — discovery happens exactly once. -// -// Idempotent no-op if the QPA isn't Wayland or the -// QPlatformNativeInterface lookup fails. -void primeDmabufModifierRegistry(); - -// Read the cached compositor-supported DRM modifiers for the given -// DRM_FORMAT_* fourcc. Returns the number of modifiers actually -// written to `out` (capped at `capacity`). Pass `out=nullptr, -// capacity=0` to query the total count. -// -// Thread-safe for readers once `primeDmabufModifierRegistry` has -// returned. Returns 0 if the registry hasn't been primed yet or the -// format isn't advertised. -std::size_t supportedDmabufModifiers(std::uint32_t drm_format, - std::uint64_t *out, - std::size_t capacity); - class SubsurfacePresenter { public: // Build a subsurface parented to `topLevel`'s native `wl_surface`,