使用 NSView 进行图层支持的绘图时没有调用 drawRect

Posted

技术标签:

【中文标题】使用 NSView 进行图层支持的绘图时没有调用 drawRect【英文标题】:drawRect not getting called for layer backed drawing with NSView 【发布时间】:2018-10-29 09:33:55 【问题描述】:

我正在尝试将我的 Opengl 应用程序迁移到 Metal,并且我正在使用 CAMetalLayer 来实现 Metal。 我在继承的NSView 类对象上设置了以下属性:

self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
[self setWantsLayer:YES];

我已经检查了关于 SO 的其他答案,例如 NSView subclass - drawRect: not called

所以,我尝试按照上面答案中的建议分配委托,类似这样

[self.layer setDelegate:self];

我得到以下编译错误:

Cannot initialize a parameter of type 'id<CALayerDelegate> _Nullable' with an lvalue of type 'MyNSView *'

此外,我还在https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/SettingUpLayerObjects/SettingUpLayerObjects.html 看到了设置图层对象的文档,其中指出:

对于具有自定义内容的 layer-backed 视图,您应该继续 覆盖视图的方法来进行绘图。支持层的视图 自动使自己成为其层的代表并实现 所需的委托方法,你不应该改变它 配置。相反,您应该实现视图的 drawRect: 绘制内容的方法。

因此,据此,我什至不必在我的情况下设置委托,我已经在MyNSView 中实现了drawRect 函数。仍然没有被调用。

还有什么需要做的吗?

谢谢!

编辑: 因此,我通过使我的层遵循协议CALayerDelegate 来修复编译错误,但我仍然没有收到任何对drawLayerdrawRect 的调用。

制作BackingLayer

- (CALayer*)makeBackingLayer

    id <MTLDevice> device = MTLCreateSystemDefaultDevice();
    CAMetalLayer *backingLayer   = [CAMetalLayer layer];
    backingLayer.opaque          = YES;
    backingLayer.device          = device;
    backingLayer.pixelFormat     = MTLPixelFormatBGRA8Unorm;
    backingLayer.framebufferOnly = YES;

//    [backingLayer setDelegate:self]; //Tried setting the delegate 
                                       //here as well

    return backingLayer;

初始化函数

self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
[self setWantsLayer:YES];
[self.layer setDelegate:self];

绘制函数

- (void)displayLayer:(CALayer *)layer

    [self drawRect:self.bounds];


- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx 
    [self drawRect:self.bounds];


-(void) drawRect: (NSRect) dirtyRect

    [super drawRect:dirtyRect];

我已经实现了所有的绘图函数来检查它们是否被击中,但是我没有收到任何这些函数的调用。

为了将内容绘制到 NSView,我绘制到屏幕外资源并从 NSView 的 CALayer 获取 drawable,并在提交 commandBuffer 之前调用 presentDrawable

【问题讨论】:

你为什么不使用MTKView的子类?你是如何安排使用CAMetalLayer 作为图层的?您是否覆盖了-makeBackingLayer?如果是这样,请显示该方法以及您尝试配置层的任何其他位置。 @KenThomases 仅将 NSView 用于使用 Metal Rendering 是一个限制。是的,我正在使用makeBackingLayer 来生成图层。我将使用相关代码 sn-ps 更新问题。 @KenThomases 更新了相关细节。 NSView 的哪些其他方法您覆盖?你在上面设置了哪些其他属性?例如,您是否覆盖 -wantsUpdateLayer 以返回 true? @KenThomases 我尝试覆盖 -wantsUpdateLayer 以返回 true,然后覆盖 UpdateLayer。但是,显然这不会被调用。在命令缓冲区上调用commit 之后,我还尝试将layerContentsRedrawPolicy 更改为NSViewLayerContentsRedrawOnSetNeedsDisplay 并调用[nsView setNeedsDisplay:YES],但这似乎也不会触发绘图。 【参考方案1】:

由于这里没有太多我可以调试的东西来找到引入错误的位置,所以我开始按照指令执行的顺序进行操作。事实证明,您调用的顺序:

[self setWantsLayer:YES];
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;

导致了这个问题。在交换这两个语句时,我开始收到对我的displayLayer 函数的调用。我仍然无法弄清楚为什么 drawRect 没有被调用用于支持层的 NSView。

发布这个以防万一这种交换也解决了其他人的问题。

【讨论】:

请注意,如果您的CALayerDelegate 类同时实现了-displayLayer:-drawLayer:inContext:,那么只会调用前者。

以上是关于使用 NSView 进行图层支持的绘图时没有调用 drawRect的主要内容,如果未能解决你的问题,请参考以下文章

OS X/Cocoa:在透明窗口内的 NSView 上绘图

在NSView子类中没有调用cancelOperation

Layer-Backed NSControl 仍然调用 NSCell 绘图例程

使用层支持的 NSView 作为 NSDockTile contentView

使 NSBezierPath 保持相对于 windowResize 上的 NSView

Python 在类实例调用时崩溃