diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/renderer/metal/buffer.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (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.zig | 185 |
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; + } + }; +} |
