qt/wayland: split DmabufRegistry header out of SubsurfacePresenter

`SubsurfacePresenter.h` used to expose two unrelated APIs: the
per-widget presenter class AND the process-wide registry free
functions (`primeDmabufModifierRegistry`, `supportedDmabufModifiers`).
Moves the latter pair into `qt/src/wayland/DmabufRegistry.h` so
each header owns one concern.

Implementations stay in `SubsurfacePresenter.cpp` — they share the
`globalState()` machinery for Wayland-globals discovery, and tearing
that apart would be deeper surgery without a payoff. The split is
header-only on the public API side, which is what the review called
for: presenter is per-widget, registry is process-wide and read-only,
the headers should reflect that.

Also: the dmabuf registry priming moves out of `vulkan::Host::instance`
and into `GhosttySurface`'s ctor (still on the GUI thread, still
called once before the renderer thread is spawned). `Host` is a
Vulkan-side singleton and shouldn't be responsible for a Wayland
priming concern that's only loosely related.

Step 5c of 6.

Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
ntomsic 2026-05-25 19:10:35 -05:00
parent 002dddc9df
commit 85ebee27ce
6 changed files with 85 additions and 43 deletions

View File

@ -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);

View File

@ -9,7 +9,7 @@
#include <optional>
#include <vector>
#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();
}

View File

@ -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

View File

@ -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 <cstddef>
#include <cstdint>
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

View File

@ -1,4 +1,5 @@
#include "SubsurfacePresenter.h"
#include "DmabufRegistry.h"
#include <algorithm>
#include <cstdio>

View File

@ -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`,