#author("2023-04-20T19:36:26+09:00","","")
* Tutorial.2 ビットマップモード1 [#s37d3144]
#author("2023-04-20T22:39:25+09:00","","")
* ビットマップモード1 [#s37d3144]

GBAの画面の表示方法には大きく分けて2種類あります。それはビットマップモードとタイルモードです。さらにビットマップモードではMode0, 1, 2とあってビットマップモードではMode3, 4, 5と別れています。図にすると次のようなものです。~

| Mode   | 名称               |
| 0-2    | タイルモード       |
| 3-5    | ビットマップモード |

まず最初はビットマップモードの3~5を練習してみましょう。~

** ビットマップモード [#z9cbb8fc]
このモードはとあるメモリ領域に値を書き込むと、それがそのまま画面に反映されます。C言語ではint変数に123を代入するとき「int abc = 123;」という書き方をしますが、この123が色データの役割を持ち、abcはメモリ領域(そのまま画面に反映されるもの)だ、と思ってください。GBAの画面サイズは240x160ドットです。1ドットにつき2バイトで表現します。240x160x2バイトなので容量は76800バイト。メモリ領域(VRAM)は、0x6000000番地から開始されます。~
このモードはとあるメモリ領域に値を書き込むと、それがそのまま画面に反映されます。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とは
31とは2進数でいくつでしょうか。windowsに標準装備されている電卓をプログラマー設定して入力してみます。~

#ref(1.png,nolink)

この2進数表現を入れてみます。~

 u16 color = ((0001 1111b)|((0001 1111b)<<5)|((0001 1111b)<<10));

#ref(clip_1.gif,nolink)
この値はu16単位。つまり2バイト16bitです。拡張します。~

 u16 color = ((0000 0000 0001 1111b)        |
             ((0000 0000 0001 1111b) <<  5) | 
             ((0000 0000 0001 1111b) << 10)  );

シフトビット計算をします。

 u16 color = ((0000 0000 0001 1111b) |
             ((0000 0011 1110 0000b) | 
             ((0111 1100 0000 0000b)  );

OR計算します。

 u16 color = 0111 1111 1111 1111b

この値は0x7fffです。この値は最初にいったとおり白色を意味しています。GBATEKでは次のように書かれているのでちょっと引用してみましょう。32*32*32色で32768 colorsで表現できますよ、と書いてあります。15Bit目は使わないようですね。~

- LCD VRAM Bitmap BG Modes
 
 In BG Modes 3-5 the background is defined in form of a bitmap (unlike as for Tile/Map based BG modes).
 Bitmaps are implemented as BG2, with Rotation/Scaling support.
 As bitmap modes are occupying 80KBytes of BG memory, only 16KBytes of VRAM can be used for OBJ tiles.
 
 BG Mode 3 - 240x160 pixels, 32768 colors
 Two bytes are associated to each pixel, directly defining one of the 32768 colors
 (without using palette data, and thus not supporting a 'transparent' BG color).
 
   Bit   Expl.
   0-4   Red Intensity   (0-31)
   5-9   Green Intensity (0-31)
   10-14 Blue Intensity  (0-31)
   15    Not used in GBA Mode (in NDS Mode: Alpha=0=Transparent, Alpha=1=Normal)

** モード3でドットを描く [#g30f34b7]
とりあえず1ドットだけ出してみましょう。GBA版Hallo Worldです。~

先ほどの関数を使ってドットを書いてみます。GBA版Hallo Worldです。~

- 動作画面
#ref(2.png,nolink)

- ソースコード

 #include "lib/gba.h"
 
 //---------------------------------------------------------------------------
 void WaitForVsync(void)
 {
 	while(*(vu16*)0x4000006 >= 160) {};
 	while(*(vu16*)0x4000006 <  160) {};
 }
 //---------------------------------------------------------------------------
 void Mode3PutPixel(u32 x, u32 y, u16 col)
 void Mode3PutPixel(u32 x, u32 y, u16 color)
 {
 	u16* ScreenBuffer     = (u16*)0x6000000;
 	ScreenBuffer[y*240+x] = col;
 	ScreenBuffer[y*240+x] = color;
 }
 //---------------------------------------------------------------------------
 int main(void)
 {
 	// モード設定
 	SetMode(MODE_3 | BG2_ENABLE);
 
 	// ドットの描画
 	u32 x   = 50;
 	u32 y   = 50;
 	u16 col = RGB5(31,31,31);
 	s32 i;
 
 	Mode3PutPixel(x, y, col);
 	for(i=0; i<20; i++)
 	{
 		Mode3PutPixel(5+i, 5+i, RGB5(31, 31, 31));
 	}
 
 	for(i=0; i<32; i++)
 	{
 		Mode3PutPixel(20+i*2, 50, RGB5(i, 0, 0));
 		Mode3PutPixel(20+i*2, 60, RGB5(0, i, 0));
 		Mode3PutPixel(20+i*2, 70, RGB5(0, 0, i));
 		Mode3PutPixel(20+i*2, 80, RGB5(i, i, i));
 	}
 
 
 	for(;;)
 	{
 	    WaitForVsync();
 		WaitForVsync();
 	}
 
 }

まず始めに、グラフィックスのモードを設定します。~
まず始めにグラフィックモードをmode3を設定します。~

 SetMode(MODE_3 | BG2_ENABLE);

グラフィックスモードの設定はヘッダファイルにあるSetMode()マクロを使っています。~
この内容を分解してみると~
グラフィックモードの設定はヘッダファイルにあるSetMode()マクロを使っています。この内容を分解してみると~

 // gba.h
 
- gba.h

 static inline void SetMode(int mode)	{REG_DISPCNT = mode;}
 
 #define	REG_BASE	0x04000000
 #define	REG_DISPCNT	*((vu16 *)(REG_BASE + 0x00))
 #define	REG_BASE	0x04000000

 
 *((vu16*)(0x04000000 + 0x00)) = MODE_3 | BG2_ENABLE

と同じ意味になり、0x04000000~0x04000001に値を入れることで~
モードの設定をしています。~
と同じ意味になり、0x04000000~0x04000001に値を入れることで設定をしています。GBATEKを引用します。~

ビットマップモードで使用するBG番号は2のみですが、~
詳しいことはタイルモードのところで説明します。~
- 4000000h - DISPCNT - LCD Control (Read/Write)
   Bit   Expl.
   0-2   BG Mode                (0-5=Video Mode 0-5, 6-7=Prohibited)
   3     Reserved / CGB Mode    (0=GBA, 1=CGB; can be set only by BIOS opcodes)
   4     Display Frame Select   (0-1=Frame 0-1) (for BG Modes 4,5 only)
   5     H-Blank Interval Free  (1=Allow access to OAM during H-Blank)
   6     OBJ Character VRAM Mapping (0=Two dimensional, 1=One dimensional)
   7     Forced Blank           (1=Allow FAST access to VRAM,Palette,OAM)
   8     Screen Display BG0  (0=Off, 1=On)
   9     Screen Display BG1  (0=Off, 1=On)
   10    Screen Display BG2  (0=Off, 1=On)
   11    Screen Display BG3  (0=Off, 1=On)
   12    Screen Display OBJ  (0=Off, 1=On)
   13    Window 0 Display Flag   (0=Off, 1=On)
   14    Window 1 Display Flag   (0=Off, 1=On)
   15    OBJ Window Display Flag (0=Off, 1=On)

 u16* ScreenBuffer     = (u16*)0x6000000;
入っている値が0x403。つまり0, 1BitのBG Mode、10 BitのBG2となります。BGはチュートリアルの後半タイルモードにて説明します。今はおまじない程度なものと考えていてください。

次に、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(clip_3.png,nolink)

もう少し詳しく言い直しましょう。液晶画面は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になるまで待つことを~
意味しています。~



*** 動作画面 [#h9cf82bc]
x座標:50、y座標:50に、白色を1ドット表示
#ref(clip_2.png,nolink)


** 履歴 [#h46dccbb]
- 2023/04/20
- 2014/12/21


トップ   一覧 検索 最終更新   ヘルプ   最終更新のRSS