diff --git a/qt/src/GhosttySurface.cpp b/qt/src/GhosttySurface.cpp index 2fcb6c2d4..7ac64f836 100644 --- a/qt/src/GhosttySurface.cpp +++ b/qt/src/GhosttySurface.cpp @@ -1333,21 +1333,25 @@ void GhosttySurface::presentVulkanDmabuf( return; } // QImage holds the pixel data by copying when constructed with - // `Format_ARGB32` from a buffer with explicit stride. We then - // detach (copy()) so the QImage survives the unmap. + // `Format_ARGB32_Premultiplied` from a buffer with explicit stride. + // We then detach (copy()) so the QImage survives the unmap. // // drm_format ARGB8888 (0x34325241 = "AR24") matches QImage's - // Format_ARGB32 byte order on little-endian (B,G,R,A in memory). - // We unconditionally use ARGB32 here because the renderer currently - // emits BGRA only — extend with a format switch when other formats - // come online. + // ARGB32 byte order on little-endian (B,G,R,A in memory). + // + // We use the *premultiplied* variant because the renderer's + // fragment shaders output premultiplied alpha and the render + // target is `VK_FORMAT_B8G8R8A8_SRGB` (hardware gamma-encodes the + // linear shader output at framebuffer-write time). The bytes + // landing in this buffer are therefore sRGB-encoded premultiplied + // ARGB — exactly what Format_ARGB32_Premultiplied expects. (void)drm_format; const QImage stamped( static_cast(mapped), static_cast(width), static_cast(height), static_cast(stride), - QImage::Format_ARGB32); + QImage::Format_ARGB32_Premultiplied); QImage owned = stamped.copy(); ::munmap(mapped, bytes); diff --git a/src/renderer/Vulkan.zig b/src/renderer/Vulkan.zig index 508f6a1a1..1c1013e92 100644 --- a/src/renderer/Vulkan.zig +++ b/src/renderer/Vulkan.zig @@ -255,9 +255,17 @@ 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 + // bytes in memory would be linear and Qt (which expects sRGB + // premultiplied) would render them as if they were already gamma + // encoded — colors would look way too dark. The DRM fourcc the + // host sees is still ARGB8888; SRGB encoding is a Vulkan-side + // concern only. return try Target.init(.{ .device = devicePtr(), - .format = vk.VK_FORMAT_B8G8R8A8_UNORM, + .format = vk.VK_FORMAT_B8G8R8A8_SRGB, .width = @intCast(width), .height = @intCast(height), }); @@ -400,11 +408,11 @@ pub fn textureOptions(_: *const Vulkan) Texture.Options { // back_texture (which is BOTH sampled AND a render target). // We hand back the wider usage set so both work. The format // matches the renderer's `initTarget` choice - // (`B8G8R8A8_UNORM`) so a render → sample → render chain + // (`B8G8R8A8_SRGB`) so a render → sample → render chain // through the custom-shader pass keeps the same color format. return .{ .device = devicePtr(), - .format = vk.VK_FORMAT_B8G8R8A8_UNORM, + .format = vk.VK_FORMAT_B8G8R8A8_SRGB, .usage = vk.VK_IMAGE_USAGE_SAMPLED_BIT | vk.VK_IMAGE_USAGE_TRANSFER_DST_BIT | vk.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, diff --git a/src/renderer/vulkan/shaders.zig b/src/renderer/vulkan/shaders.zig index 879b4bcb1..9718dff4a 100644 --- a/src/renderer/vulkan/shaders.zig +++ b/src/renderer/vulkan/shaders.zig @@ -46,35 +46,13 @@ pub const source = struct { // include contents inline — same approach `opengl/shaders.zig` // uses via its `loadShaderCode`. - // DIAGNOSTIC: override bg_color.f.glsl with a hardcoded purple - // color so we can verify the pipeline + descriptor binding + - // draw recording work end-to-end without depending on the - // Uniforms.bg_color data path being correct. Once a colored - // window confirms the pipeline runs, revert to the real - // include-expanded source. - pub const bg_color_frag: [:0]const u8 = - \\#version 450 - \\layout(location = 0) out vec4 out_FragColor; - \\void main() { - \\ out_FragColor = vec4(0.5, 0.0, 0.5, 1.0); // debug: opaque purple - \\} - ; - pub const bg_color_frag_real = processIncludes(@embedFile("../shaders/glsl/bg_color.f.glsl")); + pub const bg_color_frag = processIncludes(@embedFile("../shaders/glsl/bg_color.f.glsl")); pub const bg_image_frag = processIncludes(@embedFile("../shaders/glsl/bg_image.f.glsl")); pub const bg_image_vert = processIncludes(@embedFile("../shaders/glsl/bg_image.v.glsl")); pub const cell_bg_frag = processIncludes(@embedFile("../shaders/glsl/cell_bg.f.glsl")); pub const cell_text_frag = processIncludes(@embedFile("../shaders/glsl/cell_text.f.glsl")); pub const cell_text_vert = processIncludes(@embedFile("../shaders/glsl/cell_text.v.glsl")); - // DIAGNOSTIC: inline a known-good fullscreen-triangle vertex - // shader to rule out any vulkanizeGlsl rewrite issues. - pub const full_screen_vert: [:0]const u8 = - \\#version 450 - \\void main() { - \\ vec2 pos[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0)); - \\ gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0); - \\} - ; - pub const full_screen_vert_real = processIncludes(@embedFile("../shaders/glsl/full_screen.v.glsl")); + pub const full_screen_vert = processIncludes(@embedFile("../shaders/glsl/full_screen.v.glsl")); pub const image_frag = processIncludes(@embedFile("../shaders/glsl/image.f.glsl")); pub const image_vert = processIncludes(@embedFile("../shaders/glsl/image.v.glsl")); }; @@ -565,17 +543,15 @@ pub const Shaders = struct { } errdefer device.dispatch.destroyDescriptorSetLayout(device.device, bg_color_dsl, null); - // DIAGNOSTIC: the debug bg_color shader has no inputs, so - // build the pipeline WITHOUT a descriptor set layout. The - // `bg_color_dsl` is still kept around — it gets stored in - // `Shaders.bg_color_set_layout` and torn down on deinit. + const bg_color_dsls = [_]vk.VkDescriptorSetLayout{bg_color_dsl}; const bg_color_pipeline = try Pipeline.init(.{ .device = device, + .descriptor_pool = &pool, .vertex_module = modules.full_screen_vert.handle, .fragment_module = modules.bg_color_frag.handle, .vertex_input = null, - .descriptor_set_layouts = &.{}, - .color_format = vk.VK_FORMAT_B8G8R8A8_UNORM, + .descriptor_set_layouts = &bg_color_dsls, + .color_format = vk.VK_FORMAT_B8G8R8A8_SRGB, .blending_enabled = false, .topology = vk.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, });