Skip to main content

Crate metal_live_resize

Crate metal_live_resize 

Source
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:

  1. contentsGravity = kCAGravityTopLeft — pins stale frames to the top-left corner instead of scaling them, so the “wrong” area is clipped rather than stretched.
  2. 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:

  1. 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 = kCAGravityTopLeft and contentsScale = view.window.backingScaleFactor on the layer.
drawable_texture_size
Gets the actual pixel dimensions of the drawable’s current texture.
set_contents_gravity_top_left
Sets contentsGravity = kCAGravityTopLeft on the layer.
set_contents_scale
Sets contentsScale on the layer.
view_backing_scale
Reads the backingScaleFactor from an NSView’s window.