Я пытаюсь очень эффективно конвертировать упакованные изображения RGB 24bpp в упакованный RGBA 32bpp. Я пробовал использовать vImageConvert_RGB888toRGBA8888
от Accelerate.framework
, но задавался вопросом, был ли более быстрый способ, используя вычислительное ядро в Metal. Я пробовал несколько разных подходов в Metal, но результат всегда значительно медленнее, чем с Accelerate.framework
, даже для больших изображений с> 1M пикселей.Использование GPU для преобразования в пиксельные форматы?
Вот что мое ядро вычисления выглядит следующим образом:
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 первого поколения) изображение 1024x1024 занимает около 32 мс, по сравнению с 6 мс, используя Accelerate.framework
. Интересно, что разница намного меньше для более быстрого устройства, такого как iPhone 6 на базе A9 (1,5 мс на графическом процессоре против 1,1 мс с использованием Accelerate
), но реализация Металла всегда медленнее.
Это просто не совместимая с GPU операция (возможно, из-за бесчисленных нерациональных доступов к памяти?) Могу ли я упустить что-то фундаментальное с точки зрения максимизации производительности моего вычислительного ядра?
UPDATE: Я был в конечном итоге удалось достичь значительно более высокую производительность, чем описанный выше, с использованием следующей реализации:
Этот подход использует 96-битный читает с помощью packed_uint3
, и 128-битный записывает с помощью packed_uint4
, чтобы значительно повысить производительность ,
#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;
}
При таком подходе, дифференциальная производительность на старых устройствах становится гораздо меньше (ускорение примерно 2 раза быстрее, чем GPU), а на более современных устройств (A9), металла на самом деле ветры быть около 40-50 % быстрее.
Я пробовал обрабатывать один, два или более packed_uint3
векторов в потоке, и вывод заключался в том, что два вектора - это сладкое пятно для производительности.
Мы в конечном итоге заставили Metal работать быстрее, чем процессор для этой задачи, но это было непросто. –
@IanOllmann, вы можете поделиться этим кодом здесь? У меня есть конкретное приложение, которое может в значительной степени выиграть от этого. – ldoogy