mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-25 00:21:46 +00:00
renderer/metal: warm up the Metal device machinery at app creation
The first Metal device query in a process (MTLCopyAllDevices) takes multiple milliseconds; once the framework is warm, subsequent queries are effectively free (measured ~15ms cold, ~1us warm in isolation). This cost was paid during the first surface's renderer initialization, on the critical path to the first window. Measured on macOS (Apple Silicon) with local timing instrumentation during app launch, first surface renderer initialization: GraphicsAPI.init before: 4227us (device query ~3.5ms) GraphicsAPI.init after: ~900us (device query 20-25us)
This commit is contained in:
12
src/App.zig
12
src/App.zig
@@ -106,6 +106,18 @@ fn threadedWarmup() void {
|
||||
log.warn("font warmup thread spawn failed err={}", .{err});
|
||||
}
|
||||
}
|
||||
|
||||
// Same for the renderer's graphics API (e.g. Metal), which pays
|
||||
// one-time framework initialization costs on first use.
|
||||
if (comptime @hasDecl(renderer.Renderer.API, "warmup")) {
|
||||
if (std.Thread.spawn(
|
||||
.{},
|
||||
renderer.Renderer.API.warmup,
|
||||
.{},
|
||||
)) |thr| thr.detach() else |err| {
|
||||
log.warn("renderer warmup thread spawn failed err={}", .{err});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the main app instance. This creates the main window, sets
|
||||
|
||||
@@ -407,6 +407,23 @@ pub inline fn beginFrame(
|
||||
return try Frame.begin(.{ .queue = self.queue }, renderer, target);
|
||||
}
|
||||
|
||||
/// Warm up the Metal device machinery. The first Metal device query in
|
||||
/// a process takes multiple milliseconds; once warm, subsequent queries
|
||||
/// are effectively free. Calling this early (e.g. on a background
|
||||
/// thread at app startup; Metal device queries are thread-safe) moves
|
||||
/// that one-time cost off the critical path of the first surface's
|
||||
/// renderer initialization.
|
||||
///
|
||||
/// We deliberately do NOT cache the chosen device: the device set can
|
||||
/// change at runtime (e.g. an eGPU being plugged in or removed) and
|
||||
/// chooseDevice prefers removable GPUs, so every renderer init must
|
||||
/// re-choose. Only the underlying framework initialization is a
|
||||
/// one-time cost.
|
||||
pub fn warmup() void {
|
||||
const device = chooseDevice() catch return;
|
||||
device.release();
|
||||
}
|
||||
|
||||
fn chooseDevice() error{NoMetalDevice}!objc.Object {
|
||||
var chosen_device: ?objc.Object = null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user