制作不断重绘的Metal NSView的正确方法

The right way to make a continuously redrawn Metal NSView

我正在学习 Metal 和 Cocoa 并尝试制作样板应用程序作为未来实验的平台。作为该过程的一部分,我正在实现一个将以 60fps 重绘自身(或者更准确地说,其 CAMetalLayer 的内容)的视图。同样出于教育目的,我避免使用 MTKView(对于 "learning Cocoa part")。这是我如何解决问题的简短代码片段:

@implementation MyMetalView // which is a subclass of NSView

- (BOOL) isOpaque {
    return YES;
}

- (NSViewLayerContentsRedrawPolicy) layerContentsRedrawPolicy {
    return NSViewLayerContentsRedrawOnSetNeedsDisplay;
}

- (CALayer *) makeBackingLayer {
    // create CAMetalLayer with default device
}

- (BOOL) wantsLayer {
    return YES;
}

- (BOOL) wantsUpdateLayer {
    return YES;
}

- (void) displayLayer:(CALayer *)layer {
    id<MTLCommandBuffer> cmdBuffer = [_commandQueue commandBuffer];
    id<CAMetalDrawable> drawable = [((CAMetalLayer *) layer) nextDrawable];

    [cmdBuffer enqueue];
    [cmdBuffer presentDrawable:drawable];

    // rendering

    [cmdBuffer commit];
}

@end

int main() {
    // init app, window and MyMetalView instance

    // invocation will call [myMetalViewInstance setNeedsDisplay:YES]
    [NSTimer scheduledTimerWithTimeInterval:1./60. invocation:setNeedsDisplayInvokation repeats:YES];

    [NSApp run];
    return 0;
}

这是做我想做的事情的正确方法吗?还是我选择了一个很长但不推荐的方法?

强烈建议使用 CVDisplayLink 而不是通用的 NSTimer 来驱动需要匹配显示器刷新率的动画。

您需要创建一个 ivar 或 属性 来保存 CVDisplayLinkRef:

CVDisplayLinkRef displayLink;

然后,当您的视图进入屏幕并且您想要开始动画时,您将创建、配置并启动显示 link:

CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, self);
CVDisplayLinkStart(displayLink);

显示 link 回调应该是一个静态函数。它将在显示器的 v-blank 周期开始时调用(在没有物理 v-blank 的现代显示器上,这仍然以常规的 60Hz 节奏发生):

static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{

    [(MyMetalView *)displayLinkContext setNeedsDisplay:YES];
    return kCVReturnSuccess;
}

当您的视图离开显示时,或者如果您想暂停,您可以释放显示 link 并将其清除:

CVDisplayLinkRelease(displayLink);

按照@warrenm 解决方案添加 dispatch_sync 刷新和其他次要 :

#import "imageDrawer.h"
#import "image/ImageBuffer.h"
#import "common.hpp"

@implementation imageDrawer {
    CVDisplayLinkRef displayLink;
}

CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
    dispatch_sync(dispatch_get_main_queue(), ^{
        [(__bridge imageDrawer*)displayLinkContext setNeedsDisplay:YES];
    });
    return kCVReturnSuccess;
}

-(void)setContDisplay {
    CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
    CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, (__bridge void*)self);
    CVDisplayLinkStart(displayLink);
}

-(void)awakeFromNib {

    [self setContDisplay];
}

- (void)drawRect:(NSRect)rect {
    [super drawRect:rect];

    int w=rect.size.width, h=rect.size.height;
    // do the drawing...
}

@end