0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

任天堂GBA三角形描画

0
Posted at

目的

前回以下の記事に於いてx86上で三角形を描画する検証をしました。
今回は最低限の変更で任天堂GBAに移植します。

x86ベアメタル三角形描画

コード

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);
}

動作確認

エミュレータ

截图 2026-07-14 21-15-42

実機

かなり見づらいですが薄っすらと三角形が見えます。
3cfb3a4a108018

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?