metal - 闪烁的 Metal 输出纹理...为什么?

标签 metal metalkit

我正在尝试在现有纹理之上放置多个覆盖层(纹理)。在大多数情况下,这工作得很好。

但是,在我的一生中,我无法弄清楚为什么在我的 MTKViewdrawRect 方法中,它的输出偶尔会“闪烁”。一切看起来都很好;在循环放置叠加层后,我对 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 个先前帧之间交替(因此,闪烁)。我现在相信我的视频像素缓冲区供应商正在无意中被这个“覆盖”供应商取代。

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

A sample "good" frame of video[1]

更新#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 混合。我不应该不必这样做,但它确实有效!

最佳答案

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

关键(但其本身并不充分)是减少 Metal 层上的缓冲:

metalLayer_.maximumDrawableCount = 2

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

renderPassDescriptor.colorAttachments[0].loadAction = .clear

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

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

关于metal - 闪烁的 Metal 输出纹理...为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46437079/

相关文章:

ios - 从设备运行而不是从构建和运行运行时应用程序崩溃

objective-c - 是否可以通过 [NSString drawAtPoint] 绘制文本或在 Metal View 中显示 NSButton?

ios - 我们是否需要为每次绘制创建一个新的 RenderPassDescriptor,或者我们可以缓存它并重用它?

ios - 用 "const constant"声明变量的目的是什么?

ios - MTLBlitCommandEncode 是否执行线性采样

ios - Metal 着色器中的 GLSL gl_FragDepth 等效项

swift - SceneKit:SCNRenderingAPI

metal - 在 Metal Debugger 中捕获精确的帧

ios - 如何在 iOS 中使用 Metal API 中的计算函数进行乘法

ios - 在 Metal 计算着色器中使用MPSImageConvolution内核