我有一个金属着色器,它处理 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 帧,我可以看到依赖关系图在两种情况下看起来是一样的(除了后一种情况下减小的纹理大小),并且着色器和过滤器在两种情况下都按预期工作,基于输入和过滤纹理的外观,如调试器中所示。 那么为什么我不能再成功地读出过滤后的数据,而唯一的变化是减小过滤器的输入和输出图像的大小呢?
有些事情我已经尝试过,但无济于事:
- 使用 512 x 512(和其他尺寸)图像,以避免 512 x 683 图像中可能出现的填充伪影。
- 查看输出图像中间附近的其他像素,根据 GPU 快照,这些像素也包含非零数据,但在使用较小的图像时读取为 0。
- 在同一命令缓冲区中使用 MTLBlitCommandEncoder 将输出像素复制到 MTLBuffer 中,而不是使用 getBytes,或者除了使用 getBytes 之外。(这是这个 MacOS question的答案所建议的,它并不直接适用于 iOS。)