alpha:利用MMX优化64K色Alpha混合算法



作者:云风

  自从今年 3 月云风开始使用 Pentium 200MMX CPU 后, 直在考虑如何用 MMX 技术加快 Alpha 混合操作, 尤其是针对目前常用高彩模式. 而早先在国外个有关游戏编程 MailList 讨论结果是 MMX 不利于对 16位色进行 Alpha 混合操作. 让我们先来看看 MMX 技术相对于普通指令集更新,来了解下这个论点立论. 

  MMX 技术优势在于, 它寄存器是 64 位, 而提供了分组模式, 可以将寄存器内数据按 8 个字节, 或 4 个字, 或 2 个双字同时进行同操作, 方便了大数据量数据处理; 可以成组数据同时作比较操作, 这为透明色点批量判断带来好处; MMX  CPU 拥有 8 个 MMX 寄存器, 在定程度上缓解了 80x86 CPU 寄存器数量不足缺陷. 

  但是它也有诸多不足, 比如算术指令不能对 4字节字操作; 指令结构都不影响标志位; 不能对常数立即寻址; MMX 系统指令集指令相当贫乏(连 NOT 操作也不能直接实现); 

  当颜色深度是 24/32 位时, RGB 都占 8 位, 这样可以巧妙利用 MMX 里分组乘法指令达到做 Alpha 混合运算效果(MMX 乘法相关指令只有对字操作 PMULHW/PMULLW 两条, 分别是成组数据乘后取高位和取低位) 本文旨在探讨 16bit 色快速 Alpha 混合运算, 所以此处略去不提. 

  而 16bit 色, 红绿蓝各占 5 或 6 位, 难以被分组分开, 所以不利于运用 MMX 这些特性. 当然另外解决思路方法是采用 aRGB 4444 结构, 其中 4 位是 Alpha 通道, 每个色素占半个字节, 再采用类似思路方法. 

  看过云风去年提出16bit Alpha 混合优化算法朋友, 应该会联想到这个算法向 MMX 引申, OK, 也许你已经明白了大概, 本文理论基本点就在此, 唯问题是, 我们需要面对是 MMX 指令集种种缺陷, 这些在实际设计中会逐步体现出来, 下面, 云风将在介绍算法同时, 附带提出些运用 MMX 窍门技巧(随后将有专文介绍 MMX 编程技术) 

  先来看看上次算法有无可进步优化可能: 

  16bit 下 Alpha 混合关键在于如何将 RGB 分离, 让随后乘法结果不至于相互干扰. 

我提出是将 16bit  rrrrrggggggbbbbb 扩展到 32bit 变形成 00000gggggg00000rrrrr000000bbbbb, 即将中间绿色提到高 16 位, 而使色素间隔都有 5 到 6 位, 而 对于 5 位颜色, 超过 5 位 Alpha 级别是没有意义, 所以只要设定 Alpha 值在 0~31 间, 同时算这 3 个色素乘法是不会进位造成干扰. 而这里需要多操作次移位扩展 16 位到 32 位, 然后需要次和操作, 将中间间隔位置0, 而且结果需要同样复杂逆操作从 32 位还原到 16 位. 

  改进思路是直接将两个点交错分离, 即 rrrrrggggggbbbbbRRRRRGGGGGGBBBBB 分离成 rrrrr000000bbbbb00000GGGGGG00000 和 00000gggggg00000RRRRR000000BBBBB 两部分, 前部分右移 5 位后变成 00000rrrrr000000bbbbb00000GGGGGG, 两个数字就都可以同时运算 3 个色素, 其结果后组右移 5 位后可以和前组合并. 这样就省去了好几次移位操作, 并且数据可以 4 字节读入, 和 4字节写, 粗看真效率很高. 但是在传统 80x86 上却有两点制约了它运用: 

CPU 寄存器不够用, 这个思路方法光保存数据就需要 4 个 32 位寄存器, 虽然 EAX,EBX,ECX,EDX 刚够用, 但是这就使得 Alpha 混合不能直接写在 Blit 操作里面. 必须单写个子. (不过也值得写尝试下, 不是吗? 如果有朋友写好了, 希望能给我拜读下,我在风魂游戏库里留了接口, 并在注释里提到了具体写法) 
2D 游戏中, 般都是利用 Alpha 混合绘制精灵而不是规则矩形位图, 所以这里面还存在着透明色判断, 如果是双点处理, 这步不易实现. (不过也不是没有好思路方法, 就是代码长度就长而复杂了:-( ) 
而 MMX 却提供了 8 个寄存器, 同时有分组比较指令, 正好弥补了这两点不足, 而且利用寄存器有 64 位优势可以同时运算 4 个点. 所以我们暂且只用 MMX 来实现新想法.(如果你对这个思路方法用在传统指令集上有兴趣, 希望同时操作 2 个点进行 Alpha 混合, 并写出实际代码, 请和我联系, 我非常希望看到风魂非 MMX Alpha 混合版本能够进步优化) 
  用 MMX 来做这项工作, 原理差不多(相当简单不是?), 也是读入源点和目标点后分离成 4 个数据放在 4 个寄存器中. 两对间进行 Alpha 混合, (这样对数据间就同时运算了 6 个色素) 最后就两对数据混合结果合并不过从现在开始我们就要面对 MMX 8 个寄存器不够用困境了 :-( MMX 指令不能和 64 位立即常数起使用, 所以在进行分裂操作时候用到掩码要常驻在寄存器内. 如果寄存器主够多话, 可以连掩码反值也放个, 可惜现在不能这么浪费 :-( 处理透明色问题方面, 可以先将点和透明色比较得到个掩码, 我们再将混合后点,及原来目标图上点 (这个点应当保留个备份, 哎, 又去了个寄存器) 分别和掩码逻辑运算合并得到最终数据写入目标图. 这里, 需要大量运用 NOT 操作, Intel 竟然没有在 MMX 指令集中提供 @#$%^&! 我们只好用 PANDN (取反再和操作) 间接完成. (例:可以先用 PCMPEQW mm0,mm0 (自己和自己比较当然全相等了-) 生成常数 FFFFFFFFFFFFFFFF, 用 PANDN mm1,mm0 就可以将 mm1 取反.) 这里, 不再可以利用 MMX 分组乘法, (MMX 不能对 32 位数进行乘法操作) 所以我们应该用移位和加减法来实现. 这样, 如果有几级 Alpha 值, 就应该写几个混合. 最后建立指针, 将每级 Alpha 混合依次放入. 我们在时就可以根据需要 Alpha 值来相应了 :-) 



  在风魂 0.07 里, Alpha 混合又次修改了算法, (0.06 使用上述算法, 0.07 则没有) 这里要感谢网友 T&P ([email protected]新思路. 针对分级数比较少 Alpha 混合, 比如 8 级, 可以用更简单思路方法. 大家可以注意到, 50%  Alpha 时, R=(r1+r2)/2, 也可以近似等于 r1/2+r2/2. 那么 RGB 可以方便同时运算. 只需要在移位后做次简单和操作即可 (0RRRRRGGGGGGBBBB & 011110111101111=0RRRR0GGGGG0BBBB) 然后, 将两个移位后数据相加就完成了 Alpha=50% 混合. 这个思路方法避免了切分和还原数据, 所以速度更快. 风魂早期版本, 对 50%  Alpha 度就做了此种特殊处理. 但是, 它是有误差, 误差在于移位造成每色素上 1/32 或 1/64 偏差. 

  下步我们可以将 50%  Alpha 值推广到 25% 12.5% 甚至更小. 现在来看下完成 R1*25%+R2*75%, 它等于 R2+R1*25%-R2*25%=R2+R1/4+R2/4. 这里除 4 操作和除 2 原理是即: (RRRRRGGGGGGBBBBB >> 2) & 0011100111100111. 依次类推, X * 37.5% + Y * 62.5% = (X+Y)/2 + Y/8 - X/8 等等. 我们就只需要利用移位和加减法就可以同时完成 N 个色素混合了. 

  再来看看这个思路方法缺陷. 首先是误差问题, 每组移位取和都会造成最大为 1/32 误差, 而多次运算有可能使误差累计, 所以 alpha 级别不能分太多. 而且 alpha 级别分太细后, 使得运算步骤变很多, 不切分直接运算优势有可能损失掉. 而且更致命点是, 如果想用 MMX 加速, 那么通常 AND 运算用掩码应该放在寄存器中 (如果放在内存, 而 MMX 不能立即寻址, 间接寻址取内存可能不能命中 CACHE 速度变慢, 大规模混合运算速度损失太多) MMX 寄存器却只有 8 个. 那么多个掩码会使明显感觉寄存器不够用, 但这不失为种好思路方法. 风魂 0.07 中新 alpha 精灵, 这算法更改带来了 10% 左右速度提升, 而画质损失却几乎没有体现 :-) 

  最后对有关带 Alpha 通道位图点探讨, 这里每个点将带有区别 Alpha 值, 我们应该合理协调位图结构. 将 Alpha 值和颜色信息放在起是不合算. 这样不利于高速处理我们可以将所有点 Alpha 值提出来放在起, 对于 16bit 颜色, 合理 Alpha 级别应该在 16级以下这样可以每个字节存放两个 Alpha 值. 用个寄存器作为指向 Alpha 值区域指针, 读入对应点 Alpha 值, 相应混合运算但是, 这种位图每个点都有可能是区别 alpha 值, 如此就不能多点同时运算, 云风找到了另外加速思路方法, 要知详情, 且看下文分解 ^_^
Tags:  alphahydrox substationalpha alpha通道 alpha

延伸阅读

最新评论

发表评论