Fast 24-bit array - > 32-bit array conversion?
Szybkie Podsumowanie:
Mam tablicę 24-bitowych wartości. Jakieś sugestie, jak szybko rozszerzyć poszczególne 24-bitowe elementy tablicy na elementy 32-bitowe?
Szczegóły:
Przetwarzam przychodzące klatki wideo w czasie rzeczywistym za pomocą shaderów pikseli w DirectX 10. Przeszkodą jest to, że moje klatki pochodzą ze sprzętu przechwytywania z 24-bitowymi pikselami( albo jako obrazy YUV lub RGB), ale DX10 ma tekstury 32-bitowych pikseli. Więc, Ja muszę rozszerzyć wartości 24-bitowe do 32-bitów, zanim będę mógł załadować je do GPU.
Naprawdę nie obchodzi mnie, na co ustawiłem pozostałe 8 bitów, lub gdzie przychodzące 24-bitowe są w tej wartości 32-bitowej - mogę to wszystko naprawić w pixel shader. Ale muszę zrobić konwersję z 24-bitowego na 32-bitowy naprawdę szybko.
Nie jestem zbyt dobrze obeznany z operacjami SIMD SSE, ale z mojego pobieżnego spojrzenia nie wygląda na to, że mogę zrobić rozszerzenie za ich pomocą, biorąc pod uwagę moje odczyty i zapisy nie są tego samego rozmiaru. Jakieś sugestie? A może utknąłem sekwencyjnie masując ten zestaw danych?
Wydaje się to bardzo głupie - używam shaderów pikseli do równoległości, ale wcześniej muszę wykonać sekwencyjną operację na piksel. Muszę przegapić coś oczywistego...
4 answers
Poniższy kod powinien być dość szybki. Kopiuje 4 piksele w każdej iteracji, używając tylko 32-bitowych instrukcji odczytu/zapisu. Wskaźniki źródłowe i docelowe powinny być wyrównane do 32 bitów.
uint32_t *src = ...;
uint32_t *dst = ...;
for (int i=0; i<num_pixels; i+=4) {
uint32_t sa = src[0];
uint32_t sb = src[1];
uint32_t sc = src[2];
dst[i+0] = sa;
dst[i+1] = (sa>>24) | (sb<<8);
dst[i+2] = (sb>>16) | (sc<<16);
dst[i+3] = sc>>8;
src += 3;
}
Edit:
Oto sposób, aby to zrobić, używając instrukcji SSSE3 PSHUFB i PALIGNR. Kod jest napisany przy użyciu kompilatora intrinsics, ale nie powinno być trudno przetłumaczyć go na assembly w razie potrzeby. Kopiuje 16 pikseli w każdej iteracji. Wskaźniki źródła i miejsca przeznaczenia musi być wyrównane do 16 bajtów, inaczej będzie to błąd. Jeśli nie są wyrównane, możesz sprawić, że zadziała, zamieniając _mm_load_si128
na _mm_loadu_si128
i _mm_store_si128
na _mm_storeu_si128
, ale będzie to wolniejsze.
#include <emmintrin.h>
#include <tmmintrin.h>
__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<num_pixels; i+=16) {
__m128i sa = _mm_load_si128(src);
__m128i sb = _mm_load_si128(src+1);
__m128i sc = _mm_load_si128(src+2);
__m128i val = _mm_shuffle_epi8(sa, mask);
_mm_store_si128(dst, val);
val = _mm_shuffle_epi8(_mm_alignr_epi8(sb, sa, 12), mask);
_mm_store_si128(dst+1, val);
val = _mm_shuffle_epi8(_mm_alignr_epi8(sc, sb, 8), mask);
_mm_store_si128(dst+2, val);
val = _mm_shuffle_epi8(_mm_alignr_epi8(sc, sc, 4), mask);
_mm_store_si128(dst+3, val);
src += 3;
dst += 4;
}
SSSE3 (nie mylić z SSE3) będzie wymagał stosunkowo nowego procesora: Core 2 lub nowszego, i wierzę, że AMD nie obsługuje go jeszcze. Wykonanie tego z instrukcjami SSE2 zajmie dużo więcej operacji i może nie być tego warte.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-04 21:02:22
SSE3 jest zajebisty, ale dla tych, którzy nie mogą go używać z jakiegokolwiek powodu, oto konwersja w asemblerze x86, ręcznie zoptymalizowana przez was. Dla kompletności podaję konwersję w obu kierunkach: RGB32->RGB24 i RGB24 - > rgb32.
Zauważ, że kod C interjaya pozostawia kosz w MSB (kanale Alfa) pikseli docelowych. Może to nie ma znaczenia w niektórych aplikacjach, ale ma znaczenie w moich, stąd mój kod RGB24->rgb32 zmusza MSB do zera. Podobnie mój kod RGB32 - > RGB24 ignoruje MSB; pozwala to uniknąć śmieci, jeśli dane źródłowe mają niezerowy kanał alfa. Funkcje te nie kosztują prawie nic pod względem wydajności, co potwierdzają benchmarki.
Dla RGB32 - > RGB24 udało mi się pokonać VC++ optimizer o około 20%. Dla RGB24->rgb32 zysk był nieznaczny. Benchmarking został wykonany na i5 2500K. pominąłem kod benchmarking tutaj, ale jeśli ktoś chce to podam. Najważniejszą optymalizacją było wybranie wskaźnika źródłowego, gdy tylko możliwe (zobacz komentarz ASAP). Domyślam się, że Zwiększa to równoległość, pozwalając rurociągowi instrukcji prefetch wcześniej. Poza tym po prostu zmieniłem kolejność niektórych instrukcji, aby zmniejszyć zależności i nakładać dostęp do pamięci za pomocą bit-bashingu.
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
}
A skoro już o tym mowa, oto te same konwersje w rzeczywistym zestawie SSE3. Działa to tylko wtedy, gdy masz asembler (FASM jest darmowy) i masz procesor obsługujący SSE3 (prawdopodobnie, ale lepiej sprawdzić). Zauważ, że intrinsics niekoniecznie generują coś tak wydajnego, to całkowicie zależy od narzędzi, których używasz i na jakiej platformie kompilujesz. Tutaj jest to proste: to, co widzisz, jest tym, co dostajesz. Ten kod generuje to samo wyjście co powyższy kod x86 i jest o 1.5 x szybszy (na 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-05 09:51:06
Różne rozmiary wejść/wyjść nie są przeszkodą w korzystaniu z simd, tylko ograniczeniem prędkości. Trzeba by skrawać dane tak, aby odczytywać i zapisywać pełne słowa simd (16 bajtów).
W tym przypadku należy odczytać 3 słowa SIMD( 48 bajtów = = 16 pikseli rgb), wykonać rozszerzenie, a następnie zapisać 4 słowa SIMD.
Mówię tylko, że ty możesz używać SIMD, nie mówię, że ty powinieneś . Środkowy bit, ekspansja, jest nadal trudne, ponieważ masz nierównomierne rozmiary przesunięć w różne części słowa.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-04 13:06:13
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-20 02:47:28