Expand description
Glitch-free macOS CAMetalLayer window live resize.
Provides the minimum configuration primitives to prevent content distortion during window resize on macOS Metal apps.
§The problem
During live window resize on macOS, the compositor lags behind the
app’s rendering by one or more frames. By default, the compositor
stretches the previous drawable contents to fill the new window
bounds (contentsGravity = kCAGravityResize), which shows up as
visible wobble or distortion until the next frame is presented.
§The fix
Two one-time configuration calls on the CAMetalLayer:
contentsGravity = kCAGravityTopLeft— pins stale frames to the top-left corner instead of scaling them, so the “wrong” area is clipped rather than stretched.contentsScale = NSWindow.backingScaleFactor— ensures drawable pixels map 1:1 to screen pixels on Retina displays, without which topLeft gravity puts content at the wrong position.
And one per-frame discipline:
- Read drawable texture dimensions at render time, never cached
layer
width/height. During resize, the drawable’s texture may not yet match the cached size and rendering at the wrong size causes visible distortion.
§What NOT to do
The presentsWithTransaction = true + commandBuffer.waitUntilScheduled()
approach is sometimes suggested as an alternative. It has been tested
and breaks frame delivery — AppKit events fire (hit tests pass,
state updates) but nothing renders to the screen. The
contentsGravity + contentsScale approach alone is sufficient and
does not block the event loop.
§Reference
Pattern first documented by Tristan Hume, 2019: https://thume.ca/2019/06/19/glitchless-metal-window-resizing/
§Example
// macOS-only; the crate exposes no items on other platforms.
use metal_live_resize::configure_for_live_resize;
use core::ffi::c_void;
unsafe fn example(layer: *mut c_void, view: *mut c_void) {
// after attaching your CAMetalLayer to an NSView:
unsafe { configure_for_live_resize(layer, view) };
// per-frame, read the actual drawable size rather than cached w/h:
if let Some((w, h)) = unsafe { metal_live_resize::drawable_texture_size(layer) } {
let _ = (w, h); // render at (w, h)
}
}Functions§
- configure_
for_ ⚠live_ resize - Apply both live-resize fixes: sets
contentsGravity = kCAGravityTopLeftandcontentsScale = view.window.backingScaleFactoron the layer. - drawable_
texture_ ⚠size - Gets the actual pixel dimensions of the drawable’s current texture.
- set_
contents_ ⚠gravity_ top_ left - Sets
contentsGravity = kCAGravityTopLefton the layer. - set_
contents_ ⚠scale - Sets
contentsScaleon the layer. - view_
backing_ ⚠scale - Reads the
backingScaleFactorfrom anNSView’s window.