用于预乘 ARGB 的 SSE alpha 混合

2024-05-08

我正在尝试编写一个支持 SSE 的 alpha 合成器,这就是我想出的。首先,混合两个 4 像素向量的代码:

// alpha blend two 128-bit (16 byte) SSE vectors containing 4 pre-multiplied ARGB values each
//
__attribute__((always_inline))
static inline __m128i blend4(__m128i under, __m128i over) {
    // shuffle masks for alpha and 255 vector for 255-alpha
    //
    // NOTE: storing static __m128i here with _mm_set_si128 was _very_ slow, compiler doesn't seem
    // to know it can store this as a const, so it had guard variables and did real static initialization,
    // stick with arrays.
    //
    static const uint64_t allo[2] __attribute__((aligned(16))) = { 0x03ff03ff03ff03ff, 0x07ff07ff07ff07ff };
    static const uint64_t alhi[2] __attribute__((aligned(16))) = { 0x0bff0bff0bff0bff, 0x0fff0fff0fff0fff };
    static const uint64_t m255[2] __attribute__((aligned(16))) = { 0xff00ff00ff00ff00, 0xff00ff00ff00ff00 };

    // replicate top two pixels from under
    __m128i underhi = (__m128i)_mm_movehl_ps(
        (__m128)under,
        (__m128)under
    );

    __m128i u16_0 = _mm_cvtepu8_epi16(under);                   // convert 8-bit fields to 16-bit with zero extension
    __m128i u16_1 = _mm_cvtepu8_epi16(underhi);  
    __m128i al8_0 = _mm_shuffle_epi8 (over, *(__m128i*)&allo);  // replicate (alpha << 8) to each field
    __m128i al8_1 = _mm_shuffle_epi8 (over, *(__m128i*)&alhi);
    __m128i mal_0 = _mm_sub_epi8     (*(__m128i*)&m255, al8_0); // compute 255-alpha
    __m128i mal_1 = _mm_sub_epi8     (*(__m128i*)&m255, al8_1);
    __m128i mul_0 = _mm_mulhi_epu16  (u16_0, mal_0);            // under*(255-over.alpha)
    __m128i mul_1 = _mm_mulhi_epu16  (u16_1, mal_1);
    __m128i pixel = _mm_packus_epi16 (mul_0, mul_1);

    // add to background pixel with saturation
    return _mm_adds_epi8(over, pixel);
}

其次,一个包装器展开多个像素操作并聚合加载/存储。达到约 32 像素/iter 似乎是最佳点:

// perform N 4-pixel blending operations at once, load/blend/store paradigm.  We take a template parameter
// for the size so the compiler is more likely to unroll the loops for us.
// 
template <ssize_t N>
__attribute__((always_inline, optimize("unroll-loops")))
static inline void blendN(__m128i *dst, const __m128i *punder, const __m128i *pover, bool single=false) {
    __m128i under[N];
    __m128i  over[N];
    __m128i  cc = _mm_loadu_si128(pover);

    // load
    for (ssize_t ii=0; ii < N; ii++) {
        under[ii] =              _mm_loadu_si128(punder+ii);
        over[ii] = single ? cc : _mm_loadu_si128( pover+ii);
    }

    // blend
    for (ssize_t ii=0; ii < N; ii++) {
        under[ii] = blend4(under[ii], over[ii]);
    }

    // store
    for (ssize_t ii=0; ii < N; ii++) {
        _mm_storeu_si128(dst+ii, under[ii]);
    }
}

如此称呼:

 // blend 32/16/8/4 pixels at a time
    ssize_t ii=0;
    for (ii *= 2; ii < len/32; ii++) { blendN<8>(vdst+8*ii, vunder+8*ii, vover+8*ii); }
    for (ii *= 2; ii < len/16; ii++) { blendN<4>(vdst+4*ii, vunder+4*ii, vover+4*ii); }
    for (ii *= 2; ii < len/8;  ii++) { blendN<2>(vdst+2*ii, vunder+2*ii, vover+2*ii); }
    for (ii *= 2; ii < len/4;  ii++) { blendN<1>(vdst+1*ii, vunder+1*ii, vover+1*ii); }

    // handle remainder
    ii *= 4;
    for (; ii < len; ii++) {
        *(pdst+ii) = blend(*(punder+ii), *(pover+ii));
    }

