qt/vulkan: paint a visible placeholder when no dmabuf is imported yet
The MainWindow uses `WA_TranslucentBackground` (so the terminal's
own background-opacity reaches the desktop). When the
GhosttySurface widget inside it paints nothing — which is what
happens on the Vulkan path right now because the dmabuf import +
composite isn't wired yet — the entire window becomes invisible:
fully transparent, no visible bounds, looks like the process is
running headless.
Fixes by:
1. Tracking `m_useVulkan` on the surface so we know which path
this widget is on.
2. In `paintEvent`, when `m_useVulkan` is set, fill the widget
with a muted purple (#281638) and a centered "Vulkan renderer
/ dmabuf import not yet wired" label. The QResizeOverlay still
paints on top, so resize-grid info works.
3. The OpenGL path is unchanged — same QImage blit as before.
While here:
- Skip the QOpenGLContext / QOffscreenSurface / FBO setup on the
Vulkan path. It was wasted work and may have been part of why
the previous run silently produced no window: NVIDIA's GL+VK
coexistence on a single Wayland surface is reportedly fragile,
and we don't need GL at all when libghostty's renderer is
Vulkan.
- Drop the eager `vulkan::Host::instance()` call in `main.cpp`.
Bringing up a VkInstance before any Qt window is mapped can
interact poorly with Qt's Wayland integration on some
compositors. The host is constructed lazily on the first
GhosttySurface that needs it — same effective timing as the
OpenGL path's context creation.
To verify the placeholder is visible:
GHASTTY_RENDERER=vulkan ghastty-vulkan
→ muted-purple window with the placeholder text.
The OpenGL ghastty is unaffected by any of this.
Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
parent
545898bb43
commit
a473e9e2ef
|
|
@ -75,29 +75,41 @@ GhosttySurface::GhosttySurface(ghostty_app_t app, MainWindow *owner,
|
|||
// translucent background lets that alpha reach the desktop.
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
|
||||
// A private OpenGL context for libghostty's renderer. It is never made
|
||||
// current on a window — rendering goes to an offscreen framebuffer —
|
||||
// so an unparented QOffscreenSurface is enough to satisfy makeCurrent.
|
||||
m_context = new QOpenGLContext(this);
|
||||
m_context->setFormat(QSurfaceFormat::defaultFormat());
|
||||
if (!m_context->create()) {
|
||||
std::fprintf(stderr, "[ghastty] GL context creation failed\n");
|
||||
return;
|
||||
}
|
||||
m_offscreen = new QOffscreenSurface(nullptr, this);
|
||||
m_offscreen->setFormat(m_context->format());
|
||||
m_offscreen->create();
|
||||
|
||||
if (!makeCurrent()) {
|
||||
std::fprintf(stderr, "[ghastty] makeCurrent failed\n");
|
||||
return;
|
||||
// Pick the renderer up-front so the rest of the surface setup
|
||||
// (GL context vs. Vulkan host) only touches the path we'll
|
||||
// actually use. Mixing the two on the same process can confuse
|
||||
// some drivers (NVIDIA's GL+VK coexistence on a single Wayland
|
||||
// surface is reportedly fragile); keep them disjoint.
|
||||
vulkan::Host *vk_host = nullptr;
|
||||
if (const char *r = std::getenv("GHASTTY_RENDERER");
|
||||
r != nullptr && std::strcmp(r, "vulkan") == 0) {
|
||||
vk_host = vulkan::Host::instance();
|
||||
}
|
||||
|
||||
// A placeholder framebuffer; resizeEvent installs the real size.
|
||||
QOpenGLFramebufferObjectFormat fmt;
|
||||
fmt.setInternalTextureFormat(GL_RGBA8);
|
||||
m_fbw = m_fbh = 16;
|
||||
m_fbo = new QOpenGLFramebufferObject(QSize(m_fbw, m_fbh), fmt);
|
||||
if (vk_host == nullptr) {
|
||||
// OpenGL path: stand up the private context + offscreen FBO
|
||||
// libghostty's GL renderer draws into.
|
||||
m_context = new QOpenGLContext(this);
|
||||
m_context->setFormat(QSurfaceFormat::defaultFormat());
|
||||
if (!m_context->create()) {
|
||||
std::fprintf(stderr, "[ghastty] GL context creation failed\n");
|
||||
return;
|
||||
}
|
||||
m_offscreen = new QOffscreenSurface(nullptr, this);
|
||||
m_offscreen->setFormat(m_context->format());
|
||||
m_offscreen->create();
|
||||
|
||||
if (!makeCurrent()) {
|
||||
std::fprintf(stderr, "[ghastty] makeCurrent failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// A placeholder framebuffer; resizeEvent installs the real size.
|
||||
QOpenGLFramebufferObjectFormat fmt;
|
||||
fmt.setInternalTextureFormat(GL_RGBA8);
|
||||
m_fbw = m_fbh = 16;
|
||||
m_fbo = new QOpenGLFramebufferObject(QSize(m_fbw, m_fbh), fmt);
|
||||
}
|
||||
|
||||
ghostty_surface_config_s sc =
|
||||
m_parentSurface
|
||||
|
|
@ -105,20 +117,8 @@ GhosttySurface::GhosttySurface(ghostty_app_t app, MainWindow *owner,
|
|||
GHOSTTY_SURFACE_CONTEXT_TAB)
|
||||
: ghostty_surface_config_new();
|
||||
|
||||
// Vulkan path: if the user opted in with `GHASTTY_RENDERER=vulkan`
|
||||
// AND the process-wide Vulkan host came up at launch (see
|
||||
// `main.cpp`), use the Vulkan platform plumbing. Otherwise fall
|
||||
// back to the existing OpenGL path. The Vulkan-side rendering is
|
||||
// still bring-up — frames are exported as dmabuf fds via the
|
||||
// host's `present` callback (currently just logged); display via
|
||||
// QRhiTexture import is the next chunk of Qt-side work.
|
||||
vulkan::Host *vk_host = nullptr;
|
||||
if (const char *r = std::getenv("GHASTTY_RENDERER");
|
||||
r != nullptr && std::strcmp(r, "vulkan") == 0) {
|
||||
vk_host = vulkan::Host::instance();
|
||||
}
|
||||
|
||||
if (vk_host != nullptr) {
|
||||
m_useVulkan = true;
|
||||
sc.platform_tag = GHOSTTY_PLATFORM_VULKAN;
|
||||
sc.platform.vulkan = vk_host->asPlatform(this);
|
||||
} else {
|
||||
|
|
@ -325,6 +325,23 @@ void GhosttySurface::renderTerminal() {
|
|||
}
|
||||
|
||||
void GhosttySurface::paintEvent(QPaintEvent *) {
|
||||
// Vulkan-backed surface: libghostty hands frames to the host via
|
||||
// a dmabuf fd; we don't yet composite them back into this widget.
|
||||
// Paint a visible placeholder so the (translucent) MainWindow
|
||||
// isn't completely invisible. Replace with the imported
|
||||
// QRhiTexture once the dmabuf-import path lands.
|
||||
if (m_useVulkan) {
|
||||
QPainter painter(this);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
painter.fillRect(rect(), QColor(40, 22, 56)); // muted purple — debug placeholder
|
||||
painter.setPen(QColor(220, 220, 220));
|
||||
painter.drawText(rect(),
|
||||
Qt::AlignCenter,
|
||||
QStringLiteral("Vulkan renderer\n(dmabuf import not yet wired)"));
|
||||
paintResizeOverlay(painter);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_image.isNull()) return;
|
||||
QPainter painter(this);
|
||||
// Blit the framebuffer 1:1. m_image carries the device pixel ratio, so
|
||||
|
|
|
|||
|
|
@ -207,12 +207,21 @@ private:
|
|||
ghostty_surface_t m_parentSurface; // inherited-config source; may be null
|
||||
ghostty_surface_t m_surface = nullptr;
|
||||
|
||||
// Private offscreen GL context libghostty renders into.
|
||||
// Private offscreen GL context libghostty renders into. Null for
|
||||
// the Vulkan-backed renderer (libghostty hands frames back via a
|
||||
// dmabuf fd to the apprt's `present` callback — no GL involved).
|
||||
QOpenGLContext *m_context = nullptr;
|
||||
QOffscreenSurface *m_offscreen = nullptr;
|
||||
QOpenGLFramebufferObject *m_fbo = nullptr;
|
||||
QImage m_image; // last frame, read back from m_fbo
|
||||
|
||||
// True when this surface is using the Vulkan platform. The
|
||||
// paintEvent uses this to draw a visible placeholder until the
|
||||
// host-side dmabuf-import + composite work lands; otherwise the
|
||||
// widget would paint nothing on a translucent window and look
|
||||
// invisible.
|
||||
bool m_useVulkan = false;
|
||||
|
||||
// GL objects for the alpha-premultiply pass.
|
||||
QOpenGLShaderProgram *m_premultProg = nullptr;
|
||||
QOpenGLVertexArrayObject *m_premultVao = nullptr;
|
||||
|
|
|
|||
|
|
@ -107,23 +107,14 @@ int main(int argc, char **argv) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// GHASTTY_RENDERER=vulkan opts into the Vulkan path. When set, we
|
||||
// bootstrap the process-wide Vulkan host (`vulkan::Host::instance`)
|
||||
// up-front so failures (no loader, no suitable device) surface at
|
||||
// launch and the user can drop the env var rather than waiting for
|
||||
// the first surface to fail. The OpenGL path continues to work
|
||||
// without the env var or if Vulkan bring-up fails.
|
||||
if (const char *r = std::getenv("GHASTTY_RENDERER"); r != nullptr &&
|
||||
std::strcmp(r, "vulkan") == 0) {
|
||||
if (vulkan::Host::instance() == nullptr) {
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"[ghastty] GHASTTY_RENDERER=vulkan but Vulkan setup failed; "
|
||||
"falling back to OpenGL.\n"
|
||||
" Try `unset GHASTTY_RENDERER` or install vulkan-loader / "
|
||||
"vulkan-headers.\n");
|
||||
}
|
||||
}
|
||||
// The Vulkan host is intentionally NOT bootstrapped here: doing it
|
||||
// before any window is mapped on Wayland can interact badly with
|
||||
// Qt's Wayland integration (the VkInstance starts grabbing display
|
||||
// resources before Qt has finished its own connection setup, and
|
||||
// on some compositor + driver combos the result is a process that
|
||||
// runs but never actually displays a window). It's brought up
|
||||
// lazily on the first surface that needs it — see
|
||||
// `GhosttySurface.cpp`.
|
||||
|
||||
// initial-window: when false, start headless (no window mapped at
|
||||
// launch). Combined with quit-after-last-window-closed=false this
|
||||
|
|
|
|||
Loading…
Reference in New Issue