【问题标题】:Faster alpha blending using a lookup table?使用查找表进行更快的 alpha 混合?
【发布时间】:2011-07-23 05:43:24
【问题描述】:

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

总而言之,要完全计算 3 通道 RGB 混合,需要对每个通道的数组进行两次查找,外加一个加法。这总共是 6 次查找和 3 次添加。在下面的示例中,为了便于演示,我将颜色拆分为单独的值。这个例子展示了如何通过 0 到 256 的 alpha 值混合三个通道,R G 和 B。

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 给我发邮件,但不要期待明天才能回复,因为我现在要睡觉了。)

那么……你怎么看?值不值?

【问题讨论】:

  • 这是一个非常糟糕的主意。你会破坏缓存,通常会因为做这种愚蠢的事情而受到严厉的惩罚。计算它,如果可能的话,使用 SSE/MMX 等并行计算。甚至更好,使用 GPU。
  • 您可以在编译时使用模板生成表格,也可以在启动期间在运行时生成表格,从而避免巨大的文件。
  • 我支持关于缓存垃圾的评论。
  • 我仍然无法理解 XMM 寄存器;它们用于浮点但用于整数计算?你能推荐任何材料来详细了解它们吗?
  • 在现实世界中计算它比在内存中查找要快得多(无论如何在台式电脑上)。如果你做一个微基准测试并使用相同的内存地址在它上面进行测试,你会自欺欺人地相信这个表更好,因为你最终会缓存比你真正​​想要的更多。请记住,内核的运行速度至少比主板接口快 10 倍。许多工作可以在一个总线周期发生的时间内完成。巨型表的坏处:它们将真实数据推出缓存。

标签: c++ c optimization graphics alphablending


【解决方案1】:

我有兴趣查看一些基准。

有一种算法可以在没有任何浮点计算或查找表的情况下进行完美的 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      
}

【讨论】:

  • 很好...谢谢;现在我可以摆脱巨大的查找表了
【解决方案2】:

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

我不知道你为什么关心浮点转换,这都可以用整数来完成。

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; 

【讨论】:

  • 哇,太棒了。很不错。你试过预乘图像吗?另外,如果(FF)然后复制dword,否则如果(00)然后忽略,否则进行计算是否有帮助。这会提高性能,还是做 else if 和 else 让它变慢?使用某种 RLE 进行计算(少数)怎么样?再次感谢您的算法。非常感激。 ;)
  • @Jarno 我不知道特殊的套管 00 anf FF 是否会有所帮助,我的直觉是分支会使它变得稍微慢一些,但只有一个基准可以确定。对于预乘 alpha,您不需要做任何特别的事情。
【解决方案3】:

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

【讨论】:

  • 并非所有平台和机器都支持硬件混合;并且从 float 到 int 的转换会降低性能,即使使用 fistp 指令也是如此。定点就更糟了。这只需要内存查找和添加。但是,我想知道查找是否会比浮点到整数的转换更快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多