I'm using a MTKView to draw Metal content. It's configured as follows:
mtkView = MTKView(frame: self.view.frame, device: device)
mtkView.colorPixelFormat = .bgra8Unorm
mtkView.delegate=self
mtkView.sampleCount=4
mtkView.isPaused=true
mtkView.enableSetNeedsDisplay=true
setFrameSize
is overriden to trigger a redisplay.
Whenever the view resizes it scales its old content before it redraws everything. This gives a jittering feeling.
I tried setting the contentGravity
property of the MTKView's layer to a non-resizing value, but that totally messes up the scale and position of the content. It seems MTKView doesn't want me to fiddle with that parameter.
How can I make sure that during a resize the content is always properly redrawn?
In my usage of Metal and MTKView
, I tried various combinations of presentsWithTransaction
and waitUntilScheduled
without success. I still experienced occasional frames of stretched content in between frames of properly rendered content during live resize.
Finally, I dropped MTKView
altogether and made my own NSView subclass that uses CAMetalLayer
and resize looks good now (without any use of presentsWithTransaction
or waitUntilScheduled
). One key bit is that I needed to set the layer's autoresizingMask
to get the displayLayer
method to be called every frame during window resize.
Here's the header file:
#import <Cocoa/Cocoa.h>
@interface MyMTLView : NSView<CALayerDelegate>
@end
Here's the implementation:
#import <QuartzCore/CAMetalLayer.h>
#import <Metal/Metal.h>
@implementation MyMTLView
- (id)initWithFrame:(NSRect)frame
{
if (!(self = [super initWithFrame:frame])) {
return self;
}
// We want to be backed by a CAMetalLayer.
self.wantsLayer = YES;
// We want to redraw the layer during live window resize.
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
// Not strictly necessary, but in case something goes wrong with live window
// resize, this layer placement makes it more obvious what's going wrong.
self.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft;
return self;
}
- (CALayer*)makeBackingLayer
{
CAMetalLayer* metalLayer = [CAMetalLayer layer];
metalLayer.device = MTLCreateSystemDefaultDevice();
metalLayer.delegate = self;
// *Both* of these properties are crucial to getting displayLayer to be
// called during live window resize.
metalLayer.autoresizingMask = kCALayerHeightSizable | kCALayerWidthSizable;
metalLayer.needsDisplayOnBoundsChange = YES;
return metalLayer;
}
- (CAMetalLayer*)metalLayer
{
return (CAMetalLayer*)self.layer;
}
- (void)setFrameSize:(NSSize)newSize
{
[super setFrameSize:newSize];
self.metalLayer.drawableSize = newSize;
}
- (void)displayLayer:(CALayer*)layer
{
// Do drawing with Metal.
}
@end
For reference, I do all my Metal drawing in MTKView's drawRect
method.
I have the same problem with glitches on view resizing. You can even reproduce it in the HelloTriangle example from the Apple's developer site. However the effect is minimized because the triangle is drawn near the middle of the screen, and it's the content closest to the edge of the window, opposite the corner that drags, that is effected worst. The developer notes regarding use of presentsWithTransaction
and waitUntilScheduled
do not work for me either.
My solution was to add a Metal layer beneath the window.contentView.layer
, and to make that layer large enough that it rarely needs to be resized. The reason this works is that, unlike the window.contentView.layer
, which sizes itself automatically to the view (in turn maintaining the window size), you have explicit control of the sublayer size. This eliminates the flickering.
This helped me - https://github.com/trishume/MetalTest
He uses MetalLayer and careful setting of various properties. Everything is pretty smooth even with two side by side in synchronised scroll views with 45megapixel images.
A link to my original problem How do I position an image correctly in MTKView?