mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
160 lines
6.0 KiB
Zig
160 lines
6.0 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
const objc = @import("objc");
|
|
|
|
const mtl = @import("api.zig");
|
|
|
|
const log = std.log.scoped(.metal);
|
|
|
|
/// 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();
|
|
|
|
buffer: objc.Object, // MTLBuffer
|
|
|
|
/// Initialize a buffer with the given length pre-allocated.
|
|
pub fn init(device: objc.Object, len: usize) !Self {
|
|
const buffer = device.msgSend(
|
|
objc.Object,
|
|
objc.sel("newBufferWithLength:options:"),
|
|
.{
|
|
@as(c_ulong, @intCast(len * @sizeOf(T))),
|
|
mtl.MTLResourceStorageModeShared,
|
|
},
|
|
);
|
|
|
|
return .{ .buffer = buffer };
|
|
}
|
|
|
|
/// Init the buffer filled with the given data.
|
|
pub fn initFill(device: objc.Object, data: []const T) !Self {
|
|
const buffer = device.msgSend(
|
|
objc.Object,
|
|
objc.sel("newBufferWithBytes:length:options:"),
|
|
.{
|
|
@as(*const anyopaque, @ptrCast(data.ptr)),
|
|
@as(c_ulong, @intCast(data.len * @sizeOf(T))),
|
|
mtl.MTLResourceStorageModeShared,
|
|
},
|
|
);
|
|
|
|
return .{ .buffer = buffer };
|
|
}
|
|
|
|
pub fn deinit(self: *Self) void {
|
|
self.buffer.msgSend(void, objc.sel("release"), .{});
|
|
}
|
|
|
|
/// Get the buffer contents as a slice of T. The contents are
|
|
/// mutable. The contents may or may not be automatically synced
|
|
/// depending on the buffer storage mode. See the Metal docs.
|
|
pub fn contents(self: *Self) ![]T {
|
|
const len_bytes = self.buffer.getProperty(c_ulong, "length");
|
|
assert(@mod(len_bytes, @sizeOf(T)) == 0);
|
|
const len = @divExact(len_bytes, @sizeOf(T));
|
|
const ptr = self.buffer.msgSend(
|
|
?[*]T,
|
|
objc.sel("contents"),
|
|
.{},
|
|
).?;
|
|
return ptr[0..len];
|
|
}
|
|
|
|
/// Sync new contents to the buffer. The data is expected to be the
|
|
/// complete contents of the buffer. If the amont 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, device: objc.Object, 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 = device.msgSend(
|
|
objc.Object,
|
|
objc.sel("newBufferWithLength:options:"),
|
|
.{
|
|
@as(c_ulong, @intCast(size * @sizeOf(T))),
|
|
mtl.MTLResourceStorageModeShared,
|
|
},
|
|
);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// 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, device: objc.Object, lists: []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 = device.msgSend(
|
|
objc.Object,
|
|
objc.sel("newBufferWithLength:options:"),
|
|
.{
|
|
@as(c_ulong, @intCast(size * @sizeOf(T))),
|
|
mtl.MTLResourceStorageModeShared,
|
|
},
|
|
);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
return total_len;
|
|
}
|
|
};
|
|
}
|