mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-22 19:56:08 +03:00

This commit is very large, representing about a month of work with many interdependent changes that don't separate cleanly in to atomic commits. The main change here is unifying the renderer logic to a single generic renderer, implemented on top of an abstraction layer over OpenGL/Metal. I'll write a more complete summary of the changes in the description of the PR.
187 lines
7.0 KiB
Zig
187 lines
7.0 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
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;
|
|
}
|
|
};
|
|
}
|