使用查找表更快地进行Alpha混合?

时间:2011-03-25 21:28:10

标签: c++ c optimization graphics alphablending

我创建了一个查找表,允许您使用单字节alpha通道混合两个单字节通道(每通道256色),不使用浮点值(因此没有浮点到int转换)。查找表中的每个索引对应于与alpha值相关的信道的256ths的值。

总而言之,要完全计算3通道RGB混合,每个通道需要两次查找数组,另外还要添加一个。这是总共6次查找和3次添加。在下面的示例中,我将颜色分成单独的值以便于演示。此示例显示如何将三个通道R G和B混合为范围为0到256的Alpha值。

BYTE r1, r2, rDest;
BYTE g1, g2, gDest;
BYTE b1, b2, bDest;

BYTE av; // Alpha value
BYTE rem = 255 - av; // Remaining fraction

rDest = _lookup[r1][rem] + _lookup[r2][av];
gDest = _lookup[g1][rem] + _lookup[g2][av];
bDest = _lookup[b1][rem] + _lookup[b2][av];

效果很好。精确,因为你可以使用256色通道。实际上,您可以使用实际的浮点计算得到相同的精确值。查找表是使用双打开始计算的。查找表太大,不适合这篇文章(65536字节)。 (如果你想要一份副本,请发邮件至ten.turtle.toes@gmail.com,但不要期待明天的回复,因为我现在要睡觉了。)

那么......你怎么看?它值得吗?

3 个答案:

答案 0 :(得分:5)

我有兴趣看一些基准。

有一种算法可以在没有任何浮点计算或查找表的情况下进行完美的Alpha混合。您可以在以下document中找到更多信息(最后描述了算法和代码)

如果你有兴趣,我也在很久以前做过SSE的实施......

void PreOver_SSE2(void* dest, const void* source1, const void* source2, size_t size)
{
    static const size_t STRIDE = sizeof(__m128i)*4;
    static const u32 PSD = 64;

    static const __m128i round = _mm_set1_epi16(128);
    static const __m128i lomask = _mm_set1_epi32(0x00FF00FF);

    assert(source1 != NULL && source2 != NULL && dest != NULL);
    assert(size % STRIDE == 0);

    const __m128i* source128_1 = reinterpret_cast<const __m128i*>(source1);
    const __m128i* source128_2 = reinterpret_cast<const __m128i*>(source2);
    __m128i*       dest128 = reinterpret_cast<__m128i*>(dest);  

    __m128i d, s, a, rb, ag, t;

    for(size_t k = 0, length = size/STRIDE; k < length; ++k)    
    {
        // TODO: put prefetch between calculations?(R.N)
        _mm_prefetch(reinterpret_cast<const s8*>(source128_1+PSD), _MM_HINT_NTA);
        _mm_prefetch(reinterpret_cast<const s8*>(source128_2+PSD), _MM_HINT_NTA);   

        // work on entire cacheline before next prefetch
        for(int n = 0; n < 4; ++n, ++dest128, ++source128_1, ++source128_2)
        {
            // TODO: assembly optimization use PSHUFD on moves before calculations, lower latency than MOVDQA (R.N) http://software.intel.com/en-us/articles/fast-simd-integer-move-for-the-intel-pentiumr-4-processor/

            // TODO: load entire cacheline at the same time? are there enough registers? 32 bit mode (special compile for 64bit?) (R.N)
            s = _mm_load_si128(source128_1);        // AABGGRR
            d = _mm_load_si128(source128_2);        // AABGGRR

            // PRELERP(S, D) = S+D - ((S*D[A]+0x80)>>8)+(S*D[A]+0x80))>>8
            // T = S*D[A]+0x80 => PRELERP(S,D) = S+D - ((T>>8)+T)>>8

            // set alpha to lo16 from dest_
            a = _mm_srli_epi32(d, 24);          // 000000AA 
            rb = _mm_slli_epi32(a, 16);         // 00AA0000
            a = _mm_or_si128(rb, a);            // 00AA00AA

            rb = _mm_and_si128(lomask, s);      // 00BB00RR     
            rb = _mm_mullo_epi16(rb, a);        // BBBBRRRR 
            rb = _mm_add_epi16(rb, round);      // BBBBRRRR
            t = _mm_srli_epi16(rb, 8);          
            t = _mm_add_epi16(t, rb);
            rb = _mm_srli_epi16(t, 8);          // 00BB00RR 

            ag = _mm_srli_epi16(s, 8);          // 00AA00GG     
            ag = _mm_mullo_epi16(ag, a);        // AAAAGGGG     
            ag = _mm_add_epi16(ag, round);
            t = _mm_srli_epi16(ag, 8);
            t = _mm_add_epi16(t, ag);
            ag = _mm_andnot_si128(lomask, t);   // AA00GG00     

            rb = _mm_or_si128(rb, ag);          // AABGGRR      pack

            rb = _mm_sub_epi8(s, rb);           // sub S-[(D[A]*S)/255]
            d = _mm_add_epi8(d, rb);            // add D+[S-(D[A]*S)/255]

            _mm_stream_si128(dest128, d);
        }
    }   
    _mm_mfence();   //ensure last WC buffers get flushed to memory      
}

答案 1 :(得分:5)

今天的处理器可以在从内存中获取一个值所花费的时间内进行大量计算,特别是如果它不在缓存中。这使得对可能的解决方案进行基准测试变得尤为重要,因为您无法轻易地推断出结果会是什么。

我不知道你为什么担心浮点转换,这可以全部完成。

BYTE r1, r2, rDest;
BYTE g1, g2, gDest;
BYTE b1, b2, bDest;

BYTE av; // Alpha value BYTE
rem = 255 - av; // Remaining fraction

rDest = (r1*rem + r2*av) / 255;
gDest = (g1*rem + g2*av) / 255;
bDest = (b1*rem + b2*av) / 255; 

如果你想变得非常聪明,你可以用乘法后跟右移替换除法。

编辑:这是使用右移的版本。添加常量可能会减少任何截断错误,但我会将其作为读者的练习。

BYTE r1, r2, rDest;
BYTE g1, g2, gDest;
BYTE b1, b2, bDest;

BYTE av; // Alpha value BYTE
int aMult = 0x10000 * av / 255;
rem = 0x10000 - aMult; // Remaining fraction

rDest = (r1*rem + r2*aMult) >> 16;
gDest = (g1*rem + g2*aMult) >> 16;
bDest = (b1*rem + b2*aMult) >> 16; 

答案 2 :(得分:1)

混合颜色在计算上是微不足道的。如果这产生了显着的好处,我会感到惊讶,但一如既往,你必须首先证明这个计算首先是性能瓶颈。我建议更好的解决方案是使用硬件加速混合。