ホームチュートリアルプログラム
1-セットアップ 4-サウンド 7-FIFO インベーダー
2-フレームバッファ 5-SRAM 8-割り込み DSEmu
3-キー入力 6-ファイルシステム 9-マイク  
10-拡張回転バックグラウンド

Homebrew任天堂DS開発 Part 10

拡張回転バックグラウンド

今までのグラフィックスのサンプルの大部分では、私は フレームバッファー グラフィックスモードを使用しています。 このモードには、使用するのが簡単ですが、いくつかの欠点があります。 それの1つは1個のスクリーンだけがフレームバッファモードを使用することができるということです。 両方のスクリーンに同様のグラフィックスを表示したいなら、あなたはフレームバッファを使用することができません。 このチュートリアルは異なったモードを使用することによって、この問題を私たちがどう解決できるかについて解説します。 デモアプリケーションは、モード5の拡張回転バックグラウンドを使用することで JPEG ファイルを表示します。

拡張回転バックグラウンド

事実上、拡張回転バックグラウンドはビットマップをスクリーンに表示することができます。 ビットマップはスクリーンの物理的なサイズより大きくできますので、1時点で任意な位置の部分だけを表示することができます。

このタイプのバックグラウンドは、ハードウェア スクローリングを持っているので、 2個のレジスタを変えることによってどんな追加コードも書かないでビットマップの異なった部分を表示することができます。 拡張回転バックグラウンドはハードウェアによりスケーリング、回転、また、剪断することができます。

私たちがフレームバッファに書いた方法への同様の方法でビットマップを描画でき、すぐに、スクリーンに結果が表示されます。 追加機能と拡張回転バックグラウンドの優位性を少しの追加コードで多くの利点を得られるので、フレームバッファモードが本当に必要性がないように見えます。

グラフィックモードと背景

任天堂DSには、それぞれ異なった能力で利用可能な多くの異なったグラフィックモードがあります。 各モードには、使用することができる多くの'バックグラウンド'があります。

A 'background' is a surface that when drawn upon, displays on the hardware. Modes that have multiple backgrounds allow these backgrounds to be overlayed so that they are drawn on top of each other. This enables a background with text to be overlayed on top of a background displaying an image for example.

このチュートリアルで使用するモードはモード5です。libndsドキュメンテーションによると、このモードで使用されるバックグラウンドは以下の通りです。

モードバックグラウンド記述
50テキストバックグラウンド。3D使用可能
51テキスト。2Dのみ
52拡張回転バックグランド
53拡張回転バックグランド

各バックグラウンドには、それの異なった局面を制御するのに使用できる多くのレジスタがあります。 DStek はこれらについて詳細に解説しています。

モードを5にセットして、どのバックグラウンドを使用するかを定義するために、メインスクリーンのために 'videoSetMode' 呼び出しを使用します。

  videoSetMode(MODE_5_2D | DISPLAY_BG2_ACTIVE);
  vramSetBankA(VRAM_A_MAIN_BG_0x6000000);
  BG2_CR = BG_BMP16_256x256;

  // Set translations
  BG2_XDX = scale_settings[scale];
  BG2_XDY = 0;
  BG2_YDX = 0;
  BG2_YDY = scale_settings[scale];
  BG2_CY = 0;
  BG2_CX = 0;

VRAM メモリーバンク

DSがスクリーン上にデータを表示する場合、どのモードがアクティブかによって、特定のメモリーアドレスに位置するピクセルデータを表示します。 実際のメモリーアドレスはメインスクリーン・サブスクリーンに関わらず使用するモードに依存します。

例えば、フレームバッファモードはメモリーアドレス 0x06800000 に書かれたピクセルを表示します。 従って フレームバッファ チュートリアル では VRAM_A (libnds で 0x06800000に定義されている) に書き込みを行っています。

デフォルトでは、0x06000000-0x06FFFFFF メモリー範囲には読み書きできる実際のメモリーは存在しません。 このエリアにVRAMをマップするようにVRAMメモリーバンクを設定する必要があります。

