我一直在使用this演示文稿中的示例(幻灯片41)。
据我所知,它执行alpha混合。
MOVQ mm0, alpha//4 16-b zero-padding α
MOVD mm1, A //move 4 pixels of image A
MOVD mm2, B //move 4 pixels of image B
PXOR mm3 mm3 //clear mm3 to all zeroes
//unpack 4 pixels to 4 words
PUNPCKLBW mm1, mm3 // Because B -A could be
PUNPCKLBW mm2, mm3 // negative, need 16 bits
PSUBW mm1, mm2 //(B-A)
PMULHW mm1, mm0 //(B-A)*fade/256
PADDW mm1, mm2 //(B-A)*fade + B
//pack four words back to four bytes
PACKUSWB mm1, mm3我想用汇编程序用c重写它。
现在,我有这样的东西:
void fade_mmx(SDL_Surface* im1,SDL_Surface* im2,Uint8 alpha, SDL_Surface* imOut)
{
int pixelsCount = imOut->w * im1->h;
Uint32 *A = (Uint32*) im1->pixels;
Uint32 *B = (Uint32*) im2->pixels;
Uint32 *out = (Uint32*) imOut->pixels;
Uint32 *end = out + pixelsCount;
__asm__ __volatile__ (
"\n\t movd (%0), %%mm0"
"\n\t movd (%1), %%mm1"
"\n\t movd (%2), %%mm2"
"\n\t pxor %%mm3, %%mm3"
"\n\t punpcklbw %%mm3, %%mm1"
"\n\t punpcklbw %%mm3, %%mm2"
"\n\t psubw %%mm2, %%mm1"
"\n\t pmulhw %%mm0, %%mm1"
"\n\t paddw %%mm2, %%mm1"
"\n\t packuswb %%mm3, %%mm1"
: : "r" (alpha), "r" (A), "r" (B), "r" (out), "r" (end)
);
__asm__("emms" : : );
}在编译时,我收到这样的消息:关于汇编程序中的第一行的Error: (%dl) is not a valid base/index expression。我怀疑这是因为alpha是Uint8,我试着转换它,但是我得到了一个分段错误。在这个例子中,他们谈论的是4 16-b zero-padding α,这对我来说并不是很清楚。
发布于 2020-12-29 09:05:59
您的问题是您试图使用alpha值作为地址,而不是值。movd (%0), %%mm0指令指定使用%0作为内存中的一个位置。所以你的意思是加载alpha指向的值,而不是它的值。使用movd %0, %%mm0可以解决这个问题,但是您会遇到这样的问题:您的MOVD值只有8位类型,而且它必须是32位类型才能与alpha指令一起使用。您可以解决这个问题,因为MOVQ值需要乘以256,然后广播到目标寄存器的所有4个16位字,算法才能正常工作,方法是将其与0x0100010001000100ULL相乘,然后使用alpha指令。
但是,您根本不需要MOVD/MOVQ指令。通过使用如下代码指定y约束,您可以让编译器自己将值加载到MMX寄存器中:
typedef unsigned pixel;
static inline pixel
fade_pixel_mmx_asm(pixel p1, pixel p2, unsigned fade) {
asm("punpcklbw %[zeros], %[p1]\n\t"
"punpcklbw %[zeros], %[p2]\n\t"
"psubw %[p2], %[p1]\n\t"
"pmulhw %[fade], %[p1]\n\t"
"paddw %[p2], %[p1]\n\t"
"packuswb %[zeros], %[p1]"
: [p1] "+&y" (p1), [p2] "+&y" (p2)
: [fade] "y" (fade * 0x0100010001000100ULL), [zeros] "y" (0));
return p1;
}您会注意到,这里没有必要使用粗略列表,因为没有使用编译器未分配的寄存器,也没有编译器需要知道的其他副作用。我省略了必要的EMMS指令,因为您不希望在每个像素上执行。您需要在混合两个曲面的循环之后插入一条asm("emms");语句。
更好的是,你根本不需要使用内联汇编。您可以改用内部函数,而不必担心使用内联汇编的所有陷阱:
#include <mmintrin.h>
static inline pixel
fade_pixel_mmx_intrin(pixel p1, pixel p2, unsigned fade) {
__m64 zeros = (__m64) 0ULL;
__m#64 mfade = (__m64) (fade * 0x0100010001000100ULL);
__m64 mp1 = _m_punpcklbw((__m64) (unsigned long long) p1, zeros);
__m64 mp2 = _m_punpcklbw((__m64) (unsigned long long) p2, zeros);
__m64 ret;
ret = _m_psubw(mp1, mp2);
ret = _m_pmulhw(ret, mfade);
ret = _m_paddw(ret, mp2);
ret = _m_packuswb(ret, zeros);
return (unsigned long long) ret;
}与前面的示例类似,您需要在循环之后调用_m_empty()来生成必要的EMMS指令。
你也应该认真考虑仅仅用普通的C编写例程,Autovectorizers现在已经相当不错了,而且很可能编译器可以使用现代的SIMD指令生成比你试图使用古老的MMX指令更好的代码。例如,下面的代码:
static inline unsigned
fade_component(unsigned c1, unsigned c2, unsigned fade) {
return c2 + (((int) c1 - (int) c2) * fade) / 256;
}
void
fade_blend(pixel *dest, pixel *src1, pixel *src2, unsigned char fade,
unsigned len) {
unsigned char *d = (unsigned char *) dest;
unsigned char *s1 = (unsigned char *) src1;
unsigned char *s2 = (unsigned char *) src2;
unsigned i;
for (i = 0; i < len * 4; i++) {
d[i] = fade_component(s1[i], s2[i], fade);
}
}对于GCC 10.2和-O3,上面的代码产生了汇编代码,它使用128位XMM寄存器,并在其内部循环中一次混合4个像素:
movdqu xmm5, XMMWORD PTR [rdx+rax]
movdqu xmm1, XMMWORD PTR [rsi+rax]
movdqa xmm6, xmm5
movdqa xmm0, xmm1
punpckhbw xmm1, xmm3
punpcklbw xmm6, xmm3
punpcklbw xmm0, xmm3
psubw xmm0, xmm6
movdqa xmm6, xmm5
punpckhbw xmm6, xmm3
pmullw xmm0, xmm2
psubw xmm1, xmm6
pmullw xmm1, xmm2
psrlw xmm0, 8
pand xmm0, xmm4
psrlw xmm1, 8
pand xmm1, xmm4
packuswb xmm0, xmm1
paddb xmm0, xmm5
movups XMMWORD PTR [rdi+rax], xmm0最后,即使是C代码的非向量化版本也可能是近乎最佳的,因为代码足够简单,无论blend是如何实现的,您都可能会受到内存的限制。
发布于 2020-12-28 05:59:47
在复制到MM注册表之前,您可以使用带0x0001000100010001ULL的标量乘法将alpha广播到64位。另一种选择是,对于movd,只需将8位整数扩展为32位,然后使用pshufw进行复制。
您的asm也存在各种安全问题。
#include <SDL/SDL.h>
#include <stdint.h>
void fade_mmx(SDL_Surface* im1,SDL_Surface* im2,Uint8 alpha, SDL_Surface* imOut)
{
int pixelsCount = imOut->w * im1->h;
Uint32 *A = (Uint32*) im1->pixels;
Uint32 *B = (Uint32*) im2->pixels;
Uint32 *out = (Uint32*) imOut->pixels;
Uint32 *end = out + pixelsCount;
Uint64 alphas = (Uint64)alpha * 0x0001000100010001ULL;
__asm__ __volatile__ (
"\n\t movd %0, %%mm0"
"\n\t movd %1, %%mm1"
"\n\t movd %2, %%mm2"
"\n\t pxor %%mm3, %%mm3"
"\n\t punpcklbw %%mm3, %%mm1"
"\n\t punpcklbw %%mm3, %%mm2"
"\n\t psubw %%mm2, %%mm1"
"\n\t pmulhw %%mm0, %%mm1"
"\n\t paddw %%mm2, %%mm1"
"\n\t packuswb %%mm3, %%mm1"
: // you're probably going to want an "=m"(*something) memory output here
: "r" (alphas), "m" (*A), "m" (*B), "r" (out), "r" (end)
: "mm0", "mm1", "mm2", "mm3");
__asm__("emms" : : );
}如果编译器知道所有的输入和输出,而不是依赖于"memory"拦截器,则asm语句不需要为volatile。(就像这里,没有输出,只读取作为输入操作数的寄存器和内存。)
对于32位代码,请用"m"(alphas)替换"r"(alphas)。或者使用"rm"(alphas)让编译器挑选。(但对于32位,最好使用pshufw,而不是让编译器将64位乘法结果存储为2个32位的二分之一,然后在使用movq重新加载它时遇到存储转发停滞。内部函数会将决定权留给使用_mm_set1_epi16(alpha)的编译器,尽管您只能在循环外执行一次)。
请注意,我还添加了必要的乱序列表,并将包含您取消引用的指针的寄存器操作数替换为引用您取消引用的内存的内存操作数,从而允许gcc推断您访问的内存
请注意,如果你不解决这些事情,gcc会不高兴,你的代码的行为将是未定义的,可能会以神秘的和难以调试的方式失败。除非您完全了解自己在做什么,否则不要使用内联程序集。考虑使用内部函数作为一种更安全、更有效的替代方法。(https://gcc.gnu.org/wiki/DontUseInlineAsm)。
使用__m128i向量的SSE2可以轻松地一次处理4个像素,而不是通过用零填充而浪费一半的pack吞吐量。(使用punpckhbw对punpcklbw进行补充,以便为此进行设置)。MMX非常过时,现代CPU对于某些指令的MMX版本的吞吐量低于等效的128位SSE2 XMM指令的吞吐量。
https://stackoverflow.com/questions/65470595
复制相似问题