使用此功能,我可以在 i7-2600K 上获得大约 2.5 英寸/周期的吞吐量。很好奇是否有人可以对我的 SIMD 提出改进建议。

编辑:这是与 Peter Cordes 交谈后的一些更新代码。

__attribute__((always_inline))
static inline __m128i blend4(__m128i under, __m128i over) {
    // shuffle masks for alpha and 255 vector for 255-alpha
    //
    // NOTE: storing static __m128i is _very_ slow, compiler doesn't seem to know it can store
    // this as a const, so it had guard variables and did real static initialization. Stick with 
    // just const
    //
    const __m128i allo = (__m128i)_mm_setr_epi32(0x03ff03ff, 0x03ff03ff, 0x07ff07ff, 0x07ff07ff);
    const __m128i alhi = (__m128i)_mm_setr_epi32(0x0bff0bff, 0x0bff0bff, 0x0fff0fff, 0x0fff0fff);
    const __m128i zero = (__m128i)_mm_setr_epi32(0x00000000, 0x00000000, 0x00000000, 0x00000000);
    const __m128  m255 = (__m128 )_mm_setr_epi32(0xff00ff00, 0xff00ff00, 0xff00ff00, 0xff00ff00);

    __m128i u16_0 =   _mm_cvtepu8_epi16(under);               // convert 8-bit fields to 16-bit with zero extension
    __m128i u16_1 =   _mm_unpackhi_epi8(under, zero);
    __m128i al8_0 =   _mm_shuffle_epi8 (over,  allo);         // replicate (alpha << 8) to each field
    __m128i al8_1 =   _mm_shuffle_epi8 (over,  alhi);
    __m128i mal_0 = (__m128i)_mm_xor_ps(m255, (__m128)al8_0); // compute 255-alpha
    __m128i mal_1 = (__m128i)_mm_xor_ps(m255, (__m128)al8_1);
    __m128i mul_0 =   _mm_mulhi_epu16  (u16_0, mal_0);        // under*(255-over.alpha)
    __m128i mul_1 =   _mm_mulhi_epu16  (u16_1, mal_1);
    __m128i pixel =   _mm_packus_epi16 (mul_0, mul_1);

    // add to background pixel with saturation
    return _mm_adds_epi8(over, pixel);
}

最大的变化是使用 unpackhi 而不是 cvtepu8 将像素下的前 8 个字节扩展到 16 位。然后使用异或而不是减法来计算 255-alpha。 xor 可以在多个端口上运行,而不是减法仅限于一个端口。在我的 i7-2600K 上,这大约每秒混合 22 亿像素,这似乎足够了。


不是对你的问题的直接回答,但对于评论来说太长了,也许它对某人有用。

将 alpha 排列到每个 16 位通道的上半部分的技巧,以便您可以使用_mm_mulhi_epu16用一条指令将乘积放入低位确实很巧妙。我的问题略有不同,因为我没有预乘 Alpha,并且我需要能够指定整个纹理的不透明度。我将代码扩展为以下内容:

