ghostty/src/renderer/metal/Target.zig
Qwerasd 1f733c9e7f renderer/metal: properly release texture descriptors
Fixes memory leak. We always need to release these descriptors; the
textures themselves will retain or copy them if necessary.
2025-07-02 11:48:30 -06:00

111 lines
3.1 KiB
Zig

//! Represents a render target.
//!
//! In this case, an IOSurface-backed MTLTexture.
const Self = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const builtin = @import("builtin");
const objc = @import("objc");
const macos = @import("macos");
const graphics = macos.graphics;
const IOSurface = macos.iosurface.IOSurface;
const mtl = @import("api.zig");
const log = std.log.scoped(.metal);
/// Options for initializing a Target
pub const Options = struct {
/// MTLDevice
device: objc.Object,
/// Desired width
width: usize,
/// Desired height
height: usize,
/// Pixel format for the MTLTexture
pixel_format: mtl.MTLPixelFormat,
/// Storage mode for the MTLTexture
storage_mode: mtl.MTLResourceOptions.StorageMode,
};
/// The underlying IOSurface.
surface: *IOSurface,
/// The underlying MTLTexture.
texture: objc.Object,
/// Current width of this target.
width: usize,
/// Current height of this target.
height: usize,
pub fn init(opts: Options) !Self {
// We set our surface's color space to Display P3.
// This allows us to have "Apple-style" alpha blending,
// since it seems to be the case that Apple apps like
// Terminal and TextEdit render text in the display's
// color space using converted colors, which reduces,
// but does not fully eliminate blending artifacts.
const colorspace = try graphics.ColorSpace.createNamed(.displayP3);
defer colorspace.release();
const surface = try IOSurface.init(.{
.width = @intCast(opts.width),
.height = @intCast(opts.height),
.pixel_format = .@"32BGRA",
.bytes_per_element = 4,
.colorspace = colorspace,
});
// Create our descriptor
const desc = init: {
const Class = objc.getClass("MTLTextureDescriptor").?;
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
break :init id_init;
};
defer desc.release();
// Set our properties
desc.setProperty("width", @as(c_ulong, @intCast(opts.width)));
desc.setProperty("height", @as(c_ulong, @intCast(opts.height)));
desc.setProperty("pixelFormat", @intFromEnum(opts.pixel_format));
desc.setProperty("usage", mtl.MTLTextureUsage{ .render_target = true });
desc.setProperty(
"resourceOptions",
mtl.MTLResourceOptions{
// Indicate that the CPU writes to this resource but never reads it.
.cpu_cache_mode = .write_combined,
.storage_mode = opts.storage_mode,
},
);
const id = opts.device.msgSend(
?*anyopaque,
objc.sel("newTextureWithDescriptor:iosurface:plane:"),
.{
desc,
surface,
@as(c_ulong, 0),
},
) orelse return error.MetalFailed;
const texture = objc.Object.fromId(id);
return .{
.surface = surface,
.texture = texture,
.width = opts.width,
.height = opts.height,
};
}
pub fn deinit(self: *Self) void {
self.surface.deinit();
self.texture.release();
}