Comment charger une structure de pixels dans un registre SSE?

J’ai une structure de données de pixels de 8 bits:

struct __atsortingbute__((aligned(4))) pixels { char r; char g; char b; char a; } 

Je veux utiliser les instructions SSE pour calculer certaines choses sur ces pixels (à savoir une transformation de Paeth). Comment puis-je charger ces pixels dans un registre SSE sous forme d’entiers non signés 32 bits?

Déballer les pixels non signés avec SSE2

Ok, en utilisant les insortingnsèques entiers SSE2 de chargez d’abord la chose dans les 32 bits inférieurs du registre:

 __m128i xmm0 = _mm_cvtsi32_si128(*(const int*)&pixel); 

Ensuite, décompressez d’abord ces valeurs de 8 bits en valeurs de 16 bits dans les 64 bits inférieurs du registre, en les entrelacant avec des 0:

 xmm0 = _mm_unpacklo_epi8(xmm0, _mm_setzero_si128()); 

Et encore une fois décompressez ces valeurs 16 bits en valeurs 32 bits:

 xmm0 = _mm_unpacklo_epi16(xmm0, _mm_setzero_si128()); 

Vous devez maintenant avoir chaque pixel sous forme d’entier sur 32 bits dans les 4 composantes respectives du registre SSE.


Déballer les pixels signés avec SSE2

Je viens de lire que vous voulez obtenir ces valeurs sous forme d’entiers signés 32 bits, bien que je me demande quel sens a un pixel signé dans [-127,127]. Mais si vos valeurs de pixels peuvent effectivement être négatives, l’entrelacement avec des zéros ne fonctionnera pas, car il convertit un nombre négatif à 8 bits en un nombre positif à 16 bits (interprète donc vos nombres comme des valeurs de pixels non signées). Un nombre négatif doit être étendu avec 1 s au lieu de 0 s, mais malheureusement, cela devrait être décidé dynamicment composant par composant, pour lequel SSE n’est pas très bon.

Ce que vous pouvez faire est de comparer les valeurs de négativité et d’utiliser le masque obtenu (qui utilise heureusement 1...1 pour vrai et 0...0 pour faux) comme interleavand, au lieu du registre zéro:

 xmm0 = _mm_unpacklo_epi8(xmm0, _mm_cmplt_epi8(xmm0, _mm_setzero_si128())); xmm0 = _mm_unpacklo_epi16(xmm0, _mm_cmplt_epi16(xmm0, _mm_setzero_si128())); 

Cela étendra correctement les nombres négatifs avec 1 s et les positifs avec 0 s. Mais bien sûr, cette surcharge supplémentaire (sous la forme de probablement 2 à 4 instructions SSE supplémentaires) n’est nécessaire que si vos valeurs initiales de pixels à 8 bits peuvent être négatives, ce dont je doute encore. Mais si c’est vraiment le cas, vous devriez plutôt considérer signed char sur char , car ce dernier a une signature définie par l’implémentation (de la même manière, vous devez utiliser unsigned char si ce sont les valeurs de pixel communes non signées [0,255]).


Déballage SSE2 alternatif à l’aide de décalages

Bien que, comme cela a été précisé, vous n’avez pas besoin de la conversion signature-8 bits en 32 bits, mais par souci d’exhaustivité, harold avait une autre très bonne idée pour l’extension de signe basée sur SSE2, au lieu d’utiliser la comparaison mentionnée ci-dessus. version. Nous décompressons d’abord les valeurs de 8 bits dans l’octet supérieur des valeurs de 32 bits au lieu de l’octet inférieur. Puisque nous ne nous soucions pas des parties inférieures, nous utilisons simplement à nouveau les valeurs 8 bits, ce qui nous évite d’avoir à recourir à un registre zéro supplémentaire et à un déplacement supplémentaire:

 xmm0 = _mm_unpacklo_epi8(xmm0, xmm0); xmm0 = _mm_unpacklo_epi16(xmm0, xmm0); 

Il ne rest plus qu’à effectuer un décalage arithmétique à droite de l’octet supérieur vers l’octet inférieur, ce qui permet l’extension de signe appropriée pour les valeurs négatives:

 xmm0 = _mm_srai_epi32(xmm0, 24); 

Cela devrait être plus d’instruction et enregistrer efficace que mon ci-dessus SSE2-version.

Et comme il devrait même être égal en nombre d’instructions pour un seul pixel (bien qu’une instruction supplémentaire si amorti sur plusieurs pixels) et plus efficace en termes de registre (en l’absence de registre zéro supplémentaire) par rapport à l’extension zéro ci-dessus, il pourrait même être utilisé pour la conversion non signée à signée si les registres sont rares, mais avec un décalage logique ( _mm_srli_epi32 ) au lieu d’un décalage arithmétique.


Déballage amélioré avec SSE4

Grâce au commentaire de harold , il existe même une meilleure option pour la première transformation de 8 à 32. Si vous avez le support SSE4 (SSE4.1 pour être précis), qui contient des instructions pour effectuer la conversion complète de 4 valeurs compressées à 8 bits dans les 32 bits inférieurs du registre en 4 valeurs à 32 bits dans tout le registre, à la fois pour valeurs 8 bits signées et non signées:

 xmm0 = _mm_cvtepu8_epi32(xmm0); //or _mm_cvtepi8_epi32 for signed 8-bit values 

Emballage de pixels avec SSE2

Pour ce qui est du suivi de l’inversion de cette transformation, nous commençons par compresser les entiers 32 bits signés en entiers signés 16 bits et en saturant:

 xmm0 = _mm_packs_epi32(xmm0, xmm0); 

Ensuite, nous compressons ces valeurs 16 bits en valeurs non signées 8 bits en utilisant la saturation:

 xmm0 = _mm_packus_epi16(xmm0, xmm0); 

Nous pouvons ensuite prendre notre pixel des 32 bits inférieurs du registre:

 *(int*)&pixel = _mm_cvtsi128_si32(xmm0); 

En raison de la saturation, l’ensemble du processus mappera automatiquement toutes les valeurs négatives à 0 et toutes les valeurs supérieures à 255 à 255 , ce qui est généralement prévu pour les pixels de couleur.

Si vous avez réellement besoin de la troncature au lieu de la saturation lors de la compression des valeurs 32 bits dans des caractères unsigned char , vous devrez le faire vous-même, car SSE fournit uniquement des instructions de compression saturantes. Mais ceci peut être réalisé en faisant un simple:

 xmm0 = _mm_and_si128(xmm0, _mm_set1_epi32(0xFF)); 

juste avant la procédure d’emballage ci-dessus. Cela ne devrait représenter que 2 instructions SSE supplémentaires, ou 1 seule instruction supplémentaire après amortissement sur plusieurs pixels.