__m128i blend4(__m128i under, __m128i over, float opacity) {
    const __m128i alpha16 = _mm_set1_epi16(alpha * 255);
    const __m128i allo = (__m128i) _mm_setr_epi32(0xff03ff03, 0xff03ff03, 0xff07ff07, 0x0ff7ff07);
    const __m128i alhi = (__m128i) _mm_setr_epi32(0xff0bff0b, 0xff0bff0b, 0xff0fff0f, 0x0fffff0f);
    const __m128i zero = (__m128i) _mm_setr_epi32(0x00000000, 0x00000000, 0x00000000, 0x00000000);
    const __m128i i255 = (__m128i) _mm_setr_epi32(0xff00ff00, 0xff00ff00, 0xff00ff00, 0xff00ff00);

    __m128i under0 = _mm_cvtepu8_epi16(under);
    __m128i under1 = _mm_unpackhi_epi8(under, zero);
    __m128i over0 = _mm_cvtepu8_epi16(over);
    __m128i over1 = _mm_unpackhi_epi8(over, zero);
    __m128i alpha0 = _mm_mullo_epi16(_mm_shuffle_epi8(over, allo), alpha16);
    __m128i alpha1 = _mm_mullo_epi16(_mm_shuffle_epi8(over, alhi), alpha16);
    __m128i invAlpha0 = _mm_xor_si128(i255, alpha0);
    __m128i invAlpha1 = _mm_xor_si128(i255, alpha1);
    __m128i underMul0 = _mm_mulhi_epu16(under0, invAlpha0);
    __m128i underMul1 = _mm_mulhi_epu16(under1, invAlpha1);
    __m128i overMul0 = _mm_mulhi_epu16(over0, alpha0);
    __m128i overMul1 = _mm_mulhi_epu16(over1, alpha1);
    __m128i underFinal = _mm_packus_epi16(underMul0, underMul1);
    __m128i overFinal = _mm_packus_epi16(overMul0, overMul1);
    return _mm_adds_epu8(overFinal, underFinal);
}

我最初将 alpha 混入每个通道的下半部分,以便结果的高位在乘以后最终位于每个通道的上半部分alpha16,然后从那里_mm_mulhi_epu16技巧照常进行。剩下的只是简单的阿尔法乘法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

