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 = @import("metal/api.zig");
|
||||||
const mtl_image = @import("metal/image.zig");
|
const mtl_image = @import("metal/image.zig");
|
||||||
const Image = mtl_image.Image;
|
const Image = mtl_image.Image;
|
||||||
|
const ImageMap = mtl_image.ImageMap;
|
||||||
|
|
||||||
// Get native API access on certain platforms so we can do more customization.
|
// Get native API access on certain platforms so we can do more customization.
|
||||||
const glfwNative = glfw.Native(.{
|
const glfwNative = glfw.Native(.{
|
||||||
@ -73,7 +74,7 @@ font_group: *font.GroupCache,
|
|||||||
font_shaper: font.Shaper,
|
font_shaper: font.Shaper,
|
||||||
|
|
||||||
/// The images that we may render.
|
/// The images that we may render.
|
||||||
images: std.AutoHashMapUnmanaged(u32, Image) = .{},
|
images: ImageMap = .{},
|
||||||
|
|
||||||
/// Metal objects
|
/// Metal objects
|
||||||
device: objc.Object, // MTLDevice
|
device: objc.Object, // MTLDevice
|
||||||
@ -587,9 +588,9 @@ pub fn render(
|
|||||||
const draw_cursor = self.cursor_visible and state.terminal.screen.viewportIsBottom();
|
const draw_cursor = self.cursor_visible and state.terminal.screen.viewportIsBottom();
|
||||||
|
|
||||||
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
|
||||||
// This can be dramatically improved, this is basically a v1 effort
|
// We only do this if the Kitty image state is dirty meaning only if
|
||||||
// to get it working.
|
// it changes.
|
||||||
if (state.terminal.screen.kitty_images.placements.count() > 0) {
|
if (state.terminal.screen.kitty_images.dirty) {
|
||||||
try self.prepKittyGraphics(&state.terminal.screen);
|
try self.prepKittyGraphics(&state.terminal.screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,8 +633,20 @@ pub fn render(
|
|||||||
{
|
{
|
||||||
var image_it = self.images.iterator();
|
var image_it = self.images.iterator();
|
||||||
while (image_it.next()) |kv| {
|
while (image_it.next()) |kv| {
|
||||||
if (kv.value_ptr.pending() == null) continue;
|
switch (kv.value_ptr.*) {
|
||||||
try kv.value_ptr.upload(self.alloc, self.device);
|
.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.
|
/// the visible images are loaded on the GPU.
|
||||||
fn prepKittyGraphics(
|
fn prepKittyGraphics(
|
||||||
self: *Metal,
|
self: *Metal,
|
||||||
screen: *const terminal.Screen,
|
screen: *terminal.Screen,
|
||||||
) !void {
|
) !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.
|
// Go through the placements and ensure the image is loaded on the GPU.
|
||||||
var it = screen.kitty_images.placements.iterator();
|
var it = screen.kitty_images.placements.iterator();
|
||||||
while (it.next()) |kv| {
|
while (it.next()) |kv| {
|
||||||
|
@ -5,6 +5,9 @@ const objc = @import("objc");
|
|||||||
|
|
||||||
const mtl = @import("api.zig");
|
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
|
/// The state for a single image that is to be rendered. The image can be
|
||||||
/// pending upload or ready to use with a texture.
|
/// pending upload or ready to use with a texture.
|
||||||
pub const Image = union(enum) {
|
pub const Image = union(enum) {
|
||||||
@ -20,6 +23,10 @@ pub const Image = union(enum) {
|
|||||||
/// The image is uploaded and ready to be used.
|
/// The image is uploaded and ready to be used.
|
||||||
ready: objc.Object, // MTLTexture
|
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.
|
/// Pending image data that needs to be uploaded to the GPU.
|
||||||
pub const Pending = struct {
|
pub const Pending = struct {
|
||||||
height: u32,
|
height: u32,
|
||||||
@ -42,27 +49,43 @@ pub const Image = union(enum) {
|
|||||||
switch (self) {
|
switch (self) {
|
||||||
.pending_rgb => |p| alloc.free(p.dataSlice(3)),
|
.pending_rgb => |p| alloc.free(p.dataSlice(3)),
|
||||||
.pending_rgba => |p| alloc.free(p.dataSlice(4)),
|
.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
|
/// Mark this image for unload whatever state it is in.
|
||||||
pub fn depth(self: Image) u32 {
|
pub fn markForUnload(self: *Image) void {
|
||||||
return switch (self) {
|
self.* = switch (self.*) {
|
||||||
.ready => unreachable,
|
.unload_pending,
|
||||||
.pending_rgb => 3,
|
.unload_ready,
|
||||||
.pending_rgba => 4,
|
=> 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.
|
/// Returns true if this image is pending upload.
|
||||||
pub fn pending(self: Image) ?Pending {
|
pub fn isPending(self: Image) bool {
|
||||||
return switch (self) {
|
return self.pending() != null;
|
||||||
.ready => 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_rgb,
|
||||||
.pending_rgba,
|
.pending_rgba,
|
||||||
=> |p| p,
|
=> false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +94,10 @@ pub const Image = union(enum) {
|
|||||||
/// no-op.
|
/// no-op.
|
||||||
pub fn convert(self: *Image, alloc: Allocator) !void {
|
pub fn convert(self: *Image, alloc: Allocator) !void {
|
||||||
switch (self.*) {
|
switch (self.*) {
|
||||||
.ready => unreachable, // invalid
|
.ready,
|
||||||
|
.unload_pending,
|
||||||
|
.unload_ready,
|
||||||
|
=> unreachable, // invalid
|
||||||
|
|
||||||
.pending_rgba => {}, // ready
|
.pending_rgba => {}, // ready
|
||||||
|
|
||||||
@ -142,6 +168,26 @@ pub const Image = union(enum) {
|
|||||||
self.* = .{ .ready = texture };
|
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 {
|
fn initTexture(p: Pending, device: objc.Object) !objc.Object {
|
||||||
// Create our descriptor
|
// Create our descriptor
|
||||||
const desc = init: {
|
const desc = init: {
|
||||||
|
@ -20,6 +20,12 @@ pub const ImageStorage = struct {
|
|||||||
const ImageMap = std.AutoHashMapUnmanaged(u32, Image);
|
const ImageMap = std.AutoHashMapUnmanaged(u32, Image);
|
||||||
const PlacementMap = std.AutoHashMapUnmanaged(PlacementKey, Placement);
|
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
|
/// This is the next automatically assigned ID. We start mid-way
|
||||||
/// through the u32 range to avoid collisions with buggy programs.
|
/// through the u32 range to avoid collisions with buggy programs.
|
||||||
next_id: u32 = 2147483647,
|
next_id: u32 = 2147483647,
|
||||||
@ -70,6 +76,8 @@ pub const ImageStorage = struct {
|
|||||||
// Write our new image
|
// Write our new image
|
||||||
if (gop.found_existing) gop.value_ptr.deinit(alloc);
|
if (gop.found_existing) gop.value_ptr.deinit(alloc);
|
||||||
gop.value_ptr.* = img;
|
gop.value_ptr.* = img;
|
||||||
|
|
||||||
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a placement for a given image. The caller must verify in advance
|
/// 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 key: PlacementKey = .{ .image_id = image_id, .placement_id = placement_id };
|
||||||
const gop = try self.placements.getOrPut(alloc, key);
|
const gop = try self.placements.getOrPut(alloc, key);
|
||||||
gop.value_ptr.* = p;
|
gop.value_ptr.* = p;
|
||||||
|
|
||||||
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an image by its ID. If the image doesn't exist, null is returned.
|
/// 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) {
|
.all => |delete_images| if (delete_images) {
|
||||||
// We just reset our entire state.
|
// We just reset our entire state.
|
||||||
self.deinit(alloc);
|
self.deinit(alloc);
|
||||||
self.* = .{};
|
self.* = .{ .dirty = true };
|
||||||
} else {
|
} else {
|
||||||
// Delete all our placements
|
// Delete all our placements
|
||||||
self.placements.deinit(alloc);
|
self.placements.deinit(alloc);
|
||||||
self.placements = .{};
|
self.placements = .{};
|
||||||
|
self.dirty = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
else => log.warn("unimplemented delete command: {}", .{cmd}),
|
else => log.warn("unimplemented delete command: {}", .{cmd}),
|
||||||
|
Reference in New Issue
Block a user