快速24位arrays – > 32位arrays转换?

快速摘要:

我有一个24位值的数组。 关于如何快速将各个24位数组元素扩展为32位元素的任何建议?

细节:

我正在使用DirectX 10中的Pixel Shaders实时处理传入的video帧。一个绊脚石是我的帧从捕获硬件进入24位像素(作为YUV或RGB图像),但DX10需要32位像素纹理。 因此,在将其加载到GPU之前,我必须将24位值扩展为32位。

我真的不在乎我将剩余的8位设置为什么,或者输入的24位是否在32位值中 – 我可以在像素着色器中修复所有这些。 但我需要非常快速地将24位转换为32位。

我对SIMD SSE操作并不十分熟悉,但从我粗略的一瞥来看,看起来我不能使用它们进行扩展,因为我的读写操作大小不一样。 有什么建议? 还是我按顺序按摩这个数据集?

这感觉非常愚蠢 – 我使用像素着色器进行并行处理,但在此之前我必须执行顺序逐像素操作。 我一定错过了一些明显的东西……

下面的代码应该非常快。 它仅使用32位读/写指令在每次迭代中复制4个像素。 源和目标指针应对齐32位。

uint32_t *src = ...; uint32_t *dst = ...; for (int i=0; i>24) | (sb<<8); dst[i+2] = (sb>>16) | (sc<<16); dst[i+3] = sc>>8; src += 3; } 

编辑:

这是使用SSSE3指令PSHUFB和PALIGNR执行此操作的方法。 代码是使用编译器内在函数编写的,但如果需要,转换为汇编应该不难。 它在每次迭代中复制16个像素。 源和目标指针必须对齐到16个字节,否则它将出错。 如果它们没有对齐,你可以通过用_mm_store_si128替换_mm_loadu_si128_mm_store_si128替换_mm_load_si128来实现它,但这会慢一些。

 #include  #include  __m128i *src = ...; __m128i *dst = ...; __m128i mask = _mm_setr_epi8(0,1,2,-1, 3,4,5,-1, 6,7,8,-1, 9,10,11,-1); for (int i=0; i 

SSSE3(不要与SSE3混淆)将需要一个相对较新的处理器:Core 2或更新,我相信AMD还不支持它。 仅使用SSE2指令执行此操作将需要更多操作,并且可能不值得。

SSE3很棒,但对于那些因任何原因无法使用它的人来说,这里是x86汇编程序中的转换,由您真正手动优化。 为了完整起见,我在两个方向上进行转换:RGB32-> RGB24和RGB24-> RGB32。

请注意,interjay的C代码会在目标像素的MSB(Alpha通道)中留下垃圾。 这在某些应用中可能无关紧要,但在我看来很重要,因此我的RGB24-> RGB32代码强制MSB为零。 同样,我的RGB32-> RGB24代码忽略了MSB; 如果源数据具有非零alpha通道,则可以避免垃圾输出。 正如基准所证实的那样,这些function在性能方面几乎没有任何成本。