用于预乘 ARGB 的 SSE alpha 混合 的相关文章

  • 将字段中的位扩展到掩码中所有(重叠+相邻)集位的最快方法?

    假设我有 2 个名为 IN 和 MASK 的二进制输入 实际字段大小可能是 32 到 256 位 具体取决于用于完成任务的指令集 每次调用时两个输入都会改变 Inputs IN 1100010010010100 MASK 000111101
  • 当前的 x86 架构是否支持非临时加载(来自“正常”内存)?

    我知道有关此主题的多个问题 但是 我没有看到任何明确的答案或任何基准测量 因此 我创建了一个处理两个整数数组的简单程序 第一个数组a非常大 64 MB 第二个数组b很小 无法放入 L1 缓存 程序迭代a并将其元素添加到相应的元素中b在模块化
  • 更改组合框下拉列表边框的颜色

    My code Private Sub ComboBox2 DrawItem sender As Object e As DrawItemEventArgs Handles ComboBox2 DrawItem If e Index lt
  • CISC 机器 - 它们不只是将复杂指令转换为 RISC 吗?

    也许我在架构上存在误解 但如果机器有 比如说 乘法指令 该指令是否未转换为更小的指令 或者过于复杂以至于最终与等效的 RISC 指令具有相同的速度 乘法是一个不好的例子 它在两种体系结构中都是一条指令 将上面的 乘法 替换为 CISC 中更
  • 如何在 SVG 元素上使用箭头标记?

    我需要在 d3 js 中创建一个箭头 但我找到的只是带有节点图的示例 我需要的是简单地制作一个从 A 点到 B 点的箭头 我尝试实现以下示例中的部分代码 http bl ocks org 1153292 http bl ocks org 1
  • 优化大数据读写(C++)

    我正在寻求优化 C 模拟应用程序的读取 写入大量数据 称为 映射 的数据本质上由整数 双精度数 浮点数和单个枚举组成 该地图数据的大部分大小是固定的 但一小部分可能会变化 从几KB到几KB 大小 几个这样的映射 通常是数百万个 在应用程序启
  • PAE(物理地址扩展)如何实现大于4GB的地址空间?

    维基百科文章的摘录物理地址扩展 http en wikipedia org wiki Physical Address Extension x86 处理器硬件架构通过用于选择附加内存的附加地址线进行了增强 因此物理地址大小从 32 位增加到
  • 挑战:优化取消列出[简单]

    因为 SO 最近有点慢 所以我发布了一个简单的问题 如果大鱼们能在这场比赛中留在替补席上并给新秀们一个回应的机会 我将不胜感激 有时我们的对象具有大量的大列表元素 向量 您如何将这个对象 取消列出 到单个向量中 证明你的方法比unlist
  • 当 mov 指令导致页面错误并且在 x86 上禁用中断时会发生什么?

    我最近在自定义 Linux 内核 2 6 31 5 x86 驱动程序中遇到一个问题 其中 copy to user 会定期不将任何字节复制到用户空间 它将返回传递给它的字节数 表明它没有复制任何内容 经过代码检查 我们发现代码在调用 cop
  • 如何有效地扫描每次迭代交替的 2 位掩码

    给定 2 个位掩码 应交替访问 0 1 0 1 我尝试获得运行时高效的解决方案 但找不到比以下示例更好的方法 uint32 t mask 2 uint8 t mask index 0 uint32 t f tzcnt u32 mask ma
  • Java中的整数缓存[重复]

    这个问题在这里已经有答案了 可能的重复 奇怪的Java拳击 https stackoverflow com questions 3130311 weird java boxing 最近我看到一个演示 其中有以下 Java 代码示例 Inte
  • 分组符号最大长度平衡子序列

    将 B 视为分组符号 和 的序列 如果 B 的长度为 0 或 B 具有以下形式之一 则称 B 为平衡序列 X Y 或 X Y 或 X Y 其中 X 和 Y 本身是平衡的 平衡示例 现在的问题是找到一种有效的算法来找到给定输入的最大长度平衡子
  • 适用于图形应用程序的快速、像素精度 2D 绘图 API?

    我想创建一个跨平台的绘图程序 编写应用程序的一个要求是画布上具有像素级精度 例如 我想编写自己的画线算法 而不是依赖别人的 我不想要任何形式的抗锯齿 同样 需要像素级控制 我希望屏幕上的用户交互快速且响应灵敏 取决于我编写快速算法的能力 理
  • 难以理解汇编命令“加载有效地址”[重复]

    这个问题在这里已经有答案了 可能的重复 LEA 指令的目的是什么 https stackoverflow com questions 1658294 whats the purpose of the lea instruction LEA指
  • 如何向 C# XmlDocument 添加新的根元素?

    我有一个不受我控制的 XmlDocument 其结构如下
  • gcc 删除内联汇编代码

    看起来 gcc 4 6 2 删除了它认为函数中未使用的代码 test c int main void goto exit handler asm volatile jmp 0x0 exit return 0 拆解main 0x0804840
  • 平衡括号问题的优化解

    给定一个仅包含字符的字符串 and 判断输入字符串是否有效 输入字符串在以下情况下有效 左括号必须由相同类型的括号封闭 左括号必须按正确的顺序关闭 请注意 空字符串也被视为有效 示例1 Input Output true Example 2
  • 优化 - 步进可能表现奇怪:iOS/Unity

    我正在尝试将 Unity 集成到 iOS 应用程序中 我已经遵循了这个教程http www agnosticdev com blog entry swift integrating unity and vuforia ios swift p
  • 为什么 OpenGL 有远裁剪平面,以及使用什么惯用法来处理这个问题?

    我一直在学习 OpenGL 持续困扰我的一个话题是远裁剪平面 虽然我可以理解近剪裁平面和侧剪裁平面 它们永远不会产生任何实际效果 因为它们之外的对象无论如何都不会被渲染 背后的推理 但远剪裁平面似乎只是一个烦恼 由于 OpenGL 背后的人
  • java绕中心旋转矩形

    我想围绕其中心点旋转一个矩形 它应该保留在应该绘制的位置并在该空间中旋转 这是我的代码 AffineTransform transform new AffineTransform transform rotate Math toRadian

随机推荐