GBAの画面の表示方法には大きく分けて2種類あります。それはビットマップモードとタイルモードです。さらにビットマップモードではMode0, 1, 2とあってビットマップモードではMode3, 4, 5と別れています。図にすると次のようなものです。
Mode | 名称 |
0-2 | タイルモード |
3-5 | ビットマップモード |
まず最初はビットマップモードの3~5を練習してみましょう。
このモードはとあるメモリ領域に値を書き込むと、それがそのまま画面に反映されます。C言語ではint変数に123を代入するとき「int abc = 123;」という書き方をしますが、この123が色データの役割を持ち、abcはメモリ領域(そのまま画面に反映されるもの)だ、と思ってください。GBAの画面サイズは240x160ドットです。1ドットにつき2バイトで表現します。240x160x2バイトなので容量は76800バイト。メモリ領域(VRAM)は、0x6000000番地から開始されます。
領域名称 | 開始位置 | 終了位置 | 大きさ(バイト) |
VRAM(Video RAM) | 0x6000000 | 0x6012C00 | 76800 |
ドットを表示する関数をソースコードで書くと以下になります。
void Mode3PutPixel(u32 x, u32 y, u16 color) { u16* ScreenBuffer = (u16*)0x6000000; ScreenBuffer[y*240+x] = col; }
引数xの幅は0~240、引数yの幅は0~160。それ以上の値を放り込むと簡単に壊れてしまう関数ではあります。最後の引数colorは、色(RGB)データでマクロを使って表します。
#define RGB5(r,g,b) ((r)|((g)<<5)|((b)<<10))
色 | r | g | b |
白 | 31 | 31 | 31 |
赤 | 31 | 0 | 0 |
緑 | 0 | 31 | 0 |
青 | 0 | 0 | 31 |
黒 | 0 | 0 | 0 |
下位ビットから順番に赤、緑、青がそれぞれ5bitづつ、0~31の32段階を指定して使います。いきなりビットシフトは難しいかもしれません。そこで白色を代入した場合のプログラム的な動きを見ていきましょう。
u16 color = RGB5(31,31,31);
マクロですので次のように分解されます。
u16 color = ((31)|((31)<<5)|((31)<<10));
31とは
#ref(): File not found: "clip_1.gif" at page "tutorial.2"
とりあえず1ドットだけ出してみましょう。GBA版Hallo Worldです。
#include "lib/gba.h" //--------------------------------------------------------------------------- void WaitForVsync(void) { while(*(vu16*)0x4000006 >= 160) {}; while(*(vu16*)0x4000006 < 160) {}; } //--------------------------------------------------------------------------- void Mode3PutPixel(u32 x, u32 y, u16 col) { u16* ScreenBuffer = (u16*)0x6000000; ScreenBuffer[y*240+x] = col; } //--------------------------------------------------------------------------- int main(void) { // モード設定 SetMode(MODE_3 | BG2_ENABLE); // ドットの描画 u32 x = 50; u32 y = 50; u16 col = RGB5(31,31,31); Mode3PutPixel(x, y, col); for(;;) { WaitForVsync(); } }
まず始めに、グラフィックスのモードを設定します。
SetMode(MODE_3 | BG2_ENABLE);
グラフィックスモードの設定はヘッダファイルにあるSetMode()マクロを使っています。
この内容を分解してみると
// gba.h static inline void SetMode(int mode) {REG_DISPCNT = mode;} #define REG_DISPCNT *((vu16 *)(REG_BASE + 0x00)) #define REG_BASE 0x04000000
*((vu16*)(0x04000000 + 0x00)) = MODE_3 | BG2_ENABLE
と同じ意味になり、0x04000000~0x04000001に値を入れることで
モードの設定をしています。
ビットマップモードで使用するBG番号は2のみですが、
詳しいことはタイルモードのところで説明します。
u16* ScreenBuffer = (u16*)0x6000000;
次に、ScreenBufferポインタにVRAMを指定させて、
操作をしやすいように2バイト(16bit)=1ドット単位としてu16宣言をしています。
void Mode3PutPixel(u32 x, u32 y, u16 col) { u16* ScreenBuffer = (u16*)0x6000000; ScreenBuffer[y*240+x] = col; }
最後は垂直同期待ちの処理です。
void WaitForVsync(void) { while(*(vu16*)0x4000006 >= 160) {}; while(*(vu16*)0x4000006 < 160) {}; }
GBAは高速で画面を書き換えています。
具体的には左上から右へ順番にドット単位で行い、
そのラインが終わったら次のラインへ、という具合です。
#ref(): File not found: "clip_3.png" at page "tutorial.2"
もう少し詳しく言い直しましょう。液晶画面は240x160ドットです。
最初のラインは240ドット描画後、描画しない68ドット分が存在します。
この非描画期間をH-Blankといいます。
Horizontal Dimensions The drawing time for each dot is 4 CPU cycles. Visible 240 dots, 57.221 us, 960 cycles - 78% of h-time H-Blanking 68 dots, 16.212 us, 272 cycles - 22% of h-time Total 308 dots, 73.433 us, 1232 cycles - ca. 13.620 kHz
仕様書を読むと1ドット描画するのにCPUで4サイクル。
240+68=308ドットで1232サイクルと書いてありますね。
次に、160ライン分終わった後にも68ライン分存在します。
この非描画期間をV-Blankといいます。
Vertical Dimensions Visible (*) 160 lines, 11.749 ms, 197120 cycles - 70% of v-time V-Blanking 68 lines, 4.994 ms, 83776 cycles - 30% of v-time Total 228 lines, 16.743 ms, 280896 cycles - ca. 59.737 Hz
描画処理を行う場合、基本的にはこのV-Blankの間に行います。
V-Blankを無視して描画した場合、チラついて見えたりするのでオススメしません。
他のサンプルでは無視していますけどね。(^^;
先ほどの関数は、VCOUNT(描画中のライン)が160になるまで待つことを
意味しています。
x座標:50、y座標:50に、白色を1ドット表示
#ref(): File not found: "clip_2.png" at page "tutorial.2"