mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
renderer/metal: deallocate unused image textures
This commit is contained in:
@ -24,6 +24,7 @@ const Terminal = terminal.Terminal;
|
||||
const mtl = @import("metal/api.zig");
|
||||
const mtl_image = @import("metal/image.zig");
|
||||
const Image = mtl_image.Image;
|
||||
const ImageMap = mtl_image.ImageMap;
|
||||
|
||||
// Get native API access on certain platforms so we can do more customization.
|
||||
const glfwNative = glfw.Native(.{
|
||||
@ -73,7 +74,7 @@ font_group: *font.GroupCache,
|
||||
font_shaper: font.Shaper,
|
||||
|
||||
/// The images that we may render.
|
||||
images: std.AutoHashMapUnmanaged(u32, Image) = .{},
|
||||
images: ImageMap = .{},
|
||||
|
||||
/// Metal objects
|
||||
device: objc.Object, // MTLDevice
|
||||
@ -587,9 +588,9 @@ pub fn render(
|
||||
const draw_cursor = self.cursor_visible and state.terminal.screen.viewportIsBottom();
|
||||
|
||||
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
||||
// This can be dramatically improved, this is basically a v1 effort
|
||||
// to get it working.
|
||||
if (state.terminal.screen.kitty_images.placements.count() > 0) {
|
||||
// We only do this if the Kitty image state is dirty meaning only if
|
||||
// it changes.
|
||||
if (state.terminal.screen.kitty_images.dirty) {
|
||||
try self.prepKittyGraphics(&state.terminal.screen);
|
||||
}
|
||||
|
||||
@ -632,8 +633,20 @@ pub fn render(
|
||||
{
|
||||
var image_it = self.images.iterator();
|
||||
while (image_it.next()) |kv| {
|
||||
if (kv.value_ptr.pending() == null) continue;
|
||||
try kv.value_ptr.upload(self.alloc, self.device);
|
||||
switch (kv.value_ptr.*) {
|
||||
.ready => {},
|
||||
|
||||
.pending_rgb,
|
||||
.pending_rgba,
|
||||
=> try kv.value_ptr.upload(self.alloc, self.device),
|
||||
|
||||
.unload_pending,
|
||||
.unload_ready,
|
||||
=> {
|
||||
kv.value_ptr.deinit(self.alloc);
|
||||
self.images.removeByPtr(kv.key_ptr);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -767,8 +780,24 @@ fn drawCells(
|
||||
/// the visible images are loaded on the GPU.
|
||||
fn prepKittyGraphics(
|
||||
self: *Metal,
|
||||
screen: *const terminal.Screen,
|
||||
screen: *terminal.Screen,
|
||||
) !void {
|
||||
defer screen.kitty_images.dirty = false;
|
||||
|
||||
// Go through our known images and if there are any that are no longer
|
||||
// in use then mark them to be freed.
|
||||
//
|
||||
// This never conflicts with the below because a placement can't
|
||||
// reference an image that doesn't exist.
|
||||
{
|
||||
var it = self.images.iterator();
|
||||
while (it.next()) |kv| {
|
||||
if (screen.kitty_images.imageById(kv.key_ptr.*) == null) {
|
||||
kv.value_ptr.markForUnload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go through the placements and ensure the image is loaded on the GPU.
|
||||
var it = screen.kitty_images.placements.iterator();
|
||||
while (it.next()) |kv| {
|
||||
|
@ -5,6 +5,9 @@ const objc = @import("objc");
|
||||
|
||||
const mtl = @import("api.zig");
|
||||
|
||||
/// The map used for storing images.
|
||||
pub const ImageMap = std.AutoHashMapUnmanaged(u32, Image);
|
||||
|
||||
/// The state for a single image that is to be rendered. The image can be
|
||||
/// pending upload or ready to use with a texture.
|
||||
pub const Image = union(enum) {
|
||||
@ -20,6 +23,10 @@ pub const Image = union(enum) {
|
||||
/// The image is uploaded and ready to be used.
|
||||
ready: objc.Object, // MTLTexture
|
||||
|
||||
/// The image is uploaded but is scheduled to be unloaded.
|
||||
unload_pending: []u8,
|
||||
unload_ready: objc.Object, // MTLTexture
|
||||
|
||||
/// Pending image data that needs to be uploaded to the GPU.
|
||||
pub const Pending = struct {
|
||||
height: u32,
|
||||
@ -42,27 +49,43 @@ pub const Image = union(enum) {
|
||||
switch (self) {
|
||||
.pending_rgb => |p| alloc.free(p.dataSlice(3)),
|
||||
.pending_rgba => |p| alloc.free(p.dataSlice(4)),
|
||||
.ready => |obj| obj.msgSend(void, objc.sel("release"), .{}),
|
||||
.unload_pending => |data| alloc.free(data),
|
||||
|
||||
.ready,
|
||||
.unload_ready,
|
||||
=> |obj| obj.msgSend(void, objc.sel("release"), .{}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Our pixel depth
|
||||
pub fn depth(self: Image) u32 {
|
||||
return switch (self) {
|
||||
.ready => unreachable,
|
||||
.pending_rgb => 3,
|
||||
.pending_rgba => 4,
|
||||
/// Mark this image for unload whatever state it is in.
|
||||
pub fn markForUnload(self: *Image) void {
|
||||
self.* = switch (self.*) {
|
||||
.unload_pending,
|
||||
.unload_ready,
|
||||
=> return,
|
||||
|
||||
.ready => |obj| .{ .unload_ready = obj },
|
||||
.pending_rgb => |p| .{ .unload_pending = p.dataSlice(3) },
|
||||
.pending_rgba => |p| .{ .unload_pending = p.dataSlice(4) },
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns true if this image is in a pending state and requires upload.
|
||||
pub fn pending(self: Image) ?Pending {
|
||||
return switch (self) {
|
||||
.ready => null,
|
||||
/// Returns true if this image is pending upload.
|
||||
pub fn isPending(self: Image) bool {
|
||||
return self.pending() != null;
|
||||
}
|
||||
|
||||
/// Returns true if this image is pending an unload.
|
||||
pub fn isUnloading(self: Image) bool {
|
||||
return switch (self) {
|
||||
.unload_pending,
|
||||
.unload_ready,
|
||||
=> true,
|
||||
|
||||
.ready,
|
||||
.pending_rgb,
|
||||
.pending_rgba,
|
||||
=> |p| p,
|
||||
=> false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -71,7 +94,10 @@ pub const Image = union(enum) {
|
||||
/// no-op.
|
||||
pub fn convert(self: *Image, alloc: Allocator) !void {
|
||||
switch (self.*) {
|
||||
.ready => unreachable, // invalid
|
||||
.ready,
|
||||
.unload_pending,
|
||||
.unload_ready,
|
||||
=> unreachable, // invalid
|
||||
|
||||
.pending_rgba => {}, // ready
|
||||
|
||||
@ -142,6 +168,26 @@ pub const Image = union(enum) {
|
||||
self.* = .{ .ready = texture };
|
||||
}
|
||||
|
||||
/// Our pixel depth
|
||||
fn depth(self: Image) u32 {
|
||||
return switch (self) {
|
||||
.pending_rgb => 3,
|
||||
.pending_rgba => 4,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns true if this image is in a pending state and requires upload.
|
||||
fn pending(self: Image) ?Pending {
|
||||
return switch (self) {
|
||||
.pending_rgb,
|
||||
.pending_rgba,
|
||||
=> |p| p,
|
||||
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn initTexture(p: Pending, device: objc.Object) !objc.Object {
|
||||
// Create our descriptor
|
||||
const desc = init: {
|
||||
|
@ -20,6 +20,12 @@ pub const ImageStorage = struct {
|
||||
const ImageMap = std.AutoHashMapUnmanaged(u32, Image);
|
||||
const PlacementMap = std.AutoHashMapUnmanaged(PlacementKey, Placement);
|
||||
|
||||
/// Dirty is set to true if placements or images change. This is
|
||||
/// purely informational for the renderer and doesn't affect the
|
||||
/// correctness of the program. The renderer must set this to false
|
||||
/// if it cares about this value.
|
||||
dirty: bool = false,
|
||||
|
||||
/// This is the next automatically assigned ID. We start mid-way
|
||||
/// through the u32 range to avoid collisions with buggy programs.
|
||||
next_id: u32 = 2147483647,
|
||||
@ -70,6 +76,8 @@ pub const ImageStorage = struct {
|
||||
// Write our new image
|
||||
if (gop.found_existing) gop.value_ptr.deinit(alloc);
|
||||
gop.value_ptr.* = img;
|
||||
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
/// Add a placement for a given image. The caller must verify in advance
|
||||
@ -91,6 +99,8 @@ pub const ImageStorage = struct {
|
||||
const key: PlacementKey = .{ .image_id = image_id, .placement_id = placement_id };
|
||||
const gop = try self.placements.getOrPut(alloc, key);
|
||||
gop.value_ptr.* = p;
|
||||
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
/// Get an image by its ID. If the image doesn't exist, null is returned.
|
||||
@ -121,11 +131,12 @@ pub const ImageStorage = struct {
|
||||
.all => |delete_images| if (delete_images) {
|
||||
// We just reset our entire state.
|
||||
self.deinit(alloc);
|
||||
self.* = .{};
|
||||
self.* = .{ .dirty = true };
|
||||
} else {
|
||||
// Delete all our placements
|
||||
self.placements.deinit(alloc);
|
||||
self.placements = .{};
|
||||
self.dirty = true;
|
||||
},
|
||||
|
||||
else => log.warn("unimplemented delete command: {}", .{cmd}),
|
||||
|
Reference in New Issue
Block a user