renderer/metal: don't recreate custom shader resources per frame

This commit is contained in:
Mitchell Hashimoto
2023-11-16 17:26:25 -08:00
parent d3bc1ab6da
commit 244e7266a1

View File

@ -116,9 +116,21 @@ swapchain: objc.Object, // CAMetalLayer
texture_greyscale: objc.Object, // MTLTexture
texture_color: objc.Object, // MTLTexture
/// The screen texture. This is only set if we have custom shaders. If
/// we don't have custom shaders, we render directly to the drawable.
texture_screen: ?objc.Object, // MTLTexture
/// Custom shader state. This is only set if we have custom shaders.
custom_shader_state: ?CustomShaderState = null,
pub const CustomShaderState = struct {
/// The screen texture that we render the terminal to. If we don't have
/// custom shaders, we render directly to the drawable.
screen_texture: objc.Object, // MTLTexture
sampler: mtl_sampler.Sampler,
uniforms: mtl_shaders.PostUniforms,
pub fn deinit(self: *CustomShaderState) void {
deinitMTLResource(self.screen_texture);
self.sampler.deinit();
}
};
/// The configuration for this renderer that is derived from the main
/// configuration. This must be exported so that we don't need to
@ -288,9 +300,37 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
break :err &.{};
};
// If we have custom shaders then setup our state
var custom_shader_state: ?CustomShaderState = state: {
if (custom_shaders.len == 0) break :state null;
// Build our sampler for our texture
var sampler = try mtl_sampler.Sampler.init(device);
errdefer sampler.deinit();
break :state .{
// Resolution and screen texture will be fixed up by first
// call to setScreenSize. This happens before any draw call.
.screen_texture = undefined,
.sampler = sampler,
.uniforms = .{
.resolution = .{ 0, 0, 1 },
.time = 1,
.time_delta = 1,
.frame_rate = 1,
.frame = 1,
.channel_time = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4,
.channel_resolution = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4,
.mouse = .{ 0, 0, 0, 0 },
.date = .{ 0, 0, 0, 0 },
.sample_rate = 1,
},
};
};
errdefer if (custom_shader_state) |*state| state.deinit();
// Initialize our shaders
var shaders = try Shaders.init(alloc, device, custom_shaders);
//var shaders = try Shaders.init(alloc, device, &.{@embedFile("shaders/temp3.metal")});
errdefer shaders.deinit(alloc);
// Font atlas textures
@ -336,7 +376,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal {
.swapchain = swapchain,
.texture_greyscale = texture_greyscale,
.texture_color = texture_color,
.texture_screen = null,
.custom_shader_state = custom_shader_state,
};
}
@ -360,9 +400,10 @@ pub fn deinit(self: *Metal) void {
self.buf_instance.deinit();
deinitMTLResource(self.texture_greyscale);
deinitMTLResource(self.texture_color);
if (self.texture_screen) |tex| deinitMTLResource(tex);
self.queue.msgSend(void, objc.sel("release"), .{});
if (self.custom_shader_state) |*state| state.deinit();
self.shaders.deinit(self.alloc);
self.* = undefined;
@ -621,7 +662,9 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
// Get our screen texture. If we don't have a dedicated screen texture
// then we just use the drawable texture.
const screen_texture = self.texture_screen orelse tex: {
const screen_texture = if (self.custom_shader_state) |state|
state.screen_texture
else tex: {
const texture = drawable.msgSend(objc.c.id, objc.sel("texture"), .{});
break :tex objc.Object.fromId(texture);
};
@ -703,9 +746,7 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
// If we have custom shaders AND we have a screen texture, then we
// render the custom shaders.
if (self.config.custom_shaders.items.len > 0 and
self.texture_screen != null)
{
if (self.custom_shader_state) |state| {
// MTLRenderPassDescriptor
const desc = desc: {
const MTLRenderPassDescriptor = objc.getClass("MTLRenderPassDescriptor").?;
@ -751,7 +792,9 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
);
defer encoder.msgSend(void, objc.sel("endEncoding"), .{});
try self.drawPostShader(encoder, screen_texture.value);
for (self.shaders.post_pipelines) |pipeline| {
try self.drawPostShader(encoder, pipeline, &state);
}
}
buffer.msgSend(void, objc.sel("presentDrawable:"), .{drawable.value});
@ -761,57 +804,42 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
fn drawPostShader(
self: *Metal,
encoder: objc.Object,
texture: objc.c.id,
pipeline: objc.Object,
state: *const CustomShaderState,
) !void {
// Build our sampler for our texture
var sampler = try mtl_sampler.Sampler.init(self.device);
defer sampler.deinit();
const Buffer = mtl_buffer.Buffer(mtl_shaders.PostUniforms);
var buf = try Buffer.initFill(self.device, &.{.{
.resolution = .{
@floatFromInt(self.screen_size.?.width),
@floatFromInt(self.screen_size.?.height),
1,
},
.time = 1,
.time_delta = 1,
.frame_rate = 1,
.frame = 1,
.channel_time = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4,
.channel_resolution = [1][4]f32{.{ 0, 0, 0, 0 }} ** 4,
.mouse = .{ 0, 0, 0, 0 },
.date = .{ 0, 0, 0, 0 },
.sample_rate = 1,
}});
defer buf.deinit();
_ = self;
// Use our custom shader pipeline
encoder.msgSend(
void,
objc.sel("setRenderPipelineState:"),
.{self.shaders.post_pipelines[0].value},
.{pipeline.value},
);
// Set our sampler
encoder.msgSend(
void,
objc.sel("setFragmentSamplerState:atIndex:"),
.{ sampler.sampler.value, @as(c_ulong, 0) },
.{ state.sampler.sampler.value, @as(c_ulong, 0) },
);
// Set our buffer
// Set our uniforms
encoder.msgSend(
void,
objc.sel("setFragmentBuffer:offset:atIndex:"),
.{ buf.buffer.value, @as(c_ulong, 0), @as(c_ulong, 0) },
objc.sel("setFragmentBytes:length:atIndex:"),
.{
@as(*const anyopaque, @ptrCast(&state.uniforms)),
@as(c_ulong, @sizeOf(@TypeOf(state.uniforms))),
@as(c_ulong, 0),
},
);
// Screen texture
encoder.msgSend(
void,
objc.sel("setFragmentTexture:atIndex:"),
.{
texture,
state.screen_texture.value,
@as(c_ulong, 0),
},
);
@ -1248,11 +1276,21 @@ pub fn setScreenSize(
self.cells.clearAndFree(self.alloc);
self.cells_bg.clearAndFree(self.alloc);
// Setup our screen texture
const screen_texture: ?objc.Object = screen_texture: {
// If we have no custom shaders then we don't need a screen texture.
if (self.config.custom_shaders.items.len == 0) break :screen_texture null;
// If we have custom shaders then we update the state
if (self.custom_shader_state) |*state| {
// Only free our previous texture if this isn't our first
// time setting the custom shader state.
if (state.uniforms.resolution[0] > 0) {
deinitMTLResource(state.screen_texture);
}
state.uniforms.resolution = .{
@floatFromInt(dim.width),
@floatFromInt(dim.height),
1,
};
state.screen_texture = screen_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: {
@ -1277,15 +1315,11 @@ pub fn setScreenSize(
?*anyopaque,
objc.sel("newTextureWithDescriptor:"),
.{desc},
) orelse break :screen_texture null;
) orelse return error.MetalFailed;
break :screen_texture objc.Object.fromId(id);
};
errdefer if (screen_texture) |tex| tex.msgSend(void, objc.sel("release"), .{});
// Set our new screen texture
if (self.texture_screen) |tex| tex.msgSend(void, objc.sel("release"), .{});
self.texture_screen = screen_texture;
}
log.debug("screen size screen={} grid={}, cell={}", .{ dim, grid_size, self.cell_size });
}