正确的方法来不断重绘Metal NSView

时间:2016-06-13 16:18:52

标签: macos cocoa metal

我正在学习Metal和Cocoa,并尝试将样板应用程序作为未来实验的平台。作为整个过程的一部分,我正在实现一个视图,该视图将以60fps重绘自身(或者更确切地说,是CAMetalLayer的内容)。同样出于教育目的,我避免MTKView(“学习可可部分”)。这是我如何处理问题的缩写代码片段:

@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;
}

这是做我想要的正确方法吗?或者我选择了一个长期而不推荐的方法?

2 个答案:

答案 0 :(得分:2)

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

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

CVDisplayLinkRef displayLink;

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

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

显示链接回调应该是静态函数。它将在显示器的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;
}

当您的视图离开显示屏或想要暂停时,您可以释放显示链接并将其取消:

CVDisplayLinkRelease(displayLink);

答案 1 :(得分:0)

在@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