qt/vulkan: replace cross-TU forward decl with PresentSink interface

`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<PresentSink *>(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 <ruv@ruv.net>
pull/12846/head
ntomsic 2026-05-25 19:07:57 -05:00
parent 8f47e4d117
commit 002dddc9df
4 changed files with 53 additions and 49 deletions

View File

@ -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<GhosttySurface *>(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<vulkan::PresentSink*>(userdata)->presentDmabuf(...)`,
// which `GhosttySurface::presentDmabuf` (inline forwarder in the
// header) routes to `presentVulkanDmabuf` above. No cross-TU
// `extern void presentToGhosttySurface` symbol any more.)

View File

@ -13,6 +13,7 @@
#include <QWidget>
#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

View File

@ -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<PresentSink *>(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;

View File

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