所以,我一直在尝试运行一些 MPS 内核。基于我之前的问题:MPSImageIntegral 返回全零 我试图在浮点值上运行 MPSImageIntegral 。现在,我继续讨论
uint32_t
值。但事实证明,我总是得到一个断言
/BuildRoot/Library/Caches/com.apple.xbs/Sources/MetalPerformanceShaders/MetalPerformanceShaders-121.4.2/MPSImage/Filters/MPSIntegral.mm:196: 断言失败`目标 0x600003b62760 纹理格式不 匹配源 0x600003b62680 纹理格式'
该断言具有误导性,因为我的纹理类型并非不匹配。
这就是我创建我的
MTLTexture
所做的事情
+ (id<MTLTexture>) createTestTexture: (float)val metalDevice:(id<MTLDevice>)device textureWidth:(int)widthTex
{
std::vector<uint32_t> testData;
for(int i = 0; i < widthTex; i++)
testData.push_back(i);
MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR32Uint
width: widthTex height:1 mipmapped:NO];
[desc setResourceOptions:MTLResourceStorageModeManaged];
[desc setStorageMode:MTLStorageModeManaged];
[desc setUsage:(MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite)];
id<MTLTexture> tex = [device newTextureWithDescriptor:desc];
MTLRegion texDataRegion = MTLRegionMake2D(0, 0, widthTex, 1);
[tex replaceRegion:texDataRegion mipmapLevel:0 withBytes:testData.data() bytesPerRow:1024];
return tex;
}
这是我用来创建输入和输出纹理的函数。 然后我继续像这样运行我的 MPSImageIntegral:
id<MTLTexture> inTex = [ViewController createTestTexture:1.0f metalDevice:_device textureWidth:100];
id<MTLTexture> outTex = [ViewController createTestTexture:1.0f metalDevice:_device textureWidth:100];
id<MTLCommandQueue> _commandQueue = [_device newCommandQueue];
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
// Create a MPS filter.
[integral encodeToCommandBuffer:commandBuffer sourceTexture:inTex destinationTexture:outTex];
基于此处的文档:https://developer.apple.com/documentation/metalperformanceshaders/image_filters?language=objc
MPSImageIntegral
支持MTLPixelFormatR32Uint
,我在这里做错了什么吗?
所以,首先要小心一点。 图像积分在文献中听起来都非常好,但很容易因为精度而失败。对于单精度浮点数,有 24 位精度,包括隐式 1 位、8 位指数和符号位。因此,如果您将超过 65793 个 8 位像素相加,则总和将不再具有足够的精度来包含所有内容并开始舍入。 通常,图像积分用于执行诸如通过从一点处的积分减去另一点处的积分来获得面积平均值之类的事情。如果该区域足够大以至于可以四舍五入,那么这些减法将产生不精确的结果,并且对于足够大的区域,您可能会在输出图像中得到垃圾或至少是噪声。 2^16 像素还不够。这将是一张 256x256 的邮票,在现代设备上并不是很大。
使用 uint32_ts 做类似的事情会让你在模溢出之前获得更多的空间。您将有 2^(32-image_bit_depth) 像素可供使用,而不是 2^(24-image_bit_depth) 浮点数。但是,它可能会限制您使用整数像素表示,因为如果没有特殊的内核,纹理单元不会自动将 fp16 转换为 uint32_t。 所以,如果你正在使用 fp16,我不希望它能工作,除非苹果决定为这种情况编写一个单独的内核。您这里有 uint32_t 样本。
一些可能导致全0的事情:
在离散设备上,您需要同步资源以使用 MTLBlitCommandEncoder 读回数据。尽管您已将其标记为托管资源,但我在这里看不到该代码,所以我的钱花在了这个上。如果你不同步,无论发生什么,你都会得到零。 https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/ResourceOptions.html
虽然在这种情况下可能不是,但您可能没有预先将正确的数据复制到输入纹理中。正如 Mattijs 指出的那样,rowBytes 意味着很多东西,你不能只是乱扔数字。该值通常应为 sizeof(pixel) * width,但如果在下一个扫描线开始之前有填充,或者图像是从另一个图像内的矩形图块绘制的,则可能会更大。如果它来自 CGImage,那么 CGImage 将能够告诉您图像提供程序的 rowBytes 是什么,vImage_Buffers 在结构本身、CVPixelBufferGetBytesPerRow() 等中拥有它。RowBytes 是距开始的字节距离。原点处的像素到第 1 行像素的开头。如果图像是二维数组,则为 (uintptr_t) &image[1][0] - (uintptr_t) &image[0][0]。 显然,如果图像被分配为指向单独分配的一维数组的指针数组,则这不起作用。 (没有人这样做,这太愚蠢了。)因为你的 height=1,所以在这种情况下这应该不重要,但也可以在 MPS 期望的值中输入一个数字。
由于您使用 uint32_t 作为输入和输出缓冲区,因此在第一个像素之后可能会出现模溢出。我个人会考虑使用 uint8_t 输入缓冲区来减少这种迫在眉睫的情况。可能 MPS 职能部门正在考虑这一点,并说这不可能行得通!我会默默地失败。如果确实如此,那么由于普遍无益,这可能是值得 bug 的。
并非一切都一直按 MPS 的方式进行。有时编译会失败。确保您的沙箱/ASL 溢出抑制不会抑制 DEBUG 目标上的 MPS 错误溢出,并且您正在使用适当的 MPS 选项来触发额外的调试溢出,例如MPSKernelOptionsVerbose。如果内核没有运行,可能是因为没有内核,那么您将看到零。 您可以尝试将输出缓冲区初始化为零以外的值,以查看是否写入了任何内容。