对于RGB32-> RGB24,我能够击败VC ++优化器约20%。 对于RGB24-> RGB32,增益无关紧要。 基准测试是在i5 2500K上完成的。 我在这里省略了基准测试代码,但如果有人想要它,我会提供它。 最重要的优化是尽快碰撞源指针(参见ASAP评论)。 我最好的猜测是,通过允许指令管道更快地预取,这增加了并行性。 除此之外,我只重新排序了一些指令,以减少依赖关系,并通过bit-bashing重叠内存访问。

 void ConvRGB32ToRGB24(const UINT *Src, UINT *Dst, UINT Pixels) { #if !USE_ASM for (UINT i = 0; i < Pixels; i += 4) { UINT sa = Src[i + 0] & 0xffffff; UINT sb = Src[i + 1] & 0xffffff; UINT sc = Src[i + 2] & 0xffffff; UINT sd = Src[i + 3]; Dst[0] = sa | (sb << 24); Dst[1] = (sb >> 8) | (sc << 16); Dst[2] = (sc >> 16) | (sd << 8); Dst += 3; } #else __asm { mov ecx, Pixels shr ecx, 2 // 4 pixels at once jz ConvRGB32ToRGB24_$2 mov esi, Src mov edi, Dst ConvRGB32ToRGB24_$1: mov ebx, [esi + 4] // sb and ebx, 0ffffffh // sb & 0xffffff mov eax, [esi + 0] // sa and eax, 0ffffffh // sa & 0xffffff mov edx, ebx // copy sb shl ebx, 24 // sb << 24 or eax, ebx // sa | (sb << 24) mov [edi + 0], eax // Dst[0] shr edx, 8 // sb >> 8 mov eax, [esi + 8] // sc and eax, 0ffffffh // sc & 0xffffff mov ebx, eax // copy sc shl eax, 16 // sc << 16 or eax, edx // (sb >> 8) | (sc << 16) mov [edi + 4], eax // Dst[1] shr ebx, 16 // sc >> 16 mov eax, [esi + 12] // sd add esi, 16 // Src += 4 (ASAP) shl eax, 8 // sd << 8 or eax, ebx // (sc >> 16) | (sd << 8) mov [edi + 8], eax // Dst[2] add edi, 12 // Dst += 3 dec ecx jnz SHORT ConvRGB32ToRGB24_$1 ConvRGB32ToRGB24_$2: } #endif } void ConvRGB24ToRGB32(const UINT *Src, UINT *Dst, UINT Pixels) { #if !USE_ASM for (UINT i = 0; i < Pixels; i += 4) { UINT sa = Src[0]; UINT sb = Src[1]; UINT sc = Src[2]; Dst[i + 0] = sa & 0xffffff; Dst[i + 1] = ((sa >> 24) | (sb << 8)) & 0xffffff; Dst[i + 2] = ((sb >> 16) | (sc << 16)) & 0xffffff; Dst[i + 3] = sc >> 8; Src += 3; } #else __asm { mov ecx, Pixels shr ecx, 2 // 4 pixels at once jz SHORT ConvRGB24ToRGB32_$2 mov esi, Src mov edi, Dst push ebp ConvRGB24ToRGB32_$1: mov ebx, [esi + 4] // sb mov edx, ebx // copy sb mov eax, [esi + 0] // sa mov ebp, eax // copy sa and ebx, 0ffffh // sb & 0xffff shl ebx, 8 // (sb & 0xffff) << 8 and eax, 0ffffffh // sa & 0xffffff mov [edi + 0], eax // Dst[0] shr ebp, 24 // sa >> 24 or ebx, ebp // (sa >> 24) | ((sb & 0xffff) << 8) mov [edi + 4], ebx // Dst[1] shr edx, 16 // sb >> 16 mov eax, [esi + 8] // sc add esi, 12 // Src += 12 (ASAP) mov ebx, eax // copy sc and eax, 0ffh // sc & 0xff shl eax, 16 // (sc & 0xff) << 16 or eax, edx // (sb >> 16) | ((sc & 0xff) << 16) mov [edi + 8], eax // Dst[2] shr ebx, 8 // sc >> 8 mov [edi + 12], ebx // Dst[3] add edi, 16 // Dst += 16 dec ecx jnz SHORT ConvRGB24ToRGB32_$1 pop ebp ConvRGB24ToRGB32_$2: } #endif } 

虽然我们在这里,但实际的SSE3组装中的转换是相同的。 这只适用于你有一个汇编程序(FASM是免费的)并且有一个支持SSE3的CPU(可能但最好检查一下)。 请注意,内在函数不一定输出这么高效的东西,它完全取决于您使用的工具以及您正在编译的平台。 在这里,它很简单:你看到的是你得到的。 此代码生成与上面的x86代码相同的输出,并且速度提高约1.5倍(在i5 2500K上)。

 format MS COFF section '.text' code readable executable public _ConvRGB32ToRGB24SSE3 ; ebp + 8 Src (*RGB32, 16-byte aligned) ; ebp + 12 Dst (*RGB24, 16-byte aligned) ; ebp + 16 Pixels _ConvRGB32ToRGB24SSE3: push ebp mov ebp, esp mov eax, [ebp + 8] mov edx, [ebp + 12] mov ecx, [ebp + 16] shr ecx, 4 jz done1 movupd xmm7, [mask1] top1: movupd xmm0, [eax + 0] ; sa = Src[0] pshufb xmm0, xmm7 ; sa = _mm_shuffle_epi8(sa, mask) movupd xmm1, [eax + 16] ; sb = Src[1] pshufb xmm1, xmm7 ; sb = _mm_shuffle_epi8(sb, mask) movupd xmm2, xmm1 ; sb1 = sb pslldq xmm1, 12 ; sb = _mm_slli_si128(sb, 12) por xmm0, xmm1 ; sa = _mm_or_si128(sa, sb) movupd [edx + 0], xmm0 ; Dst[0] = sa psrldq xmm2, 4 ; sb1 = _mm_srli_si128(sb1, 4) movupd xmm0, [eax + 32] ; sc = Src[2] pshufb xmm0, xmm7 ; sc = _mm_shuffle_epi8(sc, mask) movupd xmm1, xmm0 ; sc1 = sc pslldq xmm0, 8 ; sc = _mm_slli_si128(sc, 8) por xmm0, xmm2 ; sc = _mm_or_si128(sb1, sc) movupd [edx + 16], xmm0 ; Dst[1] = sc psrldq xmm1, 8 ; sc1 = _mm_srli_si128(sc1, 8) movupd xmm0, [eax + 48] ; sd = Src[3] pshufb xmm0, xmm7 ; sd = _mm_shuffle_epi8(sd, mask) pslldq xmm0, 4 ; sd = _mm_slli_si128(sd, 4) por xmm0, xmm1 ; sd = _mm_or_si128(sc1, sd) movupd [edx + 32], xmm0 ; Dst[2] = sd add eax, 64 add edx, 48 dec ecx jnz top1 done1: pop ebp ret public _ConvRGB24ToRGB32SSE3 ; ebp + 8 Src (*RGB24, 16-byte aligned) ; ebp + 12 Dst (*RGB32, 16-byte aligned) ; ebp + 16 Pixels _ConvRGB24ToRGB32SSE3: push ebp mov ebp, esp mov eax, [ebp + 8] mov edx, [ebp + 12] mov ecx, [ebp + 16] shr ecx, 4 jz done2 movupd xmm7, [mask2] top2: movupd xmm0, [eax + 0] ; sa = Src[0] movupd xmm1, [eax + 16] ; sb = Src[1] movupd xmm2, [eax + 32] ; sc = Src[2] movupd xmm3, xmm0 ; sa1 = sa pshufb xmm0, xmm7 ; sa = _mm_shuffle_epi8(sa, mask) movupd [edx], xmm0 ; Dst[0] = sa movupd xmm4, xmm1 ; sb1 = sb palignr xmm1, xmm3, 12 ; sb = _mm_alignr_epi8(sb, sa1, 12) pshufb xmm1, xmm7 ; sb = _mm_shuffle_epi8(sb, mask); movupd [edx + 16], xmm1 ; Dst[1] = sb movupd xmm3, xmm2 ; sc1 = sc palignr xmm2, xmm4, 8 ; sc = _mm_alignr_epi8(sc, sb1, 8) pshufb xmm2, xmm7 ; sc = _mm_shuffle_epi8(sc, mask) movupd [edx + 32], xmm2 ; Dst[2] = sc palignr xmm3, xmm3, 4 ; sc1 = _mm_alignr_epi8(sc1, sc1, 4) pshufb xmm3, xmm7 ; sc1 = _mm_shuffle_epi8(sc1, mask) movupd [edx + 48], xmm3 ; Dst[3] = sc1 add eax, 48 add edx, 64 dec ecx jnz top2 done2: pop ebp ret section '.data' data readable writeable align 16 label mask1 dqword db 0,1,2,4, 5,6,8,9, 10,12,13,14, -1,-1,-1,-1 label mask2 dqword db 0,1,2,-1, 3,4,5,-1, 6,7,8,-1, 9,10,11,-1 

不同的输入/输出尺寸不是使用simd的障碍,只是减速带。 您需要对数据进行分块,以便以完整的simd字(16字节)进行读写。

在这种情况下,您将读取3个SIMD字(48个字节== 16个rgb像素),进行扩展,然后写入4个SIMD字。

我只是说你可以使用SIMD,我不是说你应该 。 中间位,即扩展,仍然很棘手,因为在单词的不同部分有不均匀的移位大小。

SSE 4.1 .ASM:

 PINSRD XMM0, DWORD PTR[ESI], 0 PINSRD XMM0, DWORD PTR[ESI+3], 1 PINSRD XMM0, DWORD PTR[ESI+6], 2 PINSRD XMM0, DWORD PTR[ESI+9], 3 PSLLD XMM0, 8 PSRLD XMM0, 8 MOVNTDQ [EDI], XMM1 add ESI, 12 add EDI, 16