目的
前回以下の記事に於いてx86上で三角形を描画する検証をしました。
今回は最低限の変更で任天堂GBAに移植します。
コード
arm-none-eabi-as header.s -o header.o
arm-none-eabi-gcc -c main.c -o main.o -nostdlib -ffreestanding
arm-none-eabi-gcc header.o main.o -T link.ld -o out.elf -nostdlib -lgcc
arm-none-eabi-objcopy -O binary out.elf out.gba
header.s
header.s
.arm
.section .text
.global _start
.global ResetHandler
.org 0x00000000
_start:
b ResetHandler
/* 商標を 0x00 で埋める (0x0004 - 0x009F = 156 bytes) */
.space 156, 0x00
/* --- ゲームに関する情報(0x00A0〜0x00BF) --- */
.org 0x000000A0
.ascii "GBADEMO " @ 名前 12文字
.byte 0x00,0x00,0x00,0x00 @ ゲームコード 4バイト
.byte 0x00,0x00 @ メーカーコード 2バイト
.byte 0x96 @ 固定値
.byte 0x00 @ メインユニットコード
.byte 0x00 @ 機器の種類
.space 7 @ 予約領域
.byte 0x00,0x00 @ 補完チェック・マスクROMバージョン
.space 2 @ 残り予約領域
.align 2
ResetHandler:
ldr sp, =0x03007F00
bl main @ Cへ 跳ぶ
InfiniteLoop:
b InfiniteLoop
link.ld
link.ld
ENTRY(_start)
SECTIONS
{
. = 0x08000000;
.text :
{
*(.text)
*(.rodata)
}
.data :
{
*(.data)
}
.bss :
{
*(.bss)
*(COMMON)
}
}
main.c
main.c
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
// x86->GBAh変更点1 VRAMアドレス、解像度
//#define VGA_MEM 0xA0000
//#define SCREEN_W 320
//#define SCREEN_H 200
#define VRAM ((volatile u16*)0x06000000)
#define SCREEN_W 240
#define SCREEN_H 160
#define REG_DISPCNT (*(volatile u16*)0x04000000) // 画面制御レジスタ
/* 点 */
typedef struct {
int x, y;
} Point;
/* 三角形 */
typedef struct {
Point p0, p1, p2;
// x86->GBAh変更点2 色にビット数
//u8 color;
u16 color;
} Triangle;
/* (x,y)座標にcで指定した色の点を打つ
メモリは線形。(X,Y)の位置に対応するアドレスを求める公式 =
VRAM開始アドレス + Y*画面の解像度横 + X
*/
void put_pixel(int x, int y, u16 c)
{
// 描画範囲外(負値は unsigned 変換で弾く)
if ((unsigned)x >= SCREEN_W || (unsigned)y >= SCREEN_H)
return;
// x86->GBA変更点3 VRAMアドレス、解像度
//((volatile u8 *)VGA_MEM)[y * SCREEN_W + x] = c;
VRAM[y * SCREEN_W + x] = c;
}
/* (x0, y)から (x1, y)まで横1本の線を引く */
void draw_hline(int x0, int x1, int y, u16 c)
{
if ((unsigned)y >= SCREEN_H)return; //画面外なら即終了
if (x0 > x1) { int t = x0; x0 = x1; x1 = t; }//左 → 右になるよう入れ替え
for (int x = x0; x <= x1; x++)
put_pixel(x, y, c);
}
/* 三角形塗りつぶし(底辺を揃えない・汎用版) */
void fill_triangle(const Triangle *t)
{
// 元の頂点
int x0 = t->p0.x, y0 = t->p0.y;
int x1 = t->p1.x, y1 = t->p1.y;
int x2 = t->p2.x, y2 = t->p2.y;
// 上下ソート用
// top
// *
// / \
// / \
// / \
// *-------* 実際は斜め
// mid bot
int top_x, top_y;
int mid_x, mid_y;
int bot_x, bot_y;
// 上(Yが最小)
if (y0 <= y1 && y0 <= y2) { top_x = x0; top_y = y0; }
else if (y1 <= y0 && y1 <= y2) { top_x = x1; top_y = y1; }
else { top_x = x2; top_y = y2; }
// 下(Yが最大)
if (y0 >= y1 && y0 >= y2) { bot_x = x0; bot_y = y0; }
else if (y1 >= y0 && y1 >= y2) { bot_x = x1; bot_y = y1; }
else { bot_x = x2; bot_y = y2; }
// 残りが真ん中
if ((top_x == x0 && bot_x == x1) || (top_x == x1 && bot_x == x0))
{ mid_x = x2; mid_y = y2; }
else if ((top_x == x0 && bot_x == x2) || (top_x == x2 && bot_x == x0))
{ mid_x = x1; mid_y = y1; }
else
{ mid_x = x0; mid_y = y0; }
// 分割描画に必要な差分(Yの差 ΔY)
// 上半分と下半分に分けて描画する
int dy_top_mid = mid_y - top_y;
int dy_top_bot = bot_y - top_y;
int dy_mid_bot = bot_y - mid_y;
// 上部(top ~ mid)
// 上現在のY座標 yにおいて、三角形の左端 xaと右端 xbを計算して、その間を横線で塗る
for (int y = top_y; y <= mid_y; y++) { // 行ループ(Y方向)
if ((unsigned)y >= SCREEN_H) continue; // 画面端を越えたら何もしない
int xa = top_x;
int xb = top_x;
// 今のY位置における、top→midの辺のX座標を求める
if (dy_top_mid != 0) // 0除算防止
// 現在の行が全体の何割進んだか(相似比)を求め、
// その割合だけXを加算(或いは減算)して位置を補間する
xa += (mid_x - top_x) * (y - top_y) / dy_top_mid; // 短辺
if (dy_top_bot != 0)
xb += (bot_x - top_x) * (y - top_y) / dy_top_bot; // 長編
if (xa > xb) { int tmp = xa; xa = xb; xb = tmp; } // 常に xa ≤ xbにする (xa→xbへ描写する)
draw_hline(xa, xb, y, t->color);
}
// 下部(mid ~ bot)
for (int y = mid_y + 1; y <= bot_y; y++) {
if ((unsigned)y >= SCREEN_H) continue;
int xa = mid_x;
int xb = top_x;
if (dy_mid_bot != 0)
xa += (bot_x - mid_x) * (y - mid_y) / dy_mid_bot;
if (dy_top_bot != 0)
xb += (bot_x - top_x) * (y - top_y) / dy_top_bot;
if (xa > xb) { int tmp = xa; xa = xb; xb = tmp; }
draw_hline(xa, xb, y, t->color);
}
}
void main(void)
{
// Mode3 + BG2
REG_DISPCNT = 0x0403;
Triangle tri = {
.p0 = {70, 170},
.p1 = {180, 40},
.p2 = {260, 140},
.color = 12
};
fill_triangle(&tri);
while (1);
}