aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/renderer/metal/buffer.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/renderer/metal/buffer.zig
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/renderer/metal/buffer.zig')
-rw-r--r--vendor/libghostty-vt/src/renderer/metal/buffer.zig185
1 files changed, 185 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/renderer/metal/buffer.zig b/vendor/libghostty-vt/src/renderer/metal/buffer.zig
new file mode 100644
index 00000000..f91f89e9
--- /dev/null
+++ b/vendor/libghostty-vt/src/renderer/metal/buffer.zig
@@ -0,0 +1,185 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const objc = @import("objc");
+const macos = @import("macos");
+
+const mtl = @import("api.zig");
+const Metal = @import("../Metal.zig");
+
+const log = std.log.scoped(.metal);
+
+/// Options for initializing a buffer.
+pub const Options = struct {
+ /// MTLDevice
+ device: objc.Object,
+ resource_options: mtl.MTLResourceOptions,
+};
+
+/// Metal data storage for a certain set of equal types. This is usually
+/// used for vertex buffers, etc. This helpful wrapper makes it easy to
+/// prealloc, shrink, grow, sync, buffers with Metal.
+pub fn Buffer(comptime T: type) type {
+ return struct {
+ const Self = @This();
+
+ /// The options this buffer was initialized with.
+ opts: Options,
+
+ /// The underlying MTLBuffer object.
+ buffer: objc.Object,
+
+ /// The allocated length of the buffer.
+ /// Note that this is the number
+ /// of `T`s not the size in bytes.
+ len: usize,
+
+ /// Initialize a buffer with the given length pre-allocated.
+ pub fn init(opts: Options, len: usize) !Self {
+ const buffer = opts.device.msgSend(
+ objc.Object,
+ objc.sel("newBufferWithLength:options:"),
+ .{
+ @as(c_ulong, @intCast(len * @sizeOf(T))),
+ opts.resource_options,
+ },
+ );
+
+ return .{ .buffer = buffer, .opts = opts, .len = len };
+ }
+
+ /// Init the buffer filled with the given data.
+ pub fn initFill(opts: Options, data: []const T) !Self {
+ const buffer = opts.device.msgSend(
+ objc.Object,
+ objc.sel("newBufferWithBytes:length:options:"),
+ .{
+ @as(*const anyopaque, @ptrCast(data.ptr)),
+ @as(c_ulong, @intCast(data.len * @sizeOf(T))),
+ opts.resource_options,
+ },
+ );
+
+ return .{ .buffer = buffer, .opts = opts, .len = data.len };
+ }
+
+ pub fn deinit(self: *const Self) void {
+ self.buffer.msgSend(void, objc.sel("release"), .{});
+ }
+
+ /// Sync new contents to the buffer. The data is expected to be the
+ /// complete contents of the buffer. If the amount of data is larger
+ /// than the buffer length, the buffer will be reallocated.
+ ///
+ /// If the amount of data is smaller than the buffer length, the
+ /// remaining data in the buffer is left untouched.
+ pub fn sync(self: *Self, data: []const T) !void {
+ // If we need more bytes than our buffer has, we need to reallocate.
+ const req_bytes = data.len * @sizeOf(T);
+ const avail_bytes = self.buffer.getProperty(c_ulong, "length");
+ if (req_bytes > avail_bytes) {
+ // Deallocate previous buffer
+ self.buffer.msgSend(void, objc.sel("release"), .{});
+
+ // Allocate a new buffer with enough to hold double what we require.
+ const size = req_bytes * 2;
+ self.buffer = self.opts.device.msgSend(
+ objc.Object,
+ objc.sel("newBufferWithLength:options:"),
+ .{
+ @as(c_ulong, @intCast(size * @sizeOf(T))),
+ self.opts.resource_options,
+ },
+ );
+ }
+
+ // We can fit within the buffer so we can just replace bytes.
+ const dst = dst: {
+ const ptr = self.buffer.msgSend(?[*]u8, objc.sel("contents"), .{}) orelse {
+ log.warn("buffer contents ptr is null", .{});
+ return error.MetalFailed;
+ };
+
+ break :dst ptr[0..req_bytes];
+ };
+
+ const src = src: {
+ const ptr = @as([*]const u8, @ptrCast(data.ptr));
+ break :src ptr[0..req_bytes];
+ };
+
+ @memcpy(dst, src);
+
+ // If we're using the managed resource storage mode, then
+ // we need to signal Metal to synchronize the buffer data.
+ //
+ // Ref: https://developer.apple.com/documentation/metal/synchronizing-a-managed-resource-in-macos?language=objc
+ if (self.opts.resource_options.storage_mode == .managed) {
+ self.buffer.msgSend(
+ void,
+ "didModifyRange:",
+ .{macos.foundation.Range.init(0, req_bytes)},
+ );
+ }
+ }
+
+ /// Like Buffer.sync but takes data from an array of ArrayLists,
+ /// rather than a single array. Returns the number of items synced.
+ pub fn syncFromArrayLists(self: *Self, lists: []const std.ArrayListUnmanaged(T)) !usize {
+ var total_len: usize = 0;
+ for (lists) |list| {
+ total_len += list.items.len;
+ }
+
+ // If we need more bytes than our buffer has, we need to reallocate.
+ const req_bytes = total_len * @sizeOf(T);
+ const avail_bytes = self.buffer.getProperty(c_ulong, "length");
+ if (req_bytes > avail_bytes) {
+ // Deallocate previous buffer
+ self.buffer.msgSend(void, objc.sel("release"), .{});
+
+ // Allocate a new buffer with enough to hold double what we require.
+ const size = req_bytes * 2;
+ self.buffer = self.opts.device.msgSend(
+ objc.Object,
+ objc.sel("newBufferWithLength:options:"),
+ .{
+ @as(c_ulong, @intCast(size * @sizeOf(T))),
+ self.opts.resource_options,
+ },
+ );
+ }
+
+ // We can fit within the buffer so we can just replace bytes.
+ const dst = dst: {
+ const ptr = self.buffer.msgSend(?[*]u8, objc.sel("contents"), .{}) orelse {
+ log.warn("buffer contents ptr is null", .{});
+ return error.MetalFailed;
+ };
+
+ break :dst ptr[0..req_bytes];
+ };
+
+ var i: usize = 0;
+
+ for (lists) |list| {
+ const ptr = @as([*]const u8, @ptrCast(list.items.ptr));
+ @memcpy(dst[i..][0 .. list.items.len * @sizeOf(T)], ptr);
+ i += list.items.len * @sizeOf(T);
+ }
+
+ // If we're using the managed resource storage mode, then
+ // we need to signal Metal to synchronize the buffer data.
+ //
+ // Ref: https://developer.apple.com/documentation/metal/synchronizing-a-managed-resource-in-macos?language=objc
+ if (self.opts.resource_options.storage_mode == .managed) {
+ self.buffer.msgSend(
+ void,
+ "didModifyRange:",
+ .{macos.foundation.Range.init(0, req_bytes)},
+ );
+ }
+
+ return total_len;
+ }
+ };
+}