Glitchless Metal Window Resizing
There’s a problem with Apple’s Metal MTKView on macOS which is that seemingly nobody can figure out how to get smooth window resizing to work properly. I just figured it out, more on that later. If you reposition the triangle in Apple’s Hello Triangle program to the left (to make the rescaling more apparent) then you can see it judders horribly when the window is resized:
What’s happening is often the new Metal frame doesn’t arrive in time and it draws a stretched version of the previous frame instead. There’s a number of places on the internet dating back to 2017 with various people encountering the problem:
- Stack Overflow: Resizing MTKView scales old content before redraw
- Apple Developer Forums: Redraw MTKView when its size changes
- Apple Developer Forums: Unwanted MTKView content stretching when I resize/zoom the window
- iTerm2 switches away from Metal to software rendering when resizing the window
Basically everyone who tries to make something with Metal that’s not a game runs into this problem and it looks horrible. As far as I can tell nobody has figured out how to fix it properly before and posted about it afterwards. Note in the first dev forums thread that an Apple employee claimed they were looking into this problem almost a year ago with no resolution.
I started a test project to try out different ways of drawing with Metal during resize to see if I could get any of them to work properly. First I replicated the MTKView problems and tried to fix them by tweaking lots of different things, including all three modes of triggering draws listed in the docs and using presentsWithTransaction
in the way the docs suggest but nothing helped. Then I made a version using Core Graphics and an NSView subclass and stacked it below my Metal view so that I could have a reference that worked properly.
The Solution
Then I tried the accepted answer by Max on the Stack Overflow post which uses CAMetalLayer
and some resizing-related properties. This reduced the frequency of glitches quite a bit, but didn’t eliminate them. So I added in presentsWithTransaction = true
, which wasn’t enough on its own, but combining that with commandBuffer.waitUntilScheduled()
then presenting as suggested in the Apple CAMetalLayer
docs fixed all the glitches! I also needed to do some size conversion to make the accepted answer’s recipe draw crisply on high DPI displays.
Edit: @CoreyDotCom on Twitter reminded me I forgot to mention something. If you follow the recipe from the Stack Overflow post, it will appear to be glitch-free, but it actually isn’t. The layerContentsPlacement = .topLeft
makes the glitches manifest as small broken slices near the moving window edge, which are very difficult to notice since the edge is moving quickly. When you change the placement policy to layerContentsPlacement = .scaleAxesIndependently
to match the behavior of MTKView
you see that there are still occasional glitches. Corey reports frame rate issues with presentsWithTransaction
, and if this is the case for you as well it may be preferable to just mask the occasional remaining glitches with the top left placement policy.
Working Code
I now have a Metal triangle test program that resizes smoothly and without judder.
Check out my test project: Github repo
And the specific code file containing the working recipe: MetalLayerView
In the gif below the top is the broken MTKView
, the middle NSView
, and the bottom the working CAMetalLayer
recipe. Contrast the shakey left edge of the top triangle with the stable bottom one: