mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 13:30:29 +00:00
renderer: image can draw overlays
This commit is contained in:
@@ -7,6 +7,7 @@ const terminal = @import("../terminal/main.zig");
|
||||
const size = @import("size.zig");
|
||||
const Size = size.Size;
|
||||
const CellSize = size.CellSize;
|
||||
const Image = @import("image.zig").Image;
|
||||
|
||||
/// The surface we're drawing our overlay to.
|
||||
surface: z2d.Surface,
|
||||
@@ -46,6 +47,16 @@ pub fn deinit(self: *Overlay, alloc: Allocator) void {
|
||||
self.surface.deinit(alloc);
|
||||
}
|
||||
|
||||
/// Returns a pending image that can be used to copy, convert, upload, etc.
|
||||
pub fn pendingImage(self: *const Overlay) Image.Pending {
|
||||
return .{
|
||||
.width = @intCast(self.surface.getWidth()),
|
||||
.height = @intCast(self.surface.getHeight()),
|
||||
.pixel_format = .rgba,
|
||||
.data = @ptrCast(self.surface.image_surface_rgba.buf.ptr),
|
||||
};
|
||||
}
|
||||
|
||||
/// Add rectangles around continguous hyperlinks in the render state.
|
||||
///
|
||||
/// Note: this currently doesn't take into account unique hyperlink IDs
|
||||
|
||||
@@ -17,6 +17,7 @@ const noMinContrast = cellpkg.noMinContrast;
|
||||
const constraintWidth = cellpkg.constraintWidth;
|
||||
const isCovering = cellpkg.isCovering;
|
||||
const rowNeverExtendBg = @import("row.zig").neverExtendBg;
|
||||
const Overlay = @import("Overlay.zig");
|
||||
const imagepkg = @import("image.zig");
|
||||
const ImageState = imagepkg.State;
|
||||
const shadertoy = @import("shadertoy.zig");
|
||||
@@ -1282,28 +1283,13 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
// Reset our dirty state after updating.
|
||||
defer self.terminal_state.dirty = .false;
|
||||
|
||||
// Rebuild the overlay image set.
|
||||
overlay: {
|
||||
const alloc = arena_alloc;
|
||||
|
||||
// Create a surface that is the size of the entire screen,
|
||||
// including padding. It is transparent, since we'll overlay
|
||||
// it on top of our screen.
|
||||
var overlay: Overlay = self.rebuildOverlay(alloc) catch |err| {
|
||||
log.warn("error rebuilding overlay surface err={}", .{err});
|
||||
break :overlay;
|
||||
};
|
||||
defer overlay.deinit(alloc);
|
||||
|
||||
// Grab our mutex so we can upload some images.
|
||||
self.draw_mutex.lock();
|
||||
defer self.draw_mutex.unlock();
|
||||
|
||||
// IMPORTANT: This must be done AFTER kitty graphics
|
||||
// are setup because Kitty graphics will clear all our
|
||||
// "unused" images and our overlay will appear unused since
|
||||
// its not part of the Kitty state.
|
||||
}
|
||||
// Rebuild the overlay image if we have one.
|
||||
const overlay: ?Overlay = self.rebuildOverlay(
|
||||
arena_alloc,
|
||||
) catch |err| overlay: {
|
||||
log.warn("error rebuilding overlay surface err={}", .{err});
|
||||
break :overlay null;
|
||||
};
|
||||
|
||||
// Acquire the draw mutex for all remaining state updates.
|
||||
{
|
||||
@@ -1354,6 +1340,16 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
else => {},
|
||||
};
|
||||
|
||||
// Prepare our overlay image for upload (or unload). This
|
||||
// has to use our general allocator since it modifies
|
||||
// state that survives frames.
|
||||
self.images.overlayUpdate(
|
||||
self.alloc,
|
||||
overlay,
|
||||
) catch |err| {
|
||||
log.warn("error updating overlay images err={}", .{err});
|
||||
};
|
||||
|
||||
// Update custom shader uniforms that depend on terminal state.
|
||||
self.updateCustomShaderUniformsFromState();
|
||||
}
|
||||
@@ -1608,6 +1604,15 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
&pass,
|
||||
.kitty_above_text,
|
||||
);
|
||||
|
||||
// Debug overlay. We do this before any custom shader state
|
||||
// because our debug overlay is aligned with the grid.
|
||||
self.images.draw(
|
||||
&self.api,
|
||||
self.shaders.pipelines.image,
|
||||
&pass,
|
||||
.overlay,
|
||||
);
|
||||
}
|
||||
|
||||
// If we have custom shaders, then we render them.
|
||||
@@ -2202,8 +2207,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
||||
}
|
||||
}
|
||||
|
||||
const Overlay = @import("Overlay.zig");
|
||||
|
||||
fn rebuildOverlay(self: *Self, alloc: Allocator) !Overlay {
|
||||
var overlay: Overlay = try .init(alloc, self.size);
|
||||
overlay.highlightHyperlinks(alloc, &self.terminal_state);
|
||||
|
||||
@@ -8,6 +8,7 @@ const Renderer = @import("../renderer.zig").Renderer;
|
||||
const GraphicsAPI = Renderer.API;
|
||||
const Texture = GraphicsAPI.Texture;
|
||||
const CellSize = @import("size.zig").CellSize;
|
||||
const Overlay = @import("Overlay.zig");
|
||||
|
||||
const log = std.log.scoped(.renderer_image);
|
||||
|
||||
@@ -32,12 +33,16 @@ pub const State = struct {
|
||||
/// on frame builds and are generally more expensive to handle.
|
||||
kitty_virtual: bool,
|
||||
|
||||
/// Overlays
|
||||
overlay_placements: std.ArrayListUnmanaged(Placement),
|
||||
|
||||
pub const empty: State = .{
|
||||
.images = .empty,
|
||||
.kitty_placements = .empty,
|
||||
.kitty_bg_end = 0,
|
||||
.kitty_text_end = 0,
|
||||
.kitty_virtual = false,
|
||||
.overlay_placements = .empty,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *State, alloc: Allocator) void {
|
||||
@@ -47,6 +52,7 @@ pub const State = struct {
|
||||
self.images.deinit(alloc);
|
||||
}
|
||||
self.kitty_placements.deinit(alloc);
|
||||
self.overlay_placements.deinit(alloc);
|
||||
}
|
||||
|
||||
/// Upload any images to the GPU that need to be uploaded,
|
||||
@@ -88,6 +94,7 @@ pub const State = struct {
|
||||
kitty_below_bg,
|
||||
kitty_below_text,
|
||||
kitty_above_text,
|
||||
overlay,
|
||||
};
|
||||
|
||||
/// Draw the given named set of placements.
|
||||
@@ -105,6 +112,7 @@ pub const State = struct {
|
||||
.kitty_below_bg => self.kitty_placements.items[0..self.kitty_bg_end],
|
||||
.kitty_below_text => self.kitty_placements.items[self.kitty_bg_end..self.kitty_text_end],
|
||||
.kitty_above_text => self.kitty_placements.items[self.kitty_text_end..],
|
||||
.overlay => self.overlay_placements.items,
|
||||
};
|
||||
|
||||
for (placements) |p| {
|
||||
@@ -170,6 +178,57 @@ pub const State = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update our overlay state. Null value deletes any existing overlay.
|
||||
pub fn overlayUpdate(
|
||||
self: *State,
|
||||
alloc: Allocator,
|
||||
overlay_: ?Overlay,
|
||||
) !void {
|
||||
const overlay = overlay_ orelse {
|
||||
// If we don't have an overlay, remove any existing one.
|
||||
if (self.images.getPtr(.overlay)) |data| {
|
||||
data.image.markForUnload();
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// For transmit time we always just use the current time
|
||||
// and overwrite the overlay.
|
||||
const transmit_time = try std.time.Instant.now();
|
||||
|
||||
// Ensure we have space for our overlay placement. Do this before
|
||||
// we upload our image so we don't have to deal with cleaning
|
||||
// that up.
|
||||
self.overlay_placements.clearRetainingCapacity();
|
||||
try self.overlay_placements.ensureUnusedCapacity(alloc, 1);
|
||||
|
||||
// Setup our image.
|
||||
const pending = overlay.pendingImage();
|
||||
try self.prepImage(
|
||||
alloc,
|
||||
.overlay,
|
||||
transmit_time,
|
||||
pending,
|
||||
);
|
||||
errdefer comptime unreachable;
|
||||
|
||||
// Setup our placement
|
||||
self.overlay_placements.appendAssumeCapacity(.{
|
||||
.image_id = .overlay,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.z = 0,
|
||||
.width = pending.width,
|
||||
.height = pending.height,
|
||||
.cell_offset_x = 0,
|
||||
.cell_offset_y = 0,
|
||||
.source_x = 0,
|
||||
.source_y = 0,
|
||||
.source_width = pending.width,
|
||||
.source_height = pending.height,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if the Kitty graphics state requires an update based
|
||||
/// on the terminal state and our internal state.
|
||||
///
|
||||
@@ -332,7 +391,7 @@ pub const State = struct {
|
||||
text_end orelse @intCast(self.kitty_placements.items.len);
|
||||
}
|
||||
|
||||
const PrepKittyImageError = error{
|
||||
const PrepImageError = error{
|
||||
OutOfMemory,
|
||||
ImageConversionError,
|
||||
};
|
||||
@@ -347,7 +406,7 @@ pub const State = struct {
|
||||
bot_y: u32,
|
||||
image: *const terminal.kitty.graphics.Image,
|
||||
p: *const terminal.kitty.graphics.ImageStorage.Placement,
|
||||
) PrepKittyImageError!void {
|
||||
) PrepImageError!void {
|
||||
// Get the rect for the placement. If this placement doesn't have
|
||||
// a rect then its virtual or something so skip it.
|
||||
const rect = p.rect(image.*, t) orelse return;
|
||||
@@ -409,7 +468,7 @@ pub const State = struct {
|
||||
t: *const terminal.Terminal,
|
||||
p: *const terminal.kitty.graphics.unicode.Placement,
|
||||
cell_size: CellSize,
|
||||
) PrepKittyImageError!void {
|
||||
) PrepImageError!void {
|
||||
const storage = &t.screens.active.kitty_images;
|
||||
const image = storage.imageById(p.image_id) orelse {
|
||||
log.warn(
|
||||
@@ -461,34 +520,32 @@ pub const State = struct {
|
||||
});
|
||||
}
|
||||
|
||||
/// Prepare the provided image for upload to the GPU by copying its
|
||||
/// data with our allocator and setting it to the pending state.
|
||||
fn prepKittyImage(
|
||||
/// Prepare an image for upload to the GPU.
|
||||
fn prepImage(
|
||||
self: *State,
|
||||
alloc: Allocator,
|
||||
image: *const terminal.kitty.graphics.Image,
|
||||
) PrepKittyImageError!void {
|
||||
id: Id,
|
||||
transmit_time: std.time.Instant,
|
||||
pending: Image.Pending,
|
||||
) PrepImageError!void {
|
||||
// If this image exists and its transmit time is the same we assume
|
||||
// it is the identical image so we don't need to send it to the GPU.
|
||||
const gop = try self.images.getOrPut(
|
||||
alloc,
|
||||
.{ .kitty = image.id },
|
||||
);
|
||||
const gop = try self.images.getOrPut(alloc, id);
|
||||
if (gop.found_existing and
|
||||
gop.value_ptr.transmit_time.order(image.transmit_time) == .eq)
|
||||
gop.value_ptr.transmit_time.order(transmit_time) == .eq)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy the data into the pending state.
|
||||
// Copy the data so we own it.
|
||||
const data = if (alloc.dupe(
|
||||
u8,
|
||||
image.data,
|
||||
pending.dataSlice(),
|
||||
)) |v| v else |_| {
|
||||
if (!gop.found_existing) {
|
||||
// If this is a new entry we can just remove it since it
|
||||
// was never sent to the GPU.
|
||||
_ = self.images.remove(.{ .kitty = image.id });
|
||||
_ = self.images.remove(id);
|
||||
} else {
|
||||
// If this was an existing entry, it is invalid and
|
||||
// we must unload it.
|
||||
@@ -504,15 +561,9 @@ pub const State = struct {
|
||||
// Store it in the map
|
||||
const new_image: Image = .{
|
||||
.pending = .{
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
.pixel_format = switch (image.format) {
|
||||
.gray => .gray,
|
||||
.gray_alpha => .gray_alpha,
|
||||
.rgb => .rgb,
|
||||
.rgba => .rgba,
|
||||
.png => unreachable, // should be decoded by now
|
||||
},
|
||||
.width = pending.width,
|
||||
.height = pending.height,
|
||||
.pixel_format = pending.pixel_format,
|
||||
.data = data.ptr,
|
||||
},
|
||||
};
|
||||
@@ -532,10 +583,40 @@ pub const State = struct {
|
||||
errdefer gop.value_ptr.image.markForUnload();
|
||||
|
||||
gop.value_ptr.image.prepForUpload(alloc) catch |err| {
|
||||
log.warn("error preparing kitty image for upload err={}", .{err});
|
||||
log.warn("error preparing image for upload err={}", .{err});
|
||||
return error.ImageConversionError;
|
||||
};
|
||||
gop.value_ptr.transmit_time = image.transmit_time;
|
||||
gop.value_ptr.transmit_time = transmit_time;
|
||||
}
|
||||
|
||||
/// Prepare the provided Kitty image for upload to the GPU by copying its
|
||||
/// data with our allocator and setting it to the pending state.
|
||||
fn prepKittyImage(
|
||||
self: *State,
|
||||
alloc: Allocator,
|
||||
image: *const terminal.kitty.graphics.Image,
|
||||
) PrepImageError!void {
|
||||
try self.prepImage(
|
||||
alloc,
|
||||
.{ .kitty = image.id },
|
||||
image.transmit_time,
|
||||
.{
|
||||
.width = image.width,
|
||||
.height = image.height,
|
||||
.pixel_format = switch (image.format) {
|
||||
.gray => .gray,
|
||||
.gray_alpha => .gray_alpha,
|
||||
.rgb => .rgb,
|
||||
.rgba => .rgba,
|
||||
.png => unreachable, // should be decoded by now
|
||||
},
|
||||
|
||||
// constCasts are always gross but this one is safe is because
|
||||
// the data is only read from here and copied into its own
|
||||
// buffer.
|
||||
.data = @constCast(image.data.ptr),
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user