renderer/vulkan: per-surface platform routing for splits/tabs
New surfaces (splits, tabs, new windows) showed the placeholder forever and never received their first dmabuf frame. The frames were arriving — just at the wrong window. Root cause: `Device` is process-global (shared across surfaces, as intended) and it caches the `Platform.Vulkan` callbacks given to its first init. Those callbacks include `userdata`, which is the `GhosttySurface *` the `present` callback routes the dmabuf to. So every surface's renderer was calling `present(userdata=surface_1)`, even when the frame belonged to surface_2 — dmabuf frames landed in surface_1's `m_pending`, and surface_2 sat at its placeholder. Fix: - `Target.Options` gains a `platform: ?Platform.Vulkan` field with the SAME shape as `Device.platform`, but per-surface. `present()` uses it when set; falls back to the singleton's copy otherwise (for the smoke test, which has no apprt surface). - `Vulkan.initTarget` reaches through `self.rt_surface.platform` to pull the surface's own platform callbacks (correct `userdata`) and passes them to `Target.init`. - `surfacePlatform()` helper isolates the apprt-tag match so non-Vulkan platforms (smoke test, OpenGL surfaces) cleanly resolve to null. The placeholder still briefly flashes when a new surface opens — that's the 'awaiting first dmabuf frame' painting before libghostty emits its initial render — and is expected behavior. Co-Authored-By: claude-flow <ruv@ruv.net>pull/12846/head
parent
ab14f8f214
commit
0442416ac8
|
|
@ -254,7 +254,6 @@ pub fn initShaders(
|
|||
}
|
||||
|
||||
pub fn initTarget(self: *const Vulkan, width: usize, height: usize) !Target {
|
||||
_ = self;
|
||||
// SRGB format so the hardware gamma-encodes the linear premultiplied
|
||||
// shader output at framebuffer-write time. The renderer's shaders
|
||||
// produce linear premultiplied alpha; without an sRGB format the
|
||||
|
|
@ -263,14 +262,35 @@ pub fn initTarget(self: *const Vulkan, width: usize, height: usize) !Target {
|
|||
// encoded — colors would look way too dark. The DRM fourcc the
|
||||
// host sees is still ARGB8888; SRGB encoding is a Vulkan-side
|
||||
// concern only.
|
||||
//
|
||||
// Per-surface platform: pulled from rt_surface so the `present`
|
||||
// callback's `userdata` points at THIS surface's window. The
|
||||
// process-global Device has its own `platform` copy from
|
||||
// whichever surface first initialized it; splits and tabs would
|
||||
// otherwise route their dmabuf frames to the wrong window.
|
||||
const platform = surfacePlatform(self.rt_surface);
|
||||
return try Target.init(.{
|
||||
.device = devicePtr(),
|
||||
.format = vk.VK_FORMAT_B8G8R8A8_SRGB,
|
||||
.width = @intCast(width),
|
||||
.height = @intCast(height),
|
||||
.platform = platform,
|
||||
});
|
||||
}
|
||||
|
||||
/// Extract the Vulkan platform callbacks from a surface, when the
|
||||
/// surface was created with the Vulkan platform tag. Returns null
|
||||
/// otherwise (smoke test / OpenGL surfaces).
|
||||
fn surfacePlatform(rt_surface: *apprt.Surface) ?apprt.embedded.Platform.Vulkan {
|
||||
return switch (apprt.runtime) {
|
||||
else => null,
|
||||
apprt.embedded => switch (rt_surface.platform) {
|
||||
.vulkan => |p| p,
|
||||
else => null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn surfaceSize(self: *const Vulkan) !struct { width: u32, height: u32 } {
|
||||
const size = self.rt_surface.size;
|
||||
return .{ .width = size.width, .height = size.height };
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const Self = @This();
|
|||
const std = @import("std");
|
||||
const vk = @import("vulkan").c;
|
||||
|
||||
const apprt = @import("../../apprt.zig");
|
||||
const Device = @import("Device.zig");
|
||||
|
||||
const log = std.log.scoped(.vulkan);
|
||||
|
|
@ -51,6 +52,15 @@ pub const Options = struct {
|
|||
/// defaults (`COLOR_ATTACHMENT_BIT | SAMPLED_BIT |
|
||||
/// TRANSFER_SRC_BIT`). Rarely needed.
|
||||
extra_usage: vk.VkImageUsageFlags = 0,
|
||||
|
||||
/// Per-surface platform callbacks. `Device.platform` is also a
|
||||
/// `Platform.Vulkan`, but it's the singleton's copy — its
|
||||
/// `userdata` points at whichever surface initialized the
|
||||
/// device first. Splits/tabs share the device but each gets its
|
||||
/// own platform with the right `userdata`, so `present()` reaches
|
||||
/// the right window. Falls back to `device.platform` when
|
||||
/// null (e.g. smoke test).
|
||||
platform: ?apprt.embedded.Platform.Vulkan = null,
|
||||
};
|
||||
|
||||
pub const Error = error{
|
||||
|
|
@ -61,6 +71,10 @@ pub const Error = error{
|
|||
|
||||
device: *const Device,
|
||||
|
||||
/// Per-surface platform — see `Options.platform`. Null means "use
|
||||
/// `device.platform`" (the singleton's copy from the first surface).
|
||||
platform: ?apprt.embedded.Platform.Vulkan = null,
|
||||
|
||||
// ---- render image (OPTIMAL, internal) -------------------------------
|
||||
image: vk.VkImage,
|
||||
image_memory: vk.VkDeviceMemory,
|
||||
|
|
@ -250,6 +264,7 @@ pub fn init(opts: Options) Error!Self {
|
|||
|
||||
return .{
|
||||
.device = dev,
|
||||
.platform = opts.platform,
|
||||
.image = image,
|
||||
.image_memory = image_memory,
|
||||
.view = view,
|
||||
|
|
@ -375,8 +390,13 @@ pub fn recordCopyToDmabuf(self: *Self, cb: vk.VkCommandBuffer) void {
|
|||
}
|
||||
|
||||
pub fn present(self: *const Self) void {
|
||||
self.device.platform.present(
|
||||
self.device.platform.userdata,
|
||||
// Prefer the per-surface platform — its `userdata` points at THIS
|
||||
// surface's GhosttySurface, so present reaches the right window.
|
||||
// Fall back to the device's singleton copy when no platform was
|
||||
// attached (only the smoke test does this).
|
||||
const platform = if (self.platform) |p| p else self.device.platform;
|
||||
platform.present(
|
||||
platform.userdata,
|
||||
self.fd,
|
||||
self.drm_format,
|
||||
self.drm_modifier,
|
||||
|
|
|
|||
Loading…
Reference in New Issue