我正在尝试非常有效地将压缩的 24bpp RGB 图像转换为压缩的 32bpp RGBA。我尝试使用 Accelerate.framework
中的 vImageConvert_RGB888toRGBA8888
,但想知道在 Metal 中使用计算内核是否有更快的方法。我在 Metal 中尝试了几种不同的方法,但结果总是比使用 Accelerate.framework
慢得多,即使对于具有 >1M 像素的大图像也是如此。
这是我的计算内核的样子:
kernel void rgb24_to_rgba32(texture2d<half, access::read> inTexture [[texture(0)]],
texture2d<half, access::write> outTexture [[texture(1)]],
uint2 id [[ thread_position_in_grid ]])
{
uint2 srcAddr1 = uint2(id.x * 3, id.y);
uint2 srcAddr2 = uint2(id.x * 3 + 1, id.y);
uint2 srcAddr3 = uint2(id.x * 3 + 2, id.y);
outTexture.write(half4(inTexture.read(srcAddr1).r, inTexture.read(srcAddr2).r, inTexture.read(srcAddr3).r, 1), id);
return;
}
我将 inTexture
定义为 r8Unorm
,将 outTexture 定义为 bgra8Unorm
。两种纹理都是使用 .storageModeShared
加载的,因此不应该发生任何内存复制。
代码工作正常,转换正确执行,但性能不高。我尝试了不同的 threadgroupsPerGrid
和 threadsPerThreadgroup
设置,但没有一个能达到与 Accelerate.framework
相当的性能。
例如,在 A7(第一代 iPad Air)上,一张 1024x1024 的图像大约需要 32 毫秒,而使用 Accelerate.framework
则需要 6 毫秒。有趣的是,对于更快的设备,例如基于 A9 的 iPhone 6s,差异要小得多(在 GPU 上为 1.5 毫秒,而使用 Accelerate
为 1.1 毫秒),但 Metal 实现总是更慢。
这只是对 GPU 不友好的操作吗(可能是由于无数未对齐的内存访问?)在最大化我的计算内核的性能方面,我可能遗漏了一些基本的东西吗?
更新:使用以下实现,我最终能够获得比上述更好的性能:
这种方法利用 packed_uint3
的 96 位读取和使用 packed_uint4
的 128 位写入来显着提高性能。
#define RGB24_TO_RGBA32_PIXEL1(myUint) (myUint | 0xff000000)
#define RGB24_TO_RGBA32_PIXEL2(myUint1, myUint2) (myUint1 >> 24 | \
((myUint2) << 8) | 0xff000000)
#define RGB24_TO_RGBA32_PIXEL3(myUint2, myUint3) (myUint2 >> 16 | \
((myUint3) << 16) | 0xff000000)
#define RGB24_TO_RGBA32_PIXEL4(myUint3) ((myUint3 >> 8) | 0xff000000)
inline packed_uint4 packed_rgb24_to_packed_rgba32(packed_uint3 src) {
return uint4(RGB24_TO_RGBA32_PIXEL1(src[0]),
RGB24_TO_RGBA32_PIXEL2(src[0], src[1]),
RGB24_TO_RGBA32_PIXEL3(src[1], src[2]),
RGB24_TO_RGBA32_PIXEL4(src[2]));
}
kernel void rgb24_to_rgba32_textures(
constant packed_uint3 *src [[ buffer(0) ]],
device packed_uint4 *dest [[ buffer(1) ]],
uint2 id [[ thread_position_in_grid ]])
{
// Process 8 pixels per thread (two packed_uint3s, each containing 4 pixels):
uint index = id.x * 2;
dest[index] = packed_rgb24_to_packed_rgba32(src[index]);
dest[index + 1] = packed_rgb24_to_packed_rgba32(src[index + 1]);
return;
}
使用这种方法,旧设备上的性能差异会变得更小(Accelerate 大约比 GPU 快 2 倍),而在更现代 (A9) 设备上,Metal 实际上最终会快 40-50% 。
我尝试过为每个线程处理一个、两个或多个 packed_uint3
向量,结论是两个向量是性能的最佳点。
为了结束,这里是 Apple 的开发者关系对这个问题的回应。 最重要的是,GPU 在这种情况下并没有提供任何真正的优势,因为这种转换不是计算量很大的操作。
After discussions with engineering, and evaluating more sample implementations, the verdict is out on Metal v.s. Accelerate performance for converting packed 24bpp RGB images to packed 32bpp RGBA images: on newer devices you can get close to the same performance using Metal but Accelerate will be faster for this operation. “vImage is an extremely well-tuned implementation and since this conversion operation is not compute heavy the best we can do is to be at parity.”
The proposed reasoning behind this is data locality and efficiently operating on multiple pixels at a time (something you’ve mentioned). The fastest Metal implementation tested processed two pixels per thread and still lagged behind
vImageConvert_RGB888toRGBA8888
.There was an “optimized” implementation using Metal buffers rather than textures (something else that you’d mentioned exploring) and surprisingly this approach was slightly less performant.
Lastly, adjustment of thread groups came into discussion as well as tuning by adding code to the kernel to handle the case where the thread position in grid is outside the destination image. Again, despite these considerations Accelerate remained as the fastest implementation.
我应该补充一点,使用 Metal 的一个真正优势是 CPU 使用率,虽然它没有更快,但它确实显着减少了 CPU 的工作量。对于 CPU 负载很重的应用程序,Metal 方法实际上可能有意义。
关于ios - 使用 GPU 进行像素格式转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39781719/
欢迎光临 OGeek|极客世界-中国程序员成长平台 (https://ogeek.cn/) | Powered by Discuz! X3.4 |