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.
layerContentsRedrawPolicy
toNSViewLayerContentsRedrawDuringViewResize
(.duringViewResize
in Swift) help? – PinpointMTKView
? For example, what are the settings for thepaused
,enableSetNeedsDisplay
, andautoResizeDrawable
properties? – PinpointpresentsWithTransaction
to true. If that isn't sufficient, you may need to follow the advice in the last paragraph of the docs for that property. The issue is that Metal drawing is asynchronous. You commit a command buffer. It is actually scheduled some time later. If you use itspresent(_:)
, then it will call the drawable'spresent()
method at that time. Even that is delayed. It will wait until all rendering to its texture is completed (not just scheduled). – Pinpointpresent
as well as the suggestion from the docs with the drawable'spresent
. Both still cause the view the scale the content before the new data is displayed. – Chargeablepresent
though, the scaled version remains on display until after 5 seconds when the following warning is logged: ----CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces, or set CA_ASSERT_MAIN_THREAD_TRANSACTIONS=1 to abort when an implicit transaction isn't created on a main thread.---- I checked that the draw method is called from the main thread. After this warning, the display is suddenly updated to the correct image. – Chargeablecommit
runs on a different thread. – ChargeablepresentsWithTransaction
to true and, in the draw method, after committing the command buffer, doingwaitUntilScheduled()
and then calling the drawable'spresent()
? Also, can you confirm that your draw method is being called during the resize, hopefully multiple times? – PinpointsetFrameSize
and that revealed that the draw method gets called (mostly) after the runloop ends (after thebeforeWaiting
runloop activity). When I explicitly call the draw method fromsetFrameSize
it actually works! I'm not sue though whether that's a good place to call draw. I'm also afraid that thewaitUntilScheduled()
call has a performance penalty. Is that true? – Chargeabledraw()
andsetNeedsDisplay()
are called fromsetFrameSize()
. It will only display the new image data when onlysetNeedsDisplay()
is called.... – ChargeablewaitUntilScheduled()
call will have a performance penalty. You'd only want to do it during the resize. In that case, it would theoretically reduce how quickly the window/view could update in response to the resize to how fast you could draw frames, which is basically a requirement implicit in your question. – Pinpoint