mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
renderer/metal: detect frame commit failures and notify surface
This commit is contained in:
@ -808,9 +808,18 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|||||||
const body = std.mem.sliceTo(¬ification.body, 0);
|
const body = std.mem.sliceTo(¬ification.body, 0);
|
||||||
try self.showDesktopNotification(title, body);
|
try self.showDesktopNotification(title, body);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.renderer_health => |health| self.updateRendererHealth(health),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called when our renderer health state changes.
|
||||||
|
fn updateRendererHealth(self: *Surface, health: renderer.Health) void {
|
||||||
|
log.warn("renderer health status change status={}", .{health});
|
||||||
|
if (!@hasDecl(apprt.runtime.Surface, "updateRendererHealth")) return;
|
||||||
|
self.rt_surface.updateRendererHealth(health);
|
||||||
|
}
|
||||||
|
|
||||||
/// Update our configuration at runtime.
|
/// Update our configuration at runtime.
|
||||||
fn changeConfig(self: *Surface, config: *const configpkg.Config) !void {
|
fn changeConfig(self: *Surface, config: *const configpkg.Config) !void {
|
||||||
// Update our new derived config immediately
|
// Update our new derived config immediately
|
||||||
|
@ -54,6 +54,9 @@ pub const Message = union(enum) {
|
|||||||
/// Desktop notification body.
|
/// Desktop notification body.
|
||||||
body: [255:0]u8,
|
body: [255:0]u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Health status change for the renderer.
|
||||||
|
renderer_health: renderer.Health,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A surface mailbox.
|
/// A surface mailbox.
|
||||||
|
@ -52,6 +52,14 @@ pub const Renderer = switch (build_config.renderer) {
|
|||||||
.webgl => WebGL,
|
.webgl => WebGL,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The health status of a renderer. These must be shared across all
|
||||||
|
/// renderers even if some states aren't reachable so that our API users
|
||||||
|
/// can use the same enum for all renderers.
|
||||||
|
pub const Health = enum(u8) {
|
||||||
|
healthy = 0,
|
||||||
|
unhealthy = 1,
|
||||||
|
};
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@import("std").testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ const assert = std.debug.assert;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
const Terminal = terminal.Terminal;
|
const Terminal = terminal.Terminal;
|
||||||
|
const Health = renderer.Health;
|
||||||
|
|
||||||
const mtl = @import("metal/api.zig");
|
const mtl = @import("metal/api.zig");
|
||||||
const mtl_buffer = @import("metal/buffer.zig");
|
const mtl_buffer = @import("metal/buffer.zig");
|
||||||
@ -121,6 +122,10 @@ texture_color: objc.Object, // MTLTexture
|
|||||||
/// Custom shader state. This is only set if we have custom shaders.
|
/// Custom shader state. This is only set if we have custom shaders.
|
||||||
custom_shader_state: ?CustomShaderState = null,
|
custom_shader_state: ?CustomShaderState = null,
|
||||||
|
|
||||||
|
/// Health of the last frame. Note that when we do double/triple buffering
|
||||||
|
/// this will have to be part of the frame state.
|
||||||
|
health: std.atomic.Value(Health) = .{ .raw = .healthy },
|
||||||
|
|
||||||
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.
|
||||||
@ -856,9 +861,56 @@ pub fn drawFrame(self: *Metal, surface: *apprt.Surface) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buffer.msgSend(void, objc.sel("presentDrawable:"), .{drawable.value});
|
buffer.msgSend(void, objc.sel("presentDrawable:"), .{drawable.value});
|
||||||
|
|
||||||
|
// Create our block to register for completion updates. This is used
|
||||||
|
// so we can detect failures. The block is deallocated by the objC
|
||||||
|
// runtime on success.
|
||||||
|
const block = try CompletionBlock.init(.{ .self = self }, &bufferCompleted);
|
||||||
|
errdefer block.deinit();
|
||||||
|
buffer.msgSend(void, objc.sel("addCompletedHandler:"), .{block.context});
|
||||||
|
|
||||||
buffer.msgSend(void, objc.sel("commit"), .{});
|
buffer.msgSend(void, objc.sel("commit"), .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is the block type used for the addCompletedHandler call.back.
|
||||||
|
const CompletionBlock = objc.Block(struct { self: *Metal }, .{
|
||||||
|
objc.c.id, // MTLCommandBuffer
|
||||||
|
}, void);
|
||||||
|
|
||||||
|
/// This is the callback called by the CompletionBlock invocation for
|
||||||
|
/// addCompletedHandler.
|
||||||
|
///
|
||||||
|
/// Note: this is USUALLY called on a separate thread because the renderer
|
||||||
|
/// thread and the Apple event loop threads are usually different. Therefore,
|
||||||
|
/// we need to be mindful of thread safety here.
|
||||||
|
fn bufferCompleted(
|
||||||
|
block: *const CompletionBlock.Context,
|
||||||
|
buffer_id: objc.c.id,
|
||||||
|
) callconv(.C) void {
|
||||||
|
const self = block.self;
|
||||||
|
const buffer = objc.Object.fromId(buffer_id);
|
||||||
|
|
||||||
|
// Get our command buffer status. If it is anything other than error
|
||||||
|
// then we don't care and just return right away. We're looking for
|
||||||
|
// errors so that we can log them.
|
||||||
|
const status = buffer.getProperty(mtl.MTLCommandBufferStatus, "status");
|
||||||
|
const health: Health = switch (status) {
|
||||||
|
.@"error" => .unhealthy,
|
||||||
|
else => .healthy,
|
||||||
|
};
|
||||||
|
|
||||||
|
// If our health value hasn't changed, then we do nothing. We don't
|
||||||
|
// do a cmpxchg here because strict atomicity isn't important.
|
||||||
|
if (self.health.load(.SeqCst) == health) return;
|
||||||
|
self.health.store(health, .SeqCst);
|
||||||
|
|
||||||
|
// Our health value changed, so we notify the surface so that it
|
||||||
|
// can do something about it.
|
||||||
|
_ = self.surface_mailbox.push(.{
|
||||||
|
.renderer_health = health,
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
}
|
||||||
|
|
||||||
fn drawPostShader(
|
fn drawPostShader(
|
||||||
self: *Metal,
|
self: *Metal,
|
||||||
encoder: objc.Object,
|
encoder: objc.Object,
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
//! This file contains the definitions of the Metal API that we use.
|
//! This file contains the definitions of the Metal API that we use.
|
||||||
|
|
||||||
|
/// https://developer.apple.com/documentation/metal/mtlcommandbufferstatus?language=objc
|
||||||
|
pub const MTLCommandBufferStatus = enum(c_ulong) {
|
||||||
|
not_enqueued = 0,
|
||||||
|
enqueued = 1,
|
||||||
|
committed = 2,
|
||||||
|
scheduled = 3,
|
||||||
|
completed = 4,
|
||||||
|
@"error" = 5,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
/// https://developer.apple.com/documentation/metal/mtlloadaction?language=objc
|
/// https://developer.apple.com/documentation/metal/mtlloadaction?language=objc
|
||||||
pub const MTLLoadAction = enum(c_ulong) {
|
pub const MTLLoadAction = enum(c_ulong) {
|
||||||
dont_care = 0,
|
dont_care = 0,
|
||||||
|
Reference in New Issue
Block a user