DS には VRAM_AからVRAM_Iと名付けられた9個の日でもメモリーバンクがあります。 個々のバンクはビットマップ、スプライト、タイル等を使用するための特定のメモリ領域にマップすることができます。

それぞれは異なった目的にのは最もフィットする特定のサイズを持っていてます。 バンクとそのサイズは以下の通りです。

バンク制御レジスタサイズ
VRAM_AVRAM_A_CR128 キロバイト
VRAM_BVRAM_B_CR128 キロバイト
VRAM_CVRAM_C_CR128 キロバイト
VRAM_DVRAM_D_CR128 キロバイト
VRAM_EVRAM_E_CR64 キロバイト
VRAM_FVRAM_F_CR16 キロバイト
VRAM_GVRAM_G_CR16 キロバイト
VRAM_HVRAM_H_CR32 キロバイト
VRAM_IVRAM_I_CR16 キロバイト

フレームバッファ チュートリアル では、私達は VRAM_A を以下のコードを使用して メモリー位置 0x06800000 にマップしました。

  vramSetBankA(VRAM_A_LCD);

ibnds では以下のようになっています。

  VRAM_A_CR = VRAM_ENABLE | VRAM_A_LCD;

'VRAM_A_LCD' は指定された VRAM メモリーバンクを0x06800000 にマップするようための制御レジスタに通知するビットマスクです。 The actual bitmap values and what memory areas they map the VRAM to is available at DStek.

You must ensure that you map the right amount of video memory to the correct memory address for the mode and backgrounds that you are using. In the extended background rotation mode for the main screen, memory address 0x06000000 is used for displaying the information on the screen. We need to map a VRAM バンク to this memory area and it needs to be large enough for the background bitmap we want to display.

ビットマップ背景

バックグラウンドを表示するのに私用されるビットマップは、16ビット色または8ビット色で異なったサイズがあります。 16ビット色のビットマップのためのオプションは以下の通りです、

定義記述
BG_BMP16_128x12816 ビット色 128x128
BG_BMP16_256x25616 ビット色 256x256
BG_BMP16_512x25616 ビット色 512x256
BG_BMP16_512x51216 ビット色 512x512

このサイズをバックグラウンドのための制御レジスタに割り当てなければなりません。 私達はモード5を使用します。このモードではバックグラウンド2、3だけが拡張回転バックグラウンドです。 バックグラウンド2のビットマップサイズを設定するために、私達は以下のコードを使用します。

  videoSetMode(MODE_5_2D | DISPLAY_BG2_ACTIVE);
  vramSetBankA(VRAM_A_MAIN_BG_0x6000000);
  BG2_CR = BG_BMP16_256x256;

  // Set translations
  BG2_XDX = scale_settings[scale];
  BG2_XDY = 0;
  BG2_YDX = 0;
  BG2_YDY = scale_settings[scale];
  BG2_CY = 0;
  BG2_CX = 0;

In this case a 16 bit bitmap is used so we can write to it using a similar method to the way we wrote to the framebuffer. The size we are using is 256x256 which is larger than the DS screen (which is 256x192) so we can demonstrate scrolling around an image.

16ビット色の 256x256 イメージを格納するために 128 キロバイトを必要とします。 私達はメモリーアドレス 0x06000000 へこのサイズのイメージを表示することができる VRAM をマップする必要があります。 これにはメモリサイズが 128 キロバイトである VRAM バンク A-D が使用できます。 私達のデモでは VRAM_A を使用します。

  videoSetMode(MODE_5_2D | DISPLAY_BG2_ACTIVE);
  vramSetBankA(VRAM_A_MAIN_BG_0x6000000);
  BG2_CR = BG_BMP16_256x256;

  // Set translations
  BG2_XDX = scale_settings[scale];
  BG2_XDY = 0;
  BG2_YDX = 0;
  BG2_YDY = scale_settings[scale];
  BG2_CY = 0;
  BG2_CX = 0;

With this VRAM バンク mapped,any write to 0x06000000 through to 0x06020000 will be stored in 最初のバンク. This memory area can now be written and which results in it being displayed directly on the screen. 拡張回転バックグランド need a little more setup though to some background control registers to set up scaling, etc.

スケーリング

Two registers per background control hardware scaling. They are BGx_XDX and BGx_YDY (replace 'x' with the number of the background you are using). These are 16 bit registers that hold a scaling factor for the X and Y direction respectively. Bits 0-7 of the register control fractional scaling (ie. numbers less than 1) and bits 8-15 control factors 1 and greater.

These registers must be set for an image to display properly in 拡張回転バックグランド. The default value of '0' will scale the image down to nothing. To set the scale factor to use a scaling factor of 1 use:

  BG2_XDX = 1<<8;
  BG2_YDY = 1<<8;;

Remember that bits 8-15 control factors 1 and greater so 1 shifted left 8 times results in a scaling factor of 1. The demo uses a lookup table of scaling factors to allow the user to select predeterimined scales:

  videoSetMode(MODE_5_2D | DISPLAY_BG2_ACTIVE);
  vramSetBankA(VRAM_A_MAIN_BG_0x6000000);
  BG2_CR = BG_BMP16_256x256;

  // Set translations
  BG2_XDX = scale_settings[scale];
  BG2_XDY = 0;
  BG2_YDX = 0;
  BG2_YDY = scale_settings[scale];
  BG2_CY = 0;
  BG2_CX = 0;

回転と剪断

The registers for scaling are actually a little more complicated. There are four registers that together control scaling, rotating and shearing. They are BGx_XDX, BGx_XDY, BGx_YDX and BGx_YDY. Together these four registers define a matrix that is used for transforming pixels. I won't be going into detail on this in this tutorial but to enable no scaling, shearing and rotating, the scaling registers need to be set as mentioned previously and the other two set to zero:

  videoSetMode(MODE_5_2D | DISPLAY_BG2_ACTIVE);
  vramSetBankA(VRAM_A_MAIN_BG_0x6000000);
  BG2_CR = BG_BMP16_256x256;

  // Set translations
  BG2_XDX = 1<<8;
  BG2_XDY = 0;
  BG2_YDX = 0;
  BG2_YDY = 1<<8;
  BG2_CY = 0;
  BG2_CX = 0;

Failing to set these correctly will result in your background not displaying correctly. For details on the matrix as it relates to the Gameboy Advance, see this GBA tutorial on the affine transformation matrix.

トランスレート/スクロール

Two final registers need to be set. These control where the upper left origin of the screen maps to on the target bitmap. They are BGx_CX and BGx_CY. By manipulating these registers you can immediately scroll the image displayed without any need to copy memory around. Our demo sets these to zero initially to display the top left of the bitmap:

  videoSetMode(MODE_5_2D | DISPLAY_BG2_ACTIVE);
  vramSetBankA(VRAM_A_MAIN_BG_0x6000000);
  BG2_CR = BG_BMP16_256x256;

  // Set translations
  BG2_XDX = scale_settings[scale];
  BG2_XDY = 0;
  BG2_YDX = 0;
  BG2_YDY = scale_settings[scale];
  BG2_CY = 0;
  BG2_CX = 0;

ビットマップの描画

Once the VRAM is mapped and the background registers for translation, shearing, etc are set then data can be drawn by setting the pixels to 16bit color values. You'll recall a function from the framebuffer tutorial to draw a shape similar to this:

void draw_shape(int x, int y, uint16* buffer)
{
  buffer += y * SCREEN_WIDTH + x;
  for(int i = 0; i < shape_height; ++i) {
    uint16* line = buffer + (SCREEN_WIDTH * i);
    for(int j = 0; j < shape_width; ++j) {
      *line++ = RGB15(31, 0, 0);
    }
  }
}

This code can be modified easily to draw to the extended rotation bitmap. Firstly we no longer use SCREEN_WIDTH and SCREEN_HEIGHT. Instead the bitmap we are using is 256x256 so that will need to be changed. The other factor with extended rotation backgrounds is the 16-bit color data must have bit 15 set. Without it nothing will display. So the new function becomes:

void draw_shape(int x, int y, uint16* buffer)
{
  buffer += y * 256 + x;
  for(int i = 0; i < shape_height; ++i) {
    uint16* line = buffer + (256 * i);
    for(int j = 0; j < shape_width; ++j) {
      *line++ = RGB15(31, 0, 0) | BIT(15);
    }
  }
}

JPEG イメージ

This demo program will display JPEG images. To do the JPEG decoding I used a GBA JPEG decoder library written by Burton Radons, and modified to compile on the DS by 'headspin'. The code for this library is in gba-jpeg.h, gba-jpeg-decode.h, and gba-jpeg-decode.c. The archive can be downloaded from headspins site.

The basic use is to include the header files for the library and call 'JPEG_DecompressImage' to decompress the JPEG data to an area of memory:

// Decode the jpeg file with the given name to the VRAM location
// specified.  The height and width are the height and width of the
// output bitmap.
void BltImage(char* name, u16* vram, int output_width, int output_height)
{
  WAIT_CR &= ~0x80;
  GBFS_FILE const* gbfs_file = 
    find_first_gbfs_file((void*)0x08000000);
  uint8* image = (uint8*)gbfs_get_obj(gbfs_file, 
				      name, 
				      0);
  JPEG_DecompressImage(image, vram, output_width, output_height);

  WAIT_CR |= 0x80;
}

イメージデータ

The image data is stored in a GBFS file archive in almost exactly the same way as the sound data was handled in the filesystem tutorial. This demo program uses four simple images:

ImageSizeDescription
duddie.jpg368x656Thursday October Christian the second, grandson of Fletcher Christian who mutineered against William Bligh on HMAV Bounty. Also my great, great, great, great, Grandfather, born October 1820 on Pitcairn Island.
pitcairn_leaf.jpg255x314A traditional Pitcairn Island leaf painting.
bell.jpg216x216The 'Pitcairn Bell' on Pitcairn Island.
brave_bird.jpg254x191My flatmates cat Snowy, eyeing up her bird, Loki.

These images are stored in a GBFS archive and when the user selects the image to be displayed it is copied from the GBA flash cartridge to VRAM:

// Decode the jpeg file with the given name to the VRAM location
// specified.  The height and width are the height and width of the
// output bitmap.
void BltImage(char* name, u16* vram, int output_width, int output_height)
{
  WAIT_CR &= ~0x80;
  GBFS_FILE const* gbfs_file = 
    find_first_gbfs_file((void*)0x08000000);
  uint8* image = (uint8*)gbfs_get_obj(gbfs_file, 
				      name, 
				      0);
  JPEG_DecompressImage(image, vram, output_width, output_height);

  WAIT_CR |= 0x80;
}

int main() 
{
    [...]
    if(keysDown() & KEY_SELECT) {
      current_image++;
      if(current_image >= image_count)
	current_image = 0;
      memset(BG_GFX, 0, 256*256*2);
      BltImage(images[current_image], BG_GFX, 256, 256);
      GetImageSize(images[current_image], &width, &height);
    }
    [...]
}

Notice the BG_GFX define that is used. This is defined to be memory location 0x06000000, which is the memory location that the hardware expects background bitmap image data to be for the main screen. Remember that we mapped VRAM_A to be located at this address. The size and width is set to the bitmaps 256x256 size.

イメージのスクロール

Scrolling the image in the demo is done using the arrow keys. Pressing these keys causes an internal record of the current X and Y origin to be changed. This value is then left shifted 8 times and stored in the scrolling registers BG2_CY and BG2_CX. The left shift is required as you'll recall from the register section that bits 0-7 are fractional values while bits 8-15 are the whole number values:

static int scrolly = 0;
static int scrollx = 0;

int main() 
{
    [...]
    if(keysHeld() & KEY_DOWN) {
      scrolly++;
      BG2_CY = (scrolly<<8);
    }
    if(keysHeld() & KEY_UP) {
      scrolly--;
      BG2_CY = (scrolly<<8);
    }
    if(keysHeld() & KEY_RIGHT) {
      scrollx++;
      BG2_CX = (scrollx<<8);
    }
    if(keysHeld() & KEY_LEFT) {
      scrollx--;
      BG2_CX = (scrollx<<8);
    }
    [...]
}

As soon as these registers are updated the new origin is set in the hardware and the new image portion will display. No memory copying required.

イメージのスケーリング

Pressing the A and B keys will change the scaling settings. I've hardcoded a number of scaling settings and A and B move up and down in this array:

static int scale_settings[] = {
  1 << 4,
  1 << 5,
  1 << 6,
  1 << 7,
  1 << 8,
  2 << 8,
  3 << 8,
  4 << 8,
  5 << 8,
  6 << 8,
  7 << 8,
  8 << 8,
  9 << 8,
  10 << 8
};

int main() 
{
    [...]
    if(keysDown() & KEY_A) { 
      scale++;
      if(scale >= scale_count)
	scale = scale_count - 1;
      BG2_XDX = scale_settings[scale];
      BG2_YDY = scale_settings[scale];
    }
    if(keysDown() & KEY_B) { 
      scale--;
      if(scale < 1)
	scale = 1;
      BG2_XDX = scale_settings[scale];
      BG2_YDY = scale_settings[scale];
    }
    [...]
}

Like the scroll registers, assigning to these hardware registers will immediately scale the image. The scaling algortihm appears to be quite simple but it's done in hardware and is very fast.

2番目のスクリーン

The second screen is used in this demo to display some image information using the libnds console routines:

void on_irq() 
{	
  if(IF & IRQ_VBLANK) {
    // Handle vertical blank interrupt
    consoleClear();
    consolePrintf("Width: %d\n", width);
    consolePrintf("Height: %d\n", height);
    consolePrintf("scrolly: %d\n", scrolly);
    consolePrintf("scrollx: %d\n", scrollx);
    VBLANK_INTR_WAIT_FLAGS |= IRQ_VBLANK;
    IF |= IRQ_VBLANK;
  }
  else {
    // Ignore all other interrupts
    IF = IF;
  }
}

The console is set up to use the second screen via the following code:

  // Setup console screen to display information about the jpeg.
  videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE);
  vramSetBankC(VRAM_C_SUB_BG);
  SUB_BG0_CR = BG_MAP_BASE(31);
  BG_PALETTE_SUB[255] = RGB15(31,31,31);
  consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(31), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);

We've used console code like this before but it is changed slightly to use the 'SUB' defines and functions to go to the second screen. A future tutorial will go into detail about the second screen.

アプリケーションの使用方法

Use the arrow keys to scroll the image. Press A and B to scale it. Press select to change to one of the other images.

デモのビルド

The complete example program is 'mode5_scrolling'. The ARM9 code is in arm9_main.cpp. The ARM7 code is in arm7_main.cpp. A Makefile file is supplied to build everything.

The complete source code, including images, is supplied in mode5_scrolling.zip and you can download the mode5_scrolling.nds and mode5_scrolling.nds.gba files for running on the emulators or hardware. DSEmu 0.4.5 and above will run this demo but the hardware scaling does not work quite right. All other aspects of the demo work fine. DSEmuで実行したスクリーンショットです。

mode5 scrolling demo in
DSEmu

結論

This tutorial has introduced mode 5 and the extended rotation background. There is much more to cover on backgrounds and graphics in general. I hope to have the upcoming tutorials focusing more on this area, including tiles and sprites.

Try playing around with this demo, adding new images, experimenting with the rotation, scaling, shearing, etc.