"intercept" METAL vertex/fragment 着色器结果的最佳方法

Best way to "intercept" result of METAL vertex/fragment shader

我目前有一个 MTLTexture 用于输入,并且正在使用一组 20-30 个顶点分段渲染。这是目前在我的 drawRect 处理程序的尾端完成的 MTKView:

[encoder setVertexBuffer:mBuff offset:0 atIndex:0];  // buffer of vertices
[encoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:1];
[encoder setFragmentTexture:inputTexture atIndex:0];
[encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_vertexInfo.metalVertexCount];
[encoder endEncoding];

[commandBuffer presentDrawable:self.currentDrawable];
[commandBuffer commit];

但是,在执行最后的 presentDrawable 之前,我想拦截生成的纹理(我将把它的一个区域发送到一个单独的 MTKView)。换句话说,我需要在 drawPrimitives 调用后访问某种输出方式 MTLTexture

最有效的方法是什么?

一个想法是向中间输出 MTLTexture 引入额外的 drawPrimitives 渲染。我不确定该怎么做,但我会在此过程中获取输出纹理。我怀疑这甚至会在其他地方完成(即屏幕外)。

然后我将发布第二个 drawPrimitives,使用带有该 outputTexture 的单个巨大纹理四边形,然后在其上添加 presentDrawable。该代码将存在于我之前的代码所在的位置。

Metal API 中可能有一个简单的方法(我没有找到)可以让我捕获 drawPrimitives.

的输出纹理

我研究过使用 MTLBlitCommandEncoder,但在某些 MacOSX 硬件上存在一些问题。


更新#1: idoogy,这是您请求的代码:

这是我创建初始 "brightness output" 纹理的地方...我们正在顶点着色器中进行此操作:

...
[encoder setFragmentTexture:brightnessOutput atIndex:0];
[encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_vertexInfo.metalVertexCount];
[encoder endEncoding];

for (AltMonitorMTKView *v in self.downstreamOutputs). // ancillary MTKViews
    [v setInputTexture:brightnessOutput];

__block dispatch_semaphore_t block_sema = d.hostedAssetsSemaphore;
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
    dispatch_semaphore_signal(block_sema);
}];

[commandBuffer presentDrawable:self.currentDrawable];
[commandBuffer commit];

下面,我们在辅助视图的 drawRect 处理程序中,inputTexture 作为正在传输的纹理,显示它的子区域。我应该提到这个 MTKView 被配置为作为 setNeedsDisplay 的结果绘制,而不是作为带有内部计时器的结果绘制:

id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
encoder.label = @"Vertex Render Encoder";
[encoder setRenderPipelineState:metalVertexPipelineState];

// draw main content
NSUInteger vSize = _vertexInfo.metalVertexCount*sizeof(AAPLVertex);
id<MTLBuffer> mBuff = [self.device newBufferWithBytes:_vertexInfo.metalVertices
                                               length:vSize
                                              options:MTLResourceStorageModeShared];
[encoder setVertexBuffer:mBuff offset:0 atIndex:0];
[encoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:1];
[encoder setFragmentTexture:self.inputTexture atIndex:0];
[encoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_vertexInfo.metalVertexCount];
[encoder endEncoding];

[commandBuffer presentDrawable:self.currentDrawable];
[commandBuffer commit];

上面的代码似乎 工作正常。话虽如此,我认为我们在 Xcode 调试器中讲述了一个不同的故事。很明显,我正在浪费大量时间以这种方式做事...那个长命令缓冲区是辅助监视器视图,需要等待很多...

这应该是可行的。在 commandBuffer 上调用 commit 之前,通过调用 [commandBuffer addCompletedHandler:] 添加命令缓冲区的完成处理程序,然后在完成处理程序中,从 renderPassDescriptor 中获取颜色附件.

renderPassDescriptor 保存当前正在绘制的附件集,并由 MTKView 自动配置。实际纹理每帧旋转,因为 MTKView 使用三重缓冲来确保 GPU 的连续使用,但只要您在完成处理程序中,该特定附件就不会被释放以供将来使用框架,因此您可以安全地从中读取、复制等。

注意: 确保合理快速地完成处理程序,否则帧率会下降(因为 MTKView 会很快 运行 超出渲染目标并将坐在那里直到他们被释放)。

这是一个通用的代码片段,可以帮助您入门:

// Grab the current render pass descriptor from MTKView so it's accessible from within the completion block:
__block MTLRenderPassDescriptor *renderPassDescriptor = self.renderPassDescriptor;

[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer) {
   // This will be called once the GPU has completed rendering your frame.
   // This is your output texture:
   id <MTLTexture> outputTexture = renderPassDescriptor.colorAttachments[0].texture;
}];
[commandBuffer commit];