我正在使用c ++,我想使用以下代码进行alpha混合.
#define CLAMPTOBYTE(color) \
if ((color) & (~255)) { \
color = (BYTE)((-(color)) >> 31); \
} else { \
color = (BYTE)(color); \
}
#define GET_BYTE(accessPixel, x, y, scanline, bpp) \
((BYTE*)((accessPixel) + (y) * (scanline) + (x) * (bpp)))
for (int y = top ; y < bottom; ++y)
{
BYTE* resultByte = GET_BYTE(resultBits, left, y, stride, bytepp);
BYTE* srcByte = GET_BYTE(srcBits, left, y, stride, bytepp);
BYTE* srcByteTop = GET_BYTE(srcBitsTop, left, y, stride, bytepp);
BYTE* maskCurrent = GET_GREY(maskSrc, left, y, width);
int alpha = 0;
int red = 0;
int green = 0;
int blue = 0;
for (int x = left; x < right; ++x)
{
alpha = *maskCurrent;
red = (srcByteTop[R] * alpha + srcByte[R] * (255 - alpha)) / 255;
green = (srcByteTop[G] * alpha + srcByte[G] * (255 - alpha)) / 255;
blue = (srcByteTop[B] * alpha + srcByte[B] * (255 - alpha)) / 255;
CLAMPTOBYTE(red);
CLAMPTOBYTE(green);
CLAMPTOBYTE(blue);
resultByte[R] = red;
resultByte[G] = green;
resultByte[B] = blue;
srcByte += bytepp;
srcByteTop += bytepp;
resultByte += bytepp;
++maskCurrent;
}
}
Run Code Online (Sandbox Code Playgroud)
然而,我发现它仍然很慢,当编写两个600*600图像时需要大约40-60毫秒.有没有什么方法可以将速度提高到不到16毫秒?
任何人都可以帮我加速这段代码吗?非常感谢!
Tom*_*eys 26
使用SSE - 从第131页开始.
基本工作流程
从src加载4个像素(16个1字节数)RGBA RGBA RGBA RGBA(流加载)
加载另外4个要与srcbytetop RGBx RGBx RGBx RGBx混合的区域
做一些调整,以便1中的A项填充每个插槽即
xxxA xxxB xxxC xxxD - > AAAA BBBB CCCC DDDD
在我的解决方案下面我选择,而不是重新使用现有的"maskcurrent"阵列,但具有α-集成到1中的"A"字段将需要更少的存储器量,因而更快.在这种情况下混合可能是:并使用掩码选择A,B,C,D.右移8,或原始,右移16,或再次.
将上面的内容添加到每个插槽中全部为-255的向量中
乘以1*4(具有255-alpha的源)和2*3(具有alpha的结果).
您应该可以使用"乘法和丢弃底部8位"SSE2指令.
将这两个(4和5)加在一起
将它们存放在其他地方(如果可能)或存放在目的地之上(如果必须)
以下是您的出发点:
//Define your image with __declspec(align(16)) i.e char __declspec(align(16)) image[640*480]
// so the first byte is aligned correctly for SIMD.
// Stride must be a multiple of 16.
for (int y = top ; y < bottom; ++y)
{
BYTE* resultByte = GET_BYTE(resultBits, left, y, stride, bytepp);
BYTE* srcByte = GET_BYTE(srcBits, left, y, stride, bytepp);
BYTE* srcByteTop = GET_BYTE(srcBitsTop, left, y, stride, bytepp);
BYTE* maskCurrent = GET_GREY(maskSrc, left, y, width);
for (int x = left; x < right; x += 4)
{
//If you can't align, use _mm_loadu_si128()
// Step 1
__mm128i src = _mm_load_si128(reinterpret_cast<__mm128i*>(srcByte))
// Step 2
__mm128i srcTop = _mm_load_si128(reinterpret_cast<__mm128i*>(srcByteTop))
// Step 3
// Fill the 4 positions for the first pixel with maskCurrent[0], etc
// Could do better with shifts and so on, but this is clear
__mm128i mask = _mm_set_epi8(maskCurrent[0],maskCurrent[0],maskCurrent[0],maskCurrent[0],
maskCurrent[1],maskCurrent[1],maskCurrent[1],maskCurrent[1],
maskCurrent[2],maskCurrent[2],maskCurrent[2],maskCurrent[2],
maskCurrent[3],maskCurrent[3],maskCurrent[3],maskCurrent[3],
)
// step 4
__mm128i maskInv = _mm_subs_epu8(_mm_set1_epu8(255), mask)
//Todo : Multiply, with saturate - find correct instructions for 4..6
//note you can use Multiply and add _mm_madd_epi16
alpha = *maskCurrent;
red = (srcByteTop[R] * alpha + srcByte[R] * (255 - alpha)) / 255;
green = (srcByteTop[G] * alpha + srcByte[G] * (255 - alpha)) / 255;
blue = (srcByteTop[B] * alpha + srcByte[B] * (255 - alpha)) / 255;
CLAMPTOBYTE(red);
CLAMPTOBYTE(green);
CLAMPTOBYTE(blue);
resultByte[R] = red;
resultByte[G] = green;
resultByte[B] = blue;
//----
// Step 7 - store result.
//Store aligned if output is aligned on 16 byte boundrary
_mm_store_si128(reinterpret_cast<__mm128i*>(resultByte), result)
//Slow version if you can't guarantee alignment
//_mm_storeu_si128(reinterpret_cast<__mm128i*>(resultByte), result)
//Move pointers forward 4 places
srcByte += bytepp * 4;
srcByteTop += bytepp * 4;
resultByte += bytepp * 4;
maskCurrent += 4;
}
}
Run Code Online (Sandbox Code Playgroud)
要找出哪些AMD处理器将运行此代码(目前它正在使用SSE2指令),请参阅Wikipedia的AMD Turion微处理器列表.您还可以查看维基百科上的其他处理器列表,但我的研究表明,大约4年前的AMD cpus至少都支持SSE2.
您应该期望SSE2的良好运行速度比当前代码快8-16倍.这是因为我们消除了循环中的分支,一次处理4个像素(或12个通道),并通过使用流指令提高缓存性能.作为SSE的替代方案,您可以通过消除用于饱和的if检查来更快地运行现有代码.除此之外,我需要在您的工作负载上运行一个分析器.
当然,最好的解决方案是使用硬件支持(即在DirectX中编写问题代码)并在视频卡上完成.
Jas*_*ers 21
您始终可以同时计算红色和蓝色的alpha值.您也可以将此技巧与前面提到的SIMD实现一起使用.
unsigned int blendPreMulAlpha(unsigned int colora, unsigned int colorb, unsigned int alpha)
{
unsigned int rb = (colora & 0xFF00FF) + ( (alpha * (colorb & 0xFF00FF)) >> 8 );
unsigned int g = (colora & 0x00FF00) + ( (alpha * (colorb & 0x00FF00)) >> 8 );
return (rb & 0xFF00FF) + (g & 0x00FF00);
}
unsigned int blendAlpha(unsigned int colora, unsigned int colorb, unsigned int alpha)
{
unsigned int rb1 = ((0x100 - alpha) * (colora & 0xFF00FF)) >> 8;
unsigned int rb2 = (alpha * (colorb & 0xFF00FF)) >> 8;
unsigned int g1 = ((0x100 - alpha) * (colora & 0x00FF00)) >> 8;
unsigned int g2 = (alpha * (colorb & 0x00FF00)) >> 8;
return ((rb1 | rb2) & 0xFF00FF) + ((g1 | g2) & 0x00FF00);
}
Run Code Online (Sandbox Code Playgroud)
0 <= alpha <= 0x100
Gui*_*zan 17
对于想要除以255的人,我发现了一个完美的公式:
pt->r = (r+1 + (r >> 8)) >> 8; // fast way to divide by 255
Run Code Online (Sandbox Code Playgroud)
这里有一些指示.
考虑使用Porter和Duff描述的预乘的前景图像.除了可能更快,您还可以避免许多潜在的色边效果.
合成方程由...改变
r = kA + (1-k)B
Run Code Online (Sandbox Code Playgroud)
... 至 ...
r = A + (1-k)B
Run Code Online (Sandbox Code Playgroud)
或者,您可以重新设计标准公式以删除一个乘法.
r = kA + (1-k)B
== kA + B - kB
== k(A-B) + B
Run Code Online (Sandbox Code Playgroud)
我可能错了,但我认为你不应该需要夹紧......
我无法发表评论,因为我没有足够的声誉,但我想说 Jasper 的版本不会因为有效输入而溢出。屏蔽乘法结果是必要的,因为否则红色+蓝色乘法会在绿色通道中留下位(如果您分别将红色和蓝色相乘,这也是正确的,您仍然需要屏蔽掉蓝色通道中的位)和绿色乘法将在蓝色通道中留下位。如果将组件分离出来,这些位会因右移而丢失,这在 alpha 混合中经常出现。所以它们不会上溢或下溢。它们只是需要屏蔽以达到预期结果的无用位。
也就是说,Jasper 的版本是不正确的。它应该是 0xFF-alpha (255-alpha),而不是 0x100-alpha (256-alpha)。这可能不会产生可见的错误。会产生可见错误的是他对 | 的使用。而不是 + 合并乘法结果时。
我发现对 Jasper 代码的改编比我旧的 alpha 混合代码更快,这已经很不错了,并且目前正在我的软件渲染器项目中使用它。我使用 32 位 ARGB 像素:
Pixel AlphaBlendPixels(Pixel p1, Pixel p2)
{
static const int AMASK = 0xFF000000;
static const int RBMASK = 0x00FF00FF;
static const int GMASK = 0x0000FF00;
static const int AGMASK = AMASK | GMASK;
static const int ONEALPHA = 0x01000000;
unsigned int a = (p2 & AMASK) >> 24;
unsigned int na = 255 - a;
unsigned int rb = ((na * (p1 & RBMASK)) + (a * (p2 & RBMASK))) >> 8;
unsigned int ag = (na * ((p1 & AGMASK) >> 8)) + (a * (ONEALPHA | ((p2 & GMASK) >> 8)));
return ((rb & RBMASK) | (ag & AGMASK));
}
Run Code Online (Sandbox Code Playgroud)