0

我有一个金属着色器,它处理 iPad Pro 视频帧以在颜色附件中生成(未显示的)RGBA32Float 图像。然后将该纹理通过 MPSImageIntegral 过滤器,编码到与着色器相同的命令缓冲区中,从而产生相同大小和格式的输出图像。在命令缓冲区的完成处理程序中,我使用以下代码读出过滤图像中的最后一个像素(包含输入图像中所有像素的总和):

let src = malloc(16)    // 4 Floats per pixel * 4 bytes/Float
let region = MTLRegionMake2D(imageWidth - 1, imageHeight - 1, 1, 1) // last pixel in image
outputImage!.getBytes(src!, bytesPerRow: imageWidth * 16, from: region, mipmapLevel: 0)
let sum = src!.bindMemory(to: Float.self, capacity: 4)
NSLog("sum = \(sum[0]), \(sum[1]), \(sum[2]), \(sum[3])")

只要保存输入和过滤图像的纹理都与 iPad 的显示器尺寸相同,即 2048 x 2732,它就可以正常工作,尽管对于如此大的图像来说速度很慢。

为了加快速度,我让着色器只生成一个 ¼ 大小 (512 x 683) RGBA32Float 图像,并使用相同的大小和格式作为过滤器的输出。但在那种情况下,我读出的总和总是零。

通过在调试器中捕获 GPU 帧,我可以看到依赖关系图在两种情况下看起来是一样的(除了后一种情况下减小的纹理大小),并且着色器和过滤器在两种情况下都按预期工作,基于输入和过滤纹理的外观,如调试器中所示。 那么为什么我不能再成功地读出过滤后的数据,而唯一的变化是减小过滤器的输入和输出图像的大小呢?

有些事情我已经尝试过,但无济于事:

  1. 使用 512 x 512(和其他尺寸)图像,以避免 512 x 683 图像中可能出现的填充伪影。
  2. 查看输出图像中间附近的其他像素,根据 GPU 快照,这些像素也包含非零数据,但在使用较小的图像时读取为 0。
  3. 在同一命令缓冲区中使用 MTLBlitCommandEncoder 将输出像素复制到 MTLBuffer 中,而不是使用 getBytes,或者除了使用 getBytes 之外。(这是这个 MacOS question的答案所建议的,它并不直接适用于 iOS。)
4

1 回答 1

0

我发现,如果我将渲染通道描述符的 storeAction 用于接收初始 RGBA32Float 输入图像的着色器颜色附件,从 .dontCare 更改为 .store,则该代码适用于 512 x 683 图像和 2048 x 2732 图像。

为什么它对我仍然不知道的较大图像起作用。

我也不知道为什么这个存储操作很重要,因为过滤后的输出图像已经成功生成,即使它的输入没有被存储。

于 2020-01-10T08:21:20.957 回答