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 ならもしかしたらもっと速く転送できる方法があるかも?