调整 MTKView 的大小会在重绘之前对旧内容进行缩放。

10
我正在使用MTKView来绘制Metal内容。它的配置如下:
mtkView = MTKView(frame: self.view.frame, device: device)
mtkView.colorPixelFormat = .bgra8Unorm
mtkView.delegate = self
mtkView.sampleCount = 4
mtkView.isPaused = true
mtkView.enableSetNeedsDisplay = true

setFrameSize 被重写以触发重新显示。

每当视图调整大小时,它会在重新绘制所有内容之前缩放其旧内容。这会给人一种抖动的感觉。

我尝试将 MTKView 的层的 contentGravity 属性设置为非调整大小的值,但这完全搞乱了内容的比例和位置。似乎 MTKView 不希望我去调整该参数。

如何确保在调整大小期间始终正确地重新绘制内容?


layerContentsRedrawPolicy 设置为 NSViewLayerContentsRedrawDuringViewResize (在 Swift 中为 .duringViewResize) 有帮助吗? - Ken Thomases
不,我也尝试了其他几个选项,但没有任何区别。 - Remco Poelstra
你如何配置 MTKView?例如,pausedenableSetNeedsDisplayautoResizeDrawable 属性的设置是什么? - Ken Thomases
嗯,我不确定如何解释这些结果。很奇怪。是的,waitUntilScheduled() 调用会有性能损失。你只需要在调整大小期间执行它。在那种情况下,理论上它会减慢窗口/视图响应调整大小的速度,直到你能够绘制帧,这基本上是你问题中隐含的要求。 - Ken Thomases
好的,它适用于更一般的情况,这是一个巨大的胜利。感谢您的支持!如果您有时间编写答案,我很乐意将其归功于您的接受答案。 - Remco Poelstra
显示剩余8条评论
3个回答

8

在我使用 Metal 和 MTKView 的过程中,我尝试了各种不同的 presentsWithTransactionwaitUntilScheduled 的组合,但是没有成功。在实时调整大小期间,我仍然会经历偶尔出现拉伸内容帧的情况。

最终,我完全放弃了 MTKView,并创建了自己的 NSView 子类,使用了 CAMetalLayer ,现在调整大小看起来很好(没有使用任何 presentsWithTransactionwaitUntilScheduled)。其中一个关键点是我需要设置层的 autoresizingMask 以便在窗口调整大小时每一帧都调用 displayLayer 方法。

这是头文件:

#import <Cocoa/Cocoa.h>
    
@interface MyMTLView : NSView<CALayerDelegate>    
@end

这是实现方式:

#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

以供参考,我在 MTKView 的 drawRect 方法中执行所有的 Metal 绘图。


不幸的是,这对我没有起作用/改进。我使用委托来绘制,也许这有关系。 - Remco Poelstra
1
@RemcoPoelstra 嘿,我完全改变了我的答案。我在之前的回答中看到了一些setFrameSize的挂起情况,所以我尝试了完全不同的东西。 - Max
3
这个答案对我减少了故障的频率,但并没有完全消除它们。然而,我发现将其与“presentsWithTransaction”和“waitUntilScheduled”结合使用可以完美地解决问题。我写了一篇博客文章,并发布了一个可工作的代码示例:http://thume.ca/2019/06/19/glitchless-metal-window-resizing/。 - Tristan Hume
我一直遇到位置和缩放问题,但似乎无法解决。结果发现将layerContentsPlacement设置为TopLeft正是我需要的最后一块拼图。感谢您提供了一个还算不错的示例供我参考。 - Tim Kane

1
我遇到了与视图调整大小时出现故障的问题。你甚至可以在苹果开发者网站的HelloTriangle示例中重现这个问题。然而,由于三角形绘制在屏幕中央附近,并且是靠近窗口边缘相反的角落拖动的内容最受影响,因此效果被最小化了。关于使用“presentsWithTransaction”和“waitUntilScheduled”的开发人员注意事项对我也没有用。

我的解决方案是在“window.contentView.layer”下添加一个Metal层,并使该层足够大,以便很少需要调整大小。之所以这样做有效,是因为与“window.contentView.layer”不同的是,它会自动调整大小以适应视图(进而维护窗口大小),你可以明确地控制子层大小。这消除了闪烁。


我一直在尝试你的建议,但是我无法让它工作。你如何暂时防止MTKView调整大小?或者当父视图重新绘制后,你如何将其更新到正确的大小? - Remco Poelstra
实际上,我选择的解决方案比我在这里提出的更简单。我只是选择了一个宽裕的层大小,超出了视图的典型大小。当视图被调整大小时,它只会显示更多的层,但永远不会尝试重新调整大小。您需要将金属层设置为视图层的子层。 - Jonathan Zrake
这是否意味着您使用普通视图而不是MTKView?在绘制“过多”时性能是否可接受? - Remco Poelstra
我不会使用MTKView类,只需创建一个图层并在更改时进行绘制。我不知道你所说的“绘制太多”是什么意思。 - Jonathan Zrake
当然,没问题。 - Jonathan Zrake

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接