【问题标题】:Resizing MTKView scales old content before redraw调整 MTKView 的大小会在重绘之前缩放旧内容
【发布时间】:2017-07-28 14:05:48
【问题描述】:

我正在使用 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)有帮助吗?
  • 不,我也尝试了其他一些选项,但没有区别。
  • 你是如何配置MTKView的?例如,pausedenableSetNeedsDisplayautoResizeDrawable 属性的设置是什么?
  • 我编辑了问题以包含视图的设置代码。
  • 其他尝试:将 presentsWithTransaction 设置为 true。如果这还不够,您可能需要遵循docs 最后一段中针对该属性的建议。问题是金属绘图是异步的。您提交一个命令缓冲区。它实际上是在一段时间后安排的。如果你使用它的present(_:),那么此时它会调用drawable的present()方法。即便如此,也延迟了。它将等到对其纹理的所有渲染完成(不仅仅是计划的)。

标签: macos core-animation metal


【解决方案1】:

在使用 Metal 和 MTKView 时,我尝试了 presentsWithTransactionwaitUntilScheduled 的各种组合,但均未成功。在实时调整大小期间,我仍然偶尔会在正确渲染的内容帧之间遇到拉伸内容的帧。

最后,我完全放弃了MTKView,并创建了我自己的使用CAMetalLayer 并调整大小的NSView 子类现在看起来不错(不使用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 方法中完成所有金属绘图。

【讨论】:

  • 不幸的是,这对我没有用/改善。我用delegate来画,可能有关系吧。
  • @RemcoPoelstra 嘿,我完全改变了我的答案。我在之前的回答中看到setFrameSize 出现了一些问题,所以我尝试了一些完全不同的方法。
  • 这个答案为我减少了故障的频率,但并没有消除它们。但是我发现将它与presentsWithTransactionwaitUntilScheduled 结合起来效果很好。我写了一篇博文并发布了一个工作代码示例:thume.ca/2019/06/19/glitchless-metal-window-resizing
  • 我一直遇到我似乎无法解决的位置和缩放问题,事实证明,将 layerContentsPlacement 设置为 TopLeft 对我来说正是难题的最后一块。感谢您提供了一个比较体面的例子。
【解决方案2】:

我在调整视图大小时遇到​​了同样的问题。您甚至可以在 Apple 开发人员网站的 HelloTriangle 示例中重现它。然而,效果被最小化,因为三角形是在屏幕中间附近绘制的,并且它是最靠近窗口边缘的内容,与拖动的角相对,效果最差。关于使用presentsWithTransactionwaitUntilScheduled 的开发人员说明也不适用于我。

我的解决方案是在window.contentView.layer 下方添加一个金属层,并使该层足够大,以至于很少需要调整大小。这样做的原因是,与 window.contentView.layer 不同,它会根据视图自动调整自身大小(进而保持窗口大小),您可以显式控制子层大小。这消除了闪烁。

【讨论】:

  • 我一直在考虑你的建议,但我无法让它发挥作用。您如何暂时阻止 MTKView 调整大小?或者如何在父级重绘后将其更新为正确的大小?
  • 实际上,我确定的解决方案比我在这里建议的解决方案要简单。我只是选择了一个大的层大小,超出了视图的典型大小。当视图被调整大小时,它只是显示更多的层,但从不尝试调整它的大小。您需要将金属层作为视图层的子层。
  • 这是否意味着您使用普通视图而不是 MTKView?当你画“太多”时,性能是否可以接受?
  • 我不关心 MTKView 类,只是创建一个图层并在情况发生变化时绘制。我不知道你说的画太多是什么意思。
  • 当然,没问题。
【解决方案3】:

这对我有帮助 - https://github.com/trishume/MetalTest

他使用MetalLayer并仔细设置各种属性。即使在具有 45 兆像素图像的同步滚动视图中两个并排显示,一切都非常流畅。

我原来的问题的链接How do I position an image correctly in MTKView?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 2012-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多