BREW の高速パレット転送
昔、8 ビットインデックスのパレットから RGB565 へ高速に転送するルーチンを書きました。
ふと思い出したので貼り付けておきます。
// dst: RGB565 // src: 8bit index // pal: RGB565 // 透過パレットインデックス指定による透過転送 void Blt_RGB565_PLT8RGB565_Colorkey( word* dst, // 転送先画像 int dx, // 転送先 X 座標 int dy, // 転送先 Y 座標 int dp, // 転送先 pitch byte* src, // 転送元画像(8bit index) int sx, // 転送元 X 座標 int sy, // 転送元 Y 座標 int sp, // 転送元 pitch int w, // 転送元 width int h, // 転送元 height word* pal, // パレット uint32 key) // 透過インデックス { dst = (word*)((byte*)dst + dx * sizeof(word) + dy * dp ); src = (byte*)((byte*)src + sx * sizeof(byte) + sy * sp ); #ifdef WIN32 for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { if (src[x] != key) { dst[x] = pal[src[x]]; } } dst = (word*)((byte*)dst + dp); src = (byte*)((byte*)src + sp); } #else // この位置まで src のポインタが進んだら転送終了 uint32 jend = (uint32)((byte*)src + h * sp); // w ピクセル転送した後の、残りの pitch uint32 ddp = dp - w * sizeof(word); uint32 dsp = sp - w * sizeof(byte); // src を 4 バイトで align するために、何バイト転送すればいいか、という値 // src % 4 が 0 なら 0 バイト、 // src % 4 が 1 なら 3 バイト、 // src % 4 が 2 なら 2 バイト、 // src % 4 が 3 なら 1 バイト転送する必要がある。 uint32 al = (4 - ((uint32)src & 0x3)) & 0x3; // 8 ループ展開転送した後に、残りの転送する必要のあるバイト数 // w バイト中 al バイト転送して、8 で割った余りが残りのバイト数になる uint32 iend8d = ((w - al) & 7) * sizeof(byte); // w の値が極端に小さい場合、al を超えてしまうことがあるので調整する if ((uint32)w * sizeof(byte) < al) al = w * sizeof(byte); // 1 バイトを取り出すためのマスクを用意 // 左にシフトしているのは、こうすると key が 0 の場合に高速化出来るから uint32 mask = 0xff << 1; key <<= 1; uint32 alend; uint32 iend8, iend; // key が 0 の場合は高速化が期待できるので、分ける if (key == 0) { __asm { B jcomp_0 jloop_0: ADD iend, src, w SUB iend8, iend, iend8d ADD alend, src, al B alcomp_0 alloop_0: LDRB r0, [src], #1 ANDS r0, mask, r0, LSL #1 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #0] ADD dst, dst, #2 alcomp_0: CMP src, alend BLT alloop_0 B icomp8_0 iloop8_0: LDMIA src!, {r1, r2} ANDS r0, mask, r1, LSL #1 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #0] ANDS r0, mask, r1, LSR #7 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #2] ANDS r0, mask, r1, LSR #15 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #4] ANDS r0, mask, r1, LSR #23 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #6] ANDS r0, mask, r2, LSL #1 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #8] ANDS r0, mask, r2, LSR #7 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #10] ANDS r0, mask, r2, LSR #15 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #12] ANDS r0, mask, r2, LSR #23 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #14] ADD dst, dst, #16 icomp8_0: CMP src, iend8 BLT iloop8_0 B icomp_0 iloop_0: LDRB r0, [src], #1 ANDS r0, mask, r0, LSL #1 LDRNEH r0, [pal, r0] STRNEH r0, [dst, #0] ADD dst, dst, #2 icomp_0: CMP src, iend BLT iloop_0 ADD dst, dst, ddp ADD src, src, dsp jcomp_0: CMP src, jend BLT jloop_0 }; } else { __asm { B jcomp jloop: ADD iend, src, w SUB iend8, iend, iend8d ADD alend, src, al B alcomp alloop: LDRB r0, [src], #1 AND r0, mask, r0, LSL #1 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #0] ADD dst, dst, #2 alcomp: CMP src, alend BLT alloop B icomp8 iloop8: LDMIA src!, {r1, r2} AND r0, mask, r1, LSL #1 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #0] AND r0, mask, r1, LSR #7 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #2] AND r0, mask, r1, LSR #15 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #4] AND r0, mask, r1, LSR #23 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #6] AND r0, mask, r2, LSL #1 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #8] AND r0, mask, r2, LSR #7 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #10] AND r0, mask, r2, LSR #15 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #12] AND r0, mask, r2, LSR #23 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #14] ADD dst, dst, #16 icomp8: CMP src, iend8 BLE iloop8 B icomp iloop: LDRB r0, [src], #1 AND r0, mask, r0, LSL #1 CMP r0, key LDRNEH r0, [pal, r0] STRNEH r0, [dst, #0] ADD dst, dst, #2 icomp: CMP src, iend BLT iloop ADD dst, dst, ddp ADD src, src, dsp jcomp: CMP src, jend BLT jloop }; } #endif }
基本的に1ラインの転送において、
1.src のポインタを 4 バイトでアライン
2.8 バイトほど一気にロードして転送するのを、8 バイト同時にロードできなくなるまで繰り返す
3.残りのバイトを転送
という動作を行っています。
単なる8ループ展開ですね。
1を行っているのは、ARM でのワード(4バイト)単位のロードは、そのポインタが4バイトでアラインされている必要があるからです。
個人的には key が 0 の時にうまく高速化出来たのが素晴らしいと思ってたりするわけですが、コンパイルしてみるとパイプライン動作の兼ね合いのせいか微妙に順番が変わってたり命令が置き換えられたりとかしてショボーンだったりしたのですが、まあ手動最適化+コンパイラ最適化でうまく高速化出来てるんじゃないかなと思います。
あ、ちなみに ARM7 の時代に書いたコードなので、ARM9 ならもしかしたらもっと速く転送できる方法があるかも?