闪烁的金属输出纹理...为啥?

Posted

技术标签:

【中文标题】闪烁的金属输出纹理...为啥?【英文标题】:Flickering Metal output texture... Why?闪烁的金属输出纹理...为什么? 【发布时间】:2018-03-08 07:18:31 【问题描述】:

我正在尝试在现有纹理的顶部放置多个叠加层(纹理)。在大多数情况下,这可以正常工作。

但是,就我的一生而言,我无法弄清楚为什么在我的 drawRectMTKView 方法中这个输出偶尔会“闪烁”。一切似乎都很好;在循环放置叠加层后,我对theTexture(在内核着色器中)进行进一步处理。出于某种原因,我觉得这种编码提前结束了,而且还没有完成足够的工作。

澄清一下,一切开始正常,但大约 5 秒后,闪烁开始并逐渐恶化。出于调试目的(现在,无论如何),循环只运行一次——只有一个覆盖元素。每次开始之前,输入纹理 (theTexture) 都是真实的(使用 storageMode 为 MTLStorageModeManaged 且使用为 MTLTextureUsageUnknown 的描述符创建)。

我还尝试将编码器实例化/结束填充到循环中;没有区别。

谁能帮我看看我做错了什么?

id<MTLTexture> theTexture; // valid input texture as "background"

MTLRenderPassDescriptor *myRenderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor];
myRenderPassDesc.colorAttachments[0].texture = theTexture;
myRenderPassDesc.colorAttachments[0].storeAction = MTLStoreActionStore;
myRenderPassDesc.colorAttachments[0].loadAction = MTLLoadActionLoad;

id<MTLRenderCommandEncoder> myEncoder = [commandBuffer renderCommandEncoderWithDescriptor:myRenderPassDesc];
MTLViewport viewPort = 0.0, 0.0, 1920.0, 1080.0, -1.0, 1.0;
vector_uint2 imgSize = vector2((u_int32_t)1920,(u_int32_t)1080);
[myEncoder setViewport:viewPort];
[myEncoder setRenderPipelineState:metalVertexPipelineState];

for (OverlayWrapper *ow in overlays) 
    id<MTLTexture> overlayTexture = ow.overlayTexture;

    VertexRenderSet *v = [ow getOverlayVertexInfoPtr];
    NSUInteger vSize = v->metalVertexCount*sizeof(AAPLVertex);
    id<MTLBuffer> mBuff = [self.device newBufferWithBytes:v->metalVertices
                                                   length:vSize
                                                  options:MTLResourceStorageModeShared];

    [myEncoder setVertexBuffer:mBuff offset:0 atIndex:0];
    [myEncoder setVertexBytes:&imgSize length:sizeof(imgSize) atIndex:1];
    [myEncoder setFragmentTexture:overlayTexture atIndex:0];
    [myEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:v->metalVertexCount];


[myEncoder endEncoding];

// do more work (kernel shader) with "theTexture"...


更新 #1: 我附上了一个“好”框架的图像,显​​示了顶点区域(右下角)。我的编码器负责将绿色替代“图像”以 30fps 的速度放置在视频帧 theTexture 的顶部,它确实这样做了。澄清一下,theTexture 是为每一帧创建的(来自 CoreVideo 像素缓冲区)。在这个编码器之后,我只在内核着色器中读取theTexture 来调整亮度——一切都很好。

我的问题一定存在于其他地方,因为视频帧停止流动(尽管音频继续播放),并且一旦插入此编码器(因此,闪烁),我最终会在 2 或 3 个之前的帧之间交替。我现在相信我的视频像素缓冲区供应商无意中被这个“覆盖”供应商所取代。

如果我注释掉整个顶点渲染器,我的视频帧就可以顺利通过;这不是我的视频帧供应商的问题。

更新 #2:

这是我的渲染管道的声明:

MTLRenderPipelineDescriptor *p = [[MTLRenderPipelineDescriptor alloc] init];
if (!p)
    return nil;
p.label = @"Vertex Mapping Pipeline";
p.vertexFunction   = [metalLibrary newFunctionWithName:@"vertexShader"];
p.fragmentFunction = [metalLibrary newFunctionWithName:@"samplingShader"];
p.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;

NSError *error;
metalVertexPipelineState = [self.device newRenderPipelineStateWithDescriptor:p
                                                                       error:&error];
if (error || !metalVertexPipelineState)
    return nil;

这是用于创建theTexture的纹理描述符:

metalTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm 
                                                                            width:width
                                                                           height:height
                                                                        mipmapped:NO];
metalTextureDescriptor.storageMode = MTLStorageModePrivate;
metalTextureDescriptor.usage = MTLTextureUsageUnknown;

我没有包含 AAPLVertex 和顶点/片段函数,因为:如果我只是在我的渲染代码中注释掉 OverlayWrapper 循环(即,甚至不设置顶点缓冲区或绘制图元) ,视频帧仍然闪烁。视频仍在播放,但只有 2-3 帧左右在连续循环播放,从该编码器“运行”开始。

我还在[... endEncoding] 之后添加了这段代码,并将纹理用法更改为MTLStorageModeManaged——仍然,没有骰子:

id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
[blitEncoder synchronizeResource:crossfadeOutput];
[blitEncoder endEncoding];

澄清几点:后续计算机着色器使用theTexture 仅作为输入。这些是视频帧;因此,theTexture 每次都会重新创建。在它通过这个渲染阶段之前,它有一个真正的“背景”


更新 #3:

如果通过非常规的方式,我得到了这个工作。

我使用此顶点着色器将我的叠加层渲染到新创建的空白纹理的透明背景上,特别是我的 loadActionMTLLoadActionClear,其 clearColor 为 (0,0,0,0)。

然后我将这个生成的纹理与我的theTexture 与内核着色器混合。我应该必须这样做,但它有效!

【问题讨论】:

或许添加好帧和坏帧的截图。管道状态是如何设置的(即创建它的描述符是什么)?显示顶点和片段函数。显示AAPLVertex 的声明。计算着色器只是使用theTexture 作为输入还是修改它?为什么这与显示无关?从您写的内容中不清楚:您是否为每一帧重新创建theTexture?你每次都以某种方式填充或清除它?还是我弄错了,您从一帧到下一帧重复使用它?如果是后者,那么所有的绘图都会累积。 Ken,我添加了一些更新来澄清一些事情。希望这些回答您的问题。 我不知道你是如何从视频帧创建theTexture,但也许this 是相关的? 我正在做同样的事情——使用带有 AVPlayer 输出的 CVMetalTextureCacheCreateTextureFromImage 来获取 CVMetalTexture,然后使用 CVMetalTextureGetTexture 来获取 MTLTexture。在我介绍这个新的顶点编码器之前似乎工作正常。在渲染循环结束之前,我确实持有指向 CVPixelBuffer 的强指针。 引入带有theTexture作为颜色附件的渲染编码器会强烈引用theTexture,它至少会持续到命令缓冲区被处理超过该编码器在缓冲区中的位置,可能直到缓冲区已完成并且本身已被释放。也许你有太多的在飞,你正在耗尽像素缓冲区/纹理池。 【参考方案1】:

我遇到了同样的问题,想在尝试@zzyzy 之前探索一个更简单的解决方案。这个解决方案也有些不尽人意,但至少看起来有效。

关键(但其本身不足)是减少金属层上的缓冲:

metalLayer_.maximumDrawableCount = 2

其次,一旦缓冲减少,我发现我必须通过渲染/呈现/提交周期来绘制一个琐碎的、不可见的项目,并在渲染通道描述符上设置.clear — 非常简单:

renderPassDescriptor.colorAttachments[0].loadAction = .clear

(绘制了一些不可见的三角形可能无关紧要;可能是MTLLoadActionClear 属性区分了pass。我使用了与上面@zzyzy 相同的清晰颜色,我认为这与上述解决方案相呼应。)

第三,我发现我必须在渲染/呈现/提交周期中运行代码第二次——即连续两次。在这三个中,这似乎是最随意的,我不假装理解它,但三个一起为我工作。

【讨论】:

将命令缓冲区的最大数量减少到 1(而不是说 3)是我的解决方法。在上一个循环完成之前,我正在读取和写入的纹理似乎在下一个循环中被覆盖,导致闪烁。我同意我不满意的解决方案,但就我而言,我不需要高性能,所以它对我有用。另一种方法是不存储纹理(即每次都重新创建)-尽管这听起来也很昂贵。

以上是关于闪烁的金属输出纹理...为啥?的主要内容,如果未能解决你的问题,请参考以下文章

未找到金属“纹理”

金属着色器纹理读取与示例

如何在金属中使用一系列纹理

如何在 SceneKit 中显示金属和透明纹理?

查找金属纹理中的最小值和最大值

使用 GLUT 和 C 闪烁 2D 纹理