mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
renderer/metal: start setting up per-frame state
This commit is contained in:
@ -108,8 +108,6 @@ image_text_end: u32 = 0,
|
|||||||
|
|
||||||
/// Metal state
|
/// Metal state
|
||||||
shaders: Shaders, // Compiled shaders
|
shaders: Shaders, // Compiled shaders
|
||||||
buf_cells: CellBuffer, // Vertex buffer for cells
|
|
||||||
buf_cells_bg: CellBuffer, // Vertex buffer for background cells
|
|
||||||
buf_instance: InstanceBuffer, // MTLBuffer
|
buf_instance: InstanceBuffer, // MTLBuffer
|
||||||
|
|
||||||
/// Metal objects
|
/// Metal objects
|
||||||
@ -133,6 +131,93 @@ health: std.atomic.Value(Health) = .{ .raw = .healthy },
|
|||||||
/// will be incremented.
|
/// will be incremented.
|
||||||
inflight: std.Thread.Semaphore = .{ .permits = 1 },
|
inflight: std.Thread.Semaphore = .{ .permits = 1 },
|
||||||
|
|
||||||
|
/// Our GPU state
|
||||||
|
gpu_state: GPUState,
|
||||||
|
|
||||||
|
/// State we need for the GPU that is shared between all frames.
|
||||||
|
pub const GPUState = struct {
|
||||||
|
// The count of buffers we use for double/triple buffering. If
|
||||||
|
// this is one then we don't do any double+ buffering at all. This
|
||||||
|
// is comptime because there isn't a good reason to change this at
|
||||||
|
// runtime and there is a lot of complexity to support it. For comptime,
|
||||||
|
// this is useful for debugging.
|
||||||
|
const BufferCount = 3;
|
||||||
|
|
||||||
|
/// The frame data, the current frame index, and the semaphore protecting
|
||||||
|
/// the frame data. This is used to implement double/triple/etc. buffering.
|
||||||
|
frames: [BufferCount]FrameState,
|
||||||
|
frame_index: std.math.IntFittingRange(0, BufferCount - 1) = 0,
|
||||||
|
frame_sema: std.Thread.Semaphore = .{ .permits = BufferCount },
|
||||||
|
|
||||||
|
device: objc.Object, // MTLDevice
|
||||||
|
|
||||||
|
pub fn init() !GPUState {
|
||||||
|
var result: GPUState = .{
|
||||||
|
.device = objc.Object.fromId(mtl.MTLCreateSystemDefaultDevice()),
|
||||||
|
.frames = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize all of our frame state.
|
||||||
|
for (&result.frames) |*frame| {
|
||||||
|
frame.* = try FrameState.init(result.device);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *GPUState) void {
|
||||||
|
// Wait for all of our inflight draws to complete so that
|
||||||
|
// we can cleanly deinit our GPU state.
|
||||||
|
for (0..BufferCount) |_| self.frame_sema.wait();
|
||||||
|
for (&self.frames) |*frame| frame.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// State we need duplicated for every frame. Any state that could be
|
||||||
|
/// in a data race between the GPU and CPU while a frame is being
|
||||||
|
/// drawn should be in this struct.
|
||||||
|
///
|
||||||
|
/// While a draw is in-process, we "lock" the state (via a semaphore)
|
||||||
|
/// and prevent the CPU from updating the state until Metal reports
|
||||||
|
/// that the frame is complete.
|
||||||
|
///
|
||||||
|
/// This is used to implement double/triple buffering.
|
||||||
|
pub const FrameState = struct {
|
||||||
|
uniforms: UniformBuffer,
|
||||||
|
cells: CellBuffer,
|
||||||
|
cells_bg: CellBuffer,
|
||||||
|
|
||||||
|
/// A buffer containing the uniform data.
|
||||||
|
const UniformBuffer = mtl_buffer.Buffer(mtl_shaders.Uniforms);
|
||||||
|
|
||||||
|
pub fn init(device: objc.Object) !FrameState {
|
||||||
|
// Uniform buffer contains exactly 1 uniform struct. The
|
||||||
|
// uniform data will be undefined so this must be set before
|
||||||
|
// a frame is drawn.
|
||||||
|
var uniforms = try UniformBuffer.init(device, 1);
|
||||||
|
errdefer uniforms.deinit();
|
||||||
|
|
||||||
|
// Create the buffers for our vertex data. The preallocation size
|
||||||
|
// is likely too small but our first frame update will resize it.
|
||||||
|
var cells = try CellBuffer.init(device, 10 * 10);
|
||||||
|
errdefer cells.deinit();
|
||||||
|
var cells_bg = try CellBuffer.init(device, 10 * 10);
|
||||||
|
errdefer cells_bg.deinit();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.uniforms = uniforms,
|
||||||
|
.cells = cells,
|
||||||
|
.cells_bg = cells_bg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *FrameState) void {
|
||||||
|
self.uniforms.deinit();
|
||||||
|
self.cells.deinit();
|
||||||
|
self.cells_bg.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const CustomShaderState = struct {
|
pub const CustomShaderState = struct {
|
||||||
/// The screen texture that we render the terminal to. If we don't have
|
/// The screen texture that we render the terminal to. If we don't have
|
||||||
/// custom shaders, we render directly to the drawable.
|
/// custom shaders, we render directly to the drawable.
|
||||||
@ -354,10 +439,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
errdefer font_shaper.deinit();
|
errdefer font_shaper.deinit();
|
||||||
|
|
||||||
// Vertex buffers
|
// Vertex buffers
|
||||||
var buf_cells = try CellBuffer.init(device, 160 * 160);
|
|
||||||
errdefer buf_cells.deinit();
|
|
||||||
var buf_cells_bg = try CellBuffer.init(device, 160 * 160);
|
|
||||||
errdefer buf_cells_bg.deinit();
|
|
||||||
var buf_instance = try InstanceBuffer.initFill(device, &.{
|
var buf_instance = try InstanceBuffer.initFill(device, &.{
|
||||||
0, 1, 3, // Top-left triangle
|
0, 1, 3, // Top-left triangle
|
||||||
1, 2, 3, // Bottom-right triangle
|
1, 2, 3, // Bottom-right triangle
|
||||||
@ -433,6 +514,10 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Build our GPU state
|
||||||
|
var gpu_state = try GPUState.init();
|
||||||
|
errdefer gpu_state.deinit();
|
||||||
|
|
||||||
return Metal{
|
return Metal{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.config = options.config,
|
.config = options.config,
|
||||||
@ -461,8 +546,6 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
|
|
||||||
// Shaders
|
// Shaders
|
||||||
.shaders = shaders,
|
.shaders = shaders,
|
||||||
.buf_cells = buf_cells,
|
|
||||||
.buf_cells_bg = buf_cells_bg,
|
|
||||||
.buf_instance = buf_instance,
|
.buf_instance = buf_instance,
|
||||||
|
|
||||||
// Metal stuff
|
// Metal stuff
|
||||||
@ -472,6 +555,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
.texture_greyscale = font_critical.texture_greyscale,
|
.texture_greyscale = font_critical.texture_greyscale,
|
||||||
.texture_color = font_critical.texture_color,
|
.texture_color = font_critical.texture_color,
|
||||||
.custom_shader_state = custom_shader_state,
|
.custom_shader_state = custom_shader_state,
|
||||||
|
.gpu_state = gpu_state,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,6 +566,9 @@ pub fn deinit(self: *Metal) void {
|
|||||||
// "self"
|
// "self"
|
||||||
self.inflight.wait();
|
self.inflight.wait();
|
||||||
|
|
||||||
|
// All inflight frames are done, deinit our GPU state.
|
||||||
|
self.gpu_state.deinit();
|
||||||
|
|
||||||
self.cells.deinit(self.alloc);
|
self.cells.deinit(self.alloc);
|
||||||
self.cells_bg.deinit(self.alloc);
|
self.cells_bg.deinit(self.alloc);
|
||||||
|
|
||||||
@ -496,8 +583,6 @@ pub fn deinit(self: *Metal) void {
|
|||||||
}
|
}
|
||||||
self.image_placements.deinit(self.alloc);
|
self.image_placements.deinit(self.alloc);
|
||||||
|
|
||||||
self.buf_cells_bg.deinit();
|
|
||||||
self.buf_cells.deinit();
|
|
||||||
self.buf_instance.deinit();
|
self.buf_instance.deinit();
|
||||||
deinitMTLResource(self.texture_greyscale);
|
deinitMTLResource(self.texture_greyscale);
|
||||||
deinitMTLResource(self.texture_color);
|
deinitMTLResource(self.texture_color);
|
||||||
@ -721,6 +806,12 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
|||||||
self.inflight.wait();
|
self.inflight.wait();
|
||||||
errdefer self.inflight.post();
|
errdefer self.inflight.post();
|
||||||
|
|
||||||
|
// Setup our frame data
|
||||||
|
const frame = &self.gpu_state.frames[self.gpu_state.frame_index];
|
||||||
|
try frame.uniforms.sync(self.gpu_state.device, &.{self.uniforms});
|
||||||
|
try frame.cells_bg.sync(self.gpu_state.device, self.cells_bg.items);
|
||||||
|
try frame.cells.sync(self.gpu_state.device, self.cells.items);
|
||||||
|
|
||||||
// If we have custom shaders, update the animation time.
|
// If we have custom shaders, update the animation time.
|
||||||
if (self.custom_shader_state) |*state| {
|
if (self.custom_shader_state) |*state| {
|
||||||
const now = std.time.Instant.now() catch state.first_frame_time;
|
const now = std.time.Instant.now() catch state.first_frame_time;
|
||||||
@ -818,13 +909,13 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
|||||||
try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_bg_end]);
|
try self.drawImagePlacements(encoder, self.image_placements.items[0..self.image_bg_end]);
|
||||||
|
|
||||||
// Then draw background cells
|
// Then draw background cells
|
||||||
try self.drawCells(encoder, &self.buf_cells_bg, self.cells_bg);
|
try self.drawCells(encoder, frame, frame.cells_bg, self.cells_bg.items.len);
|
||||||
|
|
||||||
// Then draw images under text
|
// Then draw images under text
|
||||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_bg_end..self.image_text_end]);
|
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_bg_end..self.image_text_end]);
|
||||||
|
|
||||||
// Then draw fg cells
|
// Then draw fg cells
|
||||||
try self.drawCells(encoder, &self.buf_cells, self.cells);
|
try self.drawCells(encoder, frame, frame.cells, self.cells.items.len);
|
||||||
|
|
||||||
// Then draw remaining images
|
// Then draw remaining images
|
||||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]);
|
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]);
|
||||||
@ -1121,13 +1212,10 @@ fn drawImagePlacement(
|
|||||||
fn drawCells(
|
fn drawCells(
|
||||||
self: *Metal,
|
self: *Metal,
|
||||||
encoder: objc.Object,
|
encoder: objc.Object,
|
||||||
buf: *CellBuffer,
|
frame: *const FrameState,
|
||||||
cells: std.ArrayListUnmanaged(mtl_shaders.Cell),
|
buf: CellBuffer,
|
||||||
|
len: usize,
|
||||||
) !void {
|
) !void {
|
||||||
if (cells.items.len == 0) return;
|
|
||||||
|
|
||||||
try buf.sync(self.device, cells.items);
|
|
||||||
|
|
||||||
// Use our shader pipeline
|
// Use our shader pipeline
|
||||||
encoder.msgSend(
|
encoder.msgSend(
|
||||||
void,
|
void,
|
||||||
@ -1138,12 +1226,13 @@ fn drawCells(
|
|||||||
// Set our buffers
|
// Set our buffers
|
||||||
encoder.msgSend(
|
encoder.msgSend(
|
||||||
void,
|
void,
|
||||||
objc.sel("setVertexBytes:length:atIndex:"),
|
objc.sel("setVertexBuffer:offset:atIndex:"),
|
||||||
.{
|
.{ buf.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) },
|
||||||
@as(*const anyopaque, @ptrCast(&self.uniforms)),
|
);
|
||||||
@as(c_ulong, @sizeOf(@TypeOf(self.uniforms))),
|
encoder.msgSend(
|
||||||
@as(c_ulong, 1),
|
void,
|
||||||
},
|
objc.sel("setVertexBuffer:offset:atIndex:"),
|
||||||
|
.{ frame.uniforms.buffer.value, @as(c_ulong, 0), @as(c_ulong, 1) },
|
||||||
);
|
);
|
||||||
encoder.msgSend(
|
encoder.msgSend(
|
||||||
void,
|
void,
|
||||||
@ -1161,11 +1250,6 @@ fn drawCells(
|
|||||||
@as(c_ulong, 1),
|
@as(c_ulong, 1),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
encoder.msgSend(
|
|
||||||
void,
|
|
||||||
objc.sel("setVertexBuffer:offset:atIndex:"),
|
|
||||||
.{ buf.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) },
|
|
||||||
);
|
|
||||||
|
|
||||||
encoder.msgSend(
|
encoder.msgSend(
|
||||||
void,
|
void,
|
||||||
@ -1176,7 +1260,7 @@ fn drawCells(
|
|||||||
@intFromEnum(mtl.MTLIndexType.uint16),
|
@intFromEnum(mtl.MTLIndexType.uint16),
|
||||||
self.buf_instance.buffer.value,
|
self.buf_instance.buffer.value,
|
||||||
@as(c_ulong, 0),
|
@as(c_ulong, 0),
|
||||||
@as(c_ulong, cells.items.len),
|
@as(c_ulong, len),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,21 @@ pub fn Buffer(comptime T: type) type {
|
|||||||
self.buffer.msgSend(void, objc.sel("release"), .{});
|
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.
|
/// Sync new contents to the buffer.
|
||||||
pub fn sync(self: *Self, device: objc.Object, data: []const T) !void {
|
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.
|
// If we need more bytes than our buffer has, we need to reallocate.
|
||||||
|
Reference in New Issue
Block a user