mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 16:26:08 +03:00
renderer/metal: use swap chain for custom shader passes
This commit is contained in:
@ -289,20 +289,31 @@ pub const FrameState = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const CustomShaderState = struct {
|
pub const CustomShaderState = struct {
|
||||||
/// The screen texture that we render the terminal to. If we don't have
|
/// When we have a custom shader state, we maintain a front
|
||||||
/// custom shaders, we render directly to the drawable.
|
/// and back texture which we use as a swap chain to render
|
||||||
screen_texture: objc.Object, // MTLTexture
|
/// between when multiple custom shaders are defined.
|
||||||
|
front_texture: objc.Object, // MTLTexture
|
||||||
|
back_texture: objc.Object, // MTLTexture
|
||||||
|
|
||||||
sampler: mtl_sampler.Sampler,
|
sampler: mtl_sampler.Sampler,
|
||||||
uniforms: mtl_shaders.PostUniforms,
|
uniforms: mtl_shaders.PostUniforms,
|
||||||
/// The first time a frame was drawn. This is used to update the time
|
|
||||||
/// uniform.
|
/// The first time a frame was drawn.
|
||||||
|
/// This is used to update the time uniform.
|
||||||
first_frame_time: std.time.Instant,
|
first_frame_time: std.time.Instant,
|
||||||
/// The last time a frame was drawn. This is used to update the time
|
|
||||||
/// uniform.
|
/// The last time a frame was drawn.
|
||||||
|
/// This is used to update the time uniform.
|
||||||
last_frame_time: std.time.Instant,
|
last_frame_time: std.time.Instant,
|
||||||
|
|
||||||
|
/// Swap the front and back textures.
|
||||||
|
pub fn swap(self: *CustomShaderState) void {
|
||||||
|
std.mem.swap(objc.Object, &self.front_texture, &self.back_texture);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *CustomShaderState) void {
|
pub fn deinit(self: *CustomShaderState) void {
|
||||||
deinitMTLResource(self.screen_texture);
|
deinitMTLResource(self.front_texture);
|
||||||
|
deinitMTLResource(self.back_texture);
|
||||||
self.sampler.deinit();
|
self.sampler.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -531,9 +542,10 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
|
|||||||
errdefer sampler.deinit();
|
errdefer sampler.deinit();
|
||||||
|
|
||||||
break :state .{
|
break :state .{
|
||||||
// Resolution and screen texture will be fixed up by first
|
// Resolution and screen textures will be fixed up by first
|
||||||
// call to setScreenSize. This happens before any draw call.
|
// call to setScreenSize. This happens before any draw call.
|
||||||
.screen_texture = undefined,
|
.front_texture = undefined,
|
||||||
|
.back_texture = undefined,
|
||||||
.sampler = sampler,
|
.sampler = sampler,
|
||||||
.uniforms = .{
|
.uniforms = .{
|
||||||
.resolution = .{ 0, 0, 1 },
|
.resolution = .{ 0, 0, 1 },
|
||||||
@ -1076,7 +1088,7 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
|||||||
// Get our screen texture. If we don't have a dedicated screen texture
|
// Get our screen texture. If we don't have a dedicated screen texture
|
||||||
// then we just use the drawable texture.
|
// then we just use the drawable texture.
|
||||||
const screen_texture = if (self.custom_shader_state) |state|
|
const screen_texture = if (self.custom_shader_state) |state|
|
||||||
state.screen_texture
|
state.back_texture
|
||||||
else tex: {
|
else tex: {
|
||||||
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
||||||
break :tex objc.Object.fromId(texture);
|
break :tex objc.Object.fromId(texture);
|
||||||
@ -1122,11 +1134,6 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
|||||||
.{@as(c_ulong, 0)},
|
.{@as(c_ulong, 0)},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Texture is a property of CAMetalDrawable but if you run
|
|
||||||
// Ghostty in XCode in debug mode it returns a CaptureMTLDrawable
|
|
||||||
// which ironically doesn't implement CAMetalDrawable as a
|
|
||||||
// property so we just send a message.
|
|
||||||
//const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
|
||||||
attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear));
|
attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear));
|
||||||
attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store));
|
attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store));
|
||||||
attachment.setProperty("texture", screen_texture.value);
|
attachment.setProperty("texture", screen_texture.value);
|
||||||
@ -1165,9 +1172,8 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
|||||||
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]);
|
try self.drawImagePlacements(encoder, self.image_placements.items[self.image_text_end..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have custom shaders AND we have a screen texture, then we
|
// If we have custom shaders, then we render them.
|
||||||
// render the custom shaders.
|
if (self.custom_shader_state) |*state| {
|
||||||
if (self.custom_shader_state) |state| {
|
|
||||||
// MTLRenderPassDescriptor
|
// MTLRenderPassDescriptor
|
||||||
const desc = desc: {
|
const desc = desc: {
|
||||||
const MTLRenderPassDescriptor = objc.getClass("MTLRenderPassDescriptor").?;
|
const MTLRenderPassDescriptor = objc.getClass("MTLRenderPassDescriptor").?;
|
||||||
@ -1177,33 +1183,30 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
|||||||
.{},
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set our color attachment to be our drawable surface.
|
break :desc desc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare our color atachment (output).
|
||||||
const attachments = objc.Object.fromId(desc.getProperty(?*anyopaque, "colorAttachments"));
|
const attachments = objc.Object.fromId(desc.getProperty(?*anyopaque, "colorAttachments"));
|
||||||
{
|
|
||||||
const attachment = attachments.msgSend(
|
const attachment = attachments.msgSend(
|
||||||
objc.Object,
|
objc.Object,
|
||||||
objc.sel("objectAtIndexedSubscript:"),
|
objc.sel("objectAtIndexedSubscript:"),
|
||||||
.{@as(c_ulong, 0)},
|
.{@as(c_ulong, 0)},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Texture is a property of CAMetalDrawable but if you run
|
|
||||||
// Ghostty in XCode in debug mode it returns a CaptureMTLDrawable
|
|
||||||
// which ironically doesn't implement CAMetalDrawable as a
|
|
||||||
// property so we just send a message.
|
|
||||||
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
|
||||||
attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear));
|
attachment.setProperty("loadAction", @intFromEnum(mtl.MTLLoadAction.clear));
|
||||||
attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store));
|
attachment.setProperty("storeAction", @intFromEnum(mtl.MTLStoreAction.store));
|
||||||
attachment.setProperty("texture", texture);
|
|
||||||
attachment.setProperty("clearColor", mtl.MTLClearColor{
|
attachment.setProperty("clearColor", mtl.MTLClearColor{
|
||||||
.red = 0,
|
.red = 0,
|
||||||
.green = 0,
|
.green = 0,
|
||||||
.blue = 0,
|
.blue = 0,
|
||||||
.alpha = 1,
|
.alpha = 1,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
break :desc desc;
|
const post_len = self.shaders.post_pipelines.len;
|
||||||
};
|
|
||||||
|
for (self.shaders.post_pipelines[0 .. post_len - 1]) |pipeline| {
|
||||||
|
// Set our color attachment to be our front texture.
|
||||||
|
attachment.setProperty("texture", state.front_texture.value);
|
||||||
|
|
||||||
// MTLRenderCommandEncoder
|
// MTLRenderCommandEncoder
|
||||||
const encoder = buffer.msgSend(
|
const encoder = buffer.msgSend(
|
||||||
@ -1213,8 +1216,36 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
|||||||
);
|
);
|
||||||
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
|
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
|
||||||
|
|
||||||
for (self.shaders.post_pipelines) |pipeline| {
|
// Draw shader
|
||||||
try self.drawPostShader(encoder, pipeline, &state);
|
try self.drawPostShader(encoder, pipeline, state);
|
||||||
|
// Swap the front and back textures.
|
||||||
|
state.swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the final shader directly to the drawable.
|
||||||
|
{
|
||||||
|
// Set our color attachment to be our drawable.
|
||||||
|
//
|
||||||
|
// Texture is a property of CAMetalDrawable but if you run
|
||||||
|
// Ghostty in XCode in debug mode it returns a CaptureMTLDrawable
|
||||||
|
// which ironically doesn't implement CAMetalDrawable as a
|
||||||
|
// property so we just send a message.
|
||||||
|
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
|
||||||
|
attachment.setProperty("texture", texture);
|
||||||
|
|
||||||
|
// MTLRenderCommandEncoder
|
||||||
|
const encoder = buffer.msgSend(
|
||||||
|
objc.Object,
|
||||||
|
objc.sel("renderCommandEncoderWithDescriptor:"),
|
||||||
|
.{desc.value},
|
||||||
|
);
|
||||||
|
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
|
||||||
|
|
||||||
|
try self.drawPostShader(
|
||||||
|
encoder,
|
||||||
|
self.shaders.post_pipelines[post_len - 1],
|
||||||
|
state,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1311,7 +1342,7 @@ fn drawPostShader(
|
|||||||
void,
|
void,
|
||||||
objc.sel("setFragmentTexture:atIndex:"),
|
objc.sel("setFragmentTexture:atIndex:"),
|
||||||
.{
|
.{
|
||||||
state.screen_texture.value,
|
state.back_texture.value,
|
||||||
@as(c_ulong, 0),
|
@as(c_ulong, 0),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -1828,7 +1859,8 @@ pub fn setScreenSize(
|
|||||||
// Only free our previous texture if this isn't our first
|
// Only free our previous texture if this isn't our first
|
||||||
// time setting the custom shader state.
|
// time setting the custom shader state.
|
||||||
if (state.uniforms.resolution[0] > 0) {
|
if (state.uniforms.resolution[0] > 0) {
|
||||||
deinitMTLResource(state.screen_texture);
|
deinitMTLResource(state.front_texture);
|
||||||
|
deinitMTLResource(state.back_texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.uniforms.resolution = .{
|
state.uniforms.resolution = .{
|
||||||
@ -1837,7 +1869,7 @@ pub fn setScreenSize(
|
|||||||
1,
|
1,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.screen_texture = screen_texture: {
|
state.front_texture = texture: {
|
||||||
// This texture is the size of our drawable but supports being a
|
// This texture is the size of our drawable but supports being a
|
||||||
// render target AND reading so that the custom shaders can read from it.
|
// render target AND reading so that the custom shaders can read from it.
|
||||||
const desc = init: {
|
const desc = init: {
|
||||||
@ -1864,7 +1896,37 @@ pub fn setScreenSize(
|
|||||||
.{desc},
|
.{desc},
|
||||||
) orelse return error.MetalFailed;
|
) orelse return error.MetalFailed;
|
||||||
|
|
||||||
break :screen_texture objc.Object.fromId(id);
|
break :texture objc.Object.fromId(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
state.back_texture = texture: {
|
||||||
|
// This texture is the size of our drawable but supports being a
|
||||||
|
// render target AND reading so that the custom shaders can read from it.
|
||||||
|
const desc = init: {
|
||||||
|
const Class = objc.getClass("MTLTextureDescriptor").?;
|
||||||
|
const id_alloc = Class.msgSend(objc.Object, objc.sel("alloc"), .{});
|
||||||
|
const id_init = id_alloc.msgSend(objc.Object, objc.sel("init"), .{});
|
||||||
|
break :init id_init;
|
||||||
|
};
|
||||||
|
desc.setProperty("pixelFormat", @intFromEnum(mtl.MTLPixelFormat.bgra8unorm));
|
||||||
|
desc.setProperty("width", @as(c_ulong, @intCast(dim.width)));
|
||||||
|
desc.setProperty("height", @as(c_ulong, @intCast(dim.height)));
|
||||||
|
desc.setProperty(
|
||||||
|
"usage",
|
||||||
|
@intFromEnum(mtl.MTLTextureUsage.render_target) |
|
||||||
|
@intFromEnum(mtl.MTLTextureUsage.shader_read) |
|
||||||
|
@intFromEnum(mtl.MTLTextureUsage.shader_write),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we fail to create the texture, then we just don't have a screen
|
||||||
|
// texture and our custom shaders won't run.
|
||||||
|
const id = self.gpu_state.device.msgSend(
|
||||||
|
?*anyopaque,
|
||||||
|
objc.sel("newTextureWithDescriptor:"),
|
||||||
|
.{desc},
|
||||||
|
) orelse return error.MetalFailed;
|
||||||
|
|
||||||
|
break :texture objc.Object.fromId(id);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user