From de1336faddbdffc8fb4f58af3597d3f031e2b2d9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 9 Aug 2026 15:44:25 -0700 Subject: [PATCH] renderer/metal: warm up command queue and shader pipelines Extend the Metal portion of the startup warmup thread to also create (and discard) a command queue and build (and discard) the shader pipelines for both pixel formats we may use (which one is used depends on the blending config). The first command queue for a device and the first render pipeline state creations pay one-time driver setup and shader compilation costs; once warm, the real creations during surface initialization hit driver and OS caches. Measured on macOS (Apple Silicon) with local timing instrumentation during app launch, first surface renderer initialization: queue creation: 717us -> 93us pipeline builds: 1023us -> 347us renderer init total: 2777us -> 1466us --- src/renderer/Metal.zig | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 1ad6b812b..7b59ce500 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -421,7 +421,34 @@ pub inline fn beginFrame( /// one-time cost. pub fn warmup() void { const device = chooseDevice() catch return; - device.release(); + defer device.release(); + + // Create and release a command queue. The first command queue + // created for a device pays additional one-time driver setup + // costs; subsequent creations are much cheaper. + const queue = device.msgSend(objc.Object, objc.sel("newCommandQueue"), .{}); + queue.release(); + + // Build and discard our shader pipelines for both pixel formats we + // may use (which one is used depends on the blending config). The + // first pipeline state creation compiles shaders which is slow; + // once warm, later creations hit driver and OS caches. + inline for (.{ + mtl.MTLPixelFormat.bgra8unorm_srgb, + mtl.MTLPixelFormat.bgra8unorm, + }) |format| { + if (shaders.Shaders.init( + std.heap.c_allocator, + device, + &.{}, + format, + )) |s| { + var s_mut = s; + s_mut.deinit(std.heap.c_allocator); + } else |err| { + log.warn("metal warmup shader init failed err={}", .{err}); + } + } } fn chooseDevice() error{NoMetalDevice}!objc.Object {