Chiharu の日記

絵描き C/C++ プログラマーの日記です。

Parallel force - パラレル・フォース 〜SSE2 終わり

これ以上の高速化はあんまし思いつかないや、というところまで描画処理をまとめました。一例としてαブレンディングの最適化結果など。
1 画素ずつ処理する場合は、下記の非 SIMD 処理で...

inline const PixPacked32 operator()(const PixPacked32 iDst, const PixPacked32 iSrc, const PixLevel iAlpha)
{
  const auto aSrcAlpha = convertRange255to256(iAlpha);
  const auto aDstAlpha = 256 - aSrcAlpha;
  const auto aDst13 = (iDst & 0xff00ff) * aDstAlpha + (iSrc & 0xff00ff) * aSrcAlpha;
  const auto aDst02 = (iDst & 0x00ff00) * aDstAlpha + (iSrc & 0x00ff00) * aSrcAlpha;
  return ( (aDst13 & 0xff00ff00) | (aDst02 & 0x00ff0000) ) >> 8;
}

デスティネーションが 16 バイトアライメント可能かつ 4 画素ずつ処理できる場合は、下記の SIMD (SSE2) 処理で...

inline const PixPacked128 operator()(const PixPacked128 iDst, const PixPacked128 iSrc, const PixPacked128 iAlpha32x4)
{
  const auto aZero = _mm_setzero_si128();
  const auto aSrcAlpha = _mm_srli_epi16(_mm_mullo_epi16(iAlpha32x4, _mm_set1_epi32(129)), 7);  // 0-255 -> 0-256
  const auto aDstAlpha = _mm_sub_epi32(_mm_set1_epi32(256), aSrcAlpha);
  
  // 下位 64-bit
  auto aSrcAlpha1 = _mm_shufflelo_epi16(aSrcAlpha, _MM_SHUFFLE(2, 2, 0, 0));
  auto aDstAlpha1 = _mm_shufflelo_epi16(aDstAlpha, _MM_SHUFFLE(2, 2, 0, 0));
  aSrcAlpha1 = _mm_unpacklo_epi16(aSrcAlpha1, aSrcAlpha1);
  aDstAlpha1 = _mm_unpacklo_epi16(aDstAlpha1, aDstAlpha1);
  
  auto aSrc = _mm_unpacklo_epi8(aZero, iSrc);
  auto aDst = _mm_unpacklo_epi8(aZero, iDst);
  aSrc = _mm_mulhi_epu16(aSrc, aSrcAlpha1);
  aDst = _mm_mulhi_epu16(aDst, aDstAlpha1);
  const auto aLo = _mm_add_epi16(aSrc, aDst);
  
  // 上位 64-bit
  aSrcAlpha1 = _mm_shufflehi_epi16(aSrcAlpha, _MM_SHUFFLE(2, 2, 0, 0));
  aDstAlpha1 = _mm_shufflehi_epi16(aDstAlpha, _MM_SHUFFLE(2, 2, 0, 0));
  aSrcAlpha1 = _mm_unpackhi_epi16(aSrcAlpha1, aSrcAlpha1);
  aDstAlpha1 = _mm_unpackhi_epi16(aDstAlpha1, aDstAlpha1);
  
  aSrc = _mm_unpackhi_epi8(aZero, iSrc);
  aDst = _mm_unpackhi_epi8(aZero, iDst);
  aSrc = _mm_mulhi_epu16(aSrc, aSrcAlpha1);
  aDst = _mm_mulhi_epu16(aDst, aDstAlpha1);
  const auto aHi = _mm_add_epi16(aSrc, aDst);
  
  return _mm_packus_epi16(aLo, aHi);
}

SIMD 版の処理の半分以上がパック (アンパック) とシャッフルというところが泣けます。これでもかなり削ったんですけれど…。
ちょっとだけ工夫があるのは、8-bit の右シフトの回数が減っているところでしょうか。画素を 16-bit にアンパックする際、{ 0xbb00, 0xgg00, 0xrr00, 0xxx00, 0xbb00, 0xgg00, 0xrr00, 0xxx00 } として、α値 (0-256) との乗算結果の上位 16-bit を採用することでシフト不要にしてあります。
SIMD ってもうちょっと使いやすくならないものでしょうか。いくら並列演算がすごくても、並列演算自体が使いにくかったら仕方ないと思うのですが…。
とにもかくにも、今度こそ高速化は終わり。機能作ります!機能!