From 002dddc9dfba9393c96396977a3bf3d86e6a4467 Mon Sep 17 00:00:00 2001 From: ntomsic Date: Mon, 25 May 2026 19:07:57 -0500 Subject: [PATCH] qt/vulkan: replace cross-TU forward decl with PresentSink interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `vulkan::Host` no longer reaches into `GhosttySurface.cpp` via a namespace-scoped `extern void presentToGhosttySurface(...)`. The trampoline path now goes through a virtual `vulkan::PresentSink` interface declared next to `Host`: class PresentSink { public: virtual void presentDmabuf(int fd, ..., bool image_backed) = 0; }; `GhosttySurface` inherits and provides a thin override that forwards to its existing `presentVulkanDmabuf` (kept under that name so the extensive doc comments + cross-references in the rest of the file don't churn). `Host::asPlatform` now takes a `PresentSink *` instead of a `void *` — typed at the boundary; `cbPresent` does `static_cast(ud)->presentDmabuf(...)`. Drops the `Q_INVOKABLE` marker on `presentVulkanDmabuf` (only `drainVulkan` is invoked by name via QMetaObject; the present method isn't, so the marker was dead). Step 5b of 6. Co-Authored-By: claude-flow --- qt/src/GhosttySurface.cpp | 29 +++++++---------------------- qt/src/GhosttySurface.h | 17 +++++++++++++++-- qt/src/vulkan/Host.cpp | 23 +++++------------------ qt/src/vulkan/Host.h | 33 ++++++++++++++++++++++++++------- 4 files changed, 53 insertions(+), 49 deletions(-) diff --git a/qt/src/GhosttySurface.cpp b/qt/src/GhosttySurface.cpp index b474447d7..16283e99b 100644 --- a/qt/src/GhosttySurface.cpp +++ b/qt/src/GhosttySurface.cpp @@ -1836,25 +1836,10 @@ bool GhosttySurface::forceParentCommit() { return true; } -// Trampoline so `Host.cpp` doesn't need to include the full -// `GhosttySurface.h`. The forward declaration lives in -// `vulkan/Host.cpp` (namespace scope, not anonymous, so the linker -// resolves this definition). -namespace vulkan { - -void presentToGhosttySurface( - void *surface, - int dmabuf_fd, - uint32_t drm_format, - uint64_t drm_modifier, - uint32_t width, - uint32_t height, - uint32_t stride, - bool image_backed) { - if (surface == nullptr) return; - static_cast(surface)->presentVulkanDmabuf( - dmabuf_fd, drm_format, drm_modifier, width, height, stride, - image_backed); -} - -} // namespace vulkan +// (Frame delivery to GhosttySurface is now via the +// `vulkan::PresentSink` interface declared in `vulkan/Host.h`. +// `vulkan::Host`'s present-callback trampoline calls +// `static_cast(userdata)->presentDmabuf(...)`, +// which `GhosttySurface::presentDmabuf` (inline forwarder in the +// header) routes to `presentVulkanDmabuf` above. No cross-TU +// `extern void presentToGhosttySurface` symbol any more.) diff --git a/qt/src/GhosttySurface.h b/qt/src/GhosttySurface.h index 35b624d65..92d1a1482 100644 --- a/qt/src/GhosttySurface.h +++ b/qt/src/GhosttySurface.h @@ -13,6 +13,7 @@ #include #include "ghostty.h" +#include "vulkan/Host.h" namespace wayland { class SubsurfacePresenter; @@ -62,7 +63,7 @@ class OverlayScrollbar; // renderer reports image_backed=false (NVIDIA Vulkan's // legacy_copy path on this branch), the frame goes through a // mmap+memcpy+QImage+QPainter::drawImage path instead. -class GhosttySurface : public QWidget { +class GhosttySurface : public QWidget, public vulkan::PresentSink { Q_OBJECT public: @@ -175,7 +176,7 @@ public: // (zero-copy) or paints the QImage (fallback). The dropped-frame // counter `m_droppedFrames` makes any genuine queue-loss visible // (zero in the steady state). - Q_INVOKABLE void presentVulkanDmabuf( + void presentVulkanDmabuf( int dmabuf_fd, quint32 drm_format, quint64 drm_modifier, @@ -184,6 +185,18 @@ public: quint32 stride, bool image_backed); + // `vulkan::PresentSink` override. Thin forward to + // `presentVulkanDmabuf` so the existing implementation (and its + // doc comment above) stays where it is. Called by `vulkan::Host`'s + // present-callback trampoline on the libghostty renderer thread. + void presentDmabuf(int dmabuf_fd, std::uint32_t drm_format, + std::uint64_t drm_modifier, std::uint32_t width, + std::uint32_t height, std::uint32_t stride, + bool image_backed) override { + presentVulkanDmabuf(dmabuf_fd, drm_format, drm_modifier, width, + height, stride, image_backed); + } + // GUI-thread drain step: hands the most recent pending frame // either to the SubsurfacePresenter (zero-copy path) or the // QImage paint pipeline (fallback). Idempotent: returns diff --git a/qt/src/vulkan/Host.cpp b/qt/src/vulkan/Host.cpp index e6cef38ff..909f70d1a 100644 --- a/qt/src/vulkan/Host.cpp +++ b/qt/src/vulkan/Host.cpp @@ -13,20 +13,6 @@ namespace vulkan { -// Forward declaration of the entry point in `GhosttySurface.cpp` that -// receives a presented frame. Declared here at namespace scope (not -// in the anonymous namespace below) so its external definition in -// the other TU resolves at link time. -void presentToGhosttySurface( - void *surface, - int dmabuf_fd, - uint32_t drm_format, - uint64_t drm_modifier, - uint32_t width, - uint32_t height, - uint32_t stride, - bool image_backed); - namespace { constexpr const char *kRequiredDeviceExtensions[] = { @@ -136,8 +122,9 @@ void cbPresent( uint32_t stride, bool image_backed) { if (ud == nullptr) return; - ::vulkan::presentToGhosttySurface(ud, dmabuf_fd, drm_format, drm_modifier, - width, height, stride, image_backed); + static_cast(ud)->presentDmabuf( + dmabuf_fd, drm_format, drm_modifier, width, height, stride, + image_backed); } } // namespace @@ -238,9 +225,9 @@ Host::~Host() { if (m_instance != VK_NULL_HANDLE) vkDestroyInstance(m_instance, nullptr); } -ghostty_platform_vulkan_s Host::asPlatform(void *surface_userdata) const { +ghostty_platform_vulkan_s Host::asPlatform(PresentSink *sink) const { ghostty_platform_vulkan_s p{}; - p.userdata = surface_userdata; + p.userdata = sink; p.get_instance_proc_addr = cbGetInstanceProcAddr; p.instance = cbInstance; p.physical_device = cbPhysicalDevice; diff --git a/qt/src/vulkan/Host.h b/qt/src/vulkan/Host.h index 777cebe60..6c9e0ea6e 100644 --- a/qt/src/vulkan/Host.h +++ b/qt/src/vulkan/Host.h @@ -30,6 +30,25 @@ namespace vulkan { +/// Receiver for a presented dmabuf-backed frame. Implemented by +/// `GhosttySurface`; abstract so `vulkan::Host` doesn't need to +/// know about the widget type. Replaces an earlier cross-TU +/// forward declaration of a free function `presentToGhosttySurface` +/// that coupled `Host.cpp` directly to `GhosttySurface.cpp`. +class PresentSink { +public: + virtual ~PresentSink() = default; + /// Hand off a rendered frame. Called on the libghostty renderer + /// thread; the implementation is responsible for marshalling to + /// whatever thread it composites on. The fd is borrowed for the + /// duration of the call — implementations that need to retain + /// it must `dup()`. + virtual void presentDmabuf(int dmabuf_fd, std::uint32_t drm_format, + std::uint64_t drm_modifier, + std::uint32_t width, std::uint32_t height, + std::uint32_t stride, bool image_backed) = 0; +}; + /// Process-wide Vulkan setup. One per Ghastty process; threadsafe /// to call `instance()` from anywhere (constructs once via /// std::call_once on first access). @@ -40,13 +59,13 @@ public: /// repeated lookups are cheap. static Host *instance(); - /// Build a `ghostty_platform_vulkan_s` callback struct populated - /// with this host's handles. `surface_userdata` is round-tripped - /// through as the `userdata` field — used by the `present` - /// callback to identify which `GhosttySurface` the dmabuf is for. - /// The other handle-lookup callbacks ignore it and route through - /// `Host::instance()`. - ghostty_platform_vulkan_s asPlatform(void *surface_userdata) const; + /// Build a `ghostty_platform_vulkan_s` callback struct whose + /// `present` callback delivers frames to `sink`. `sink` must + /// outlive the lifetime of any libghostty surface that was + /// configured with the returned platform struct. Other callbacks + /// (handle lookups, modifier registry) ignore `sink` and route + /// through the process singleton. + ghostty_platform_vulkan_s asPlatform(PresentSink *sink) const; VkInstance vkInstance() const { return m_instance; } VkPhysicalDevice vkPhysicalDevice() const { return m_physicalDevice; }