abse/gameboy-advance

A tscircuit package created by abse.

Version
0.0.45
License
unset
Stars
0

firmware/game-console/doom_port.c

#include "board.h"
#include "dirty_regions.h"
#include "doomgeneric.h"
#include "doomkeys.h"
#include "doomstat.h"
#include "g_game.h"
#include "hardware/sync.h"
#include "hardware/watchdog.h"
#include "i_video.h"
#include "m_controls.h"
#include "m_menu.h"
#include "p_mobj.h"
#include "pico/multicore.h"
#include "pico/stdlib.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static uint16_t previous;
static unsigned char held_keys[12];
static uint16_t pending;
static uint16_t snapshot;
static uint16_t remote_buttons;
static uint32_t remote_until;
static unsigned frames;
static int last_submitted_tic = -1;
enum { FRAME_FREE, FRAME_QUEUED, FRAME_DISPLAYING, FRAME_WRITING };
struct queued_frame {
    uint8_t pixels[320 * 107] __attribute__((aligned(4)));
    uint32_t palette[256];
    struct dirty_region regions[MAX_DIRTY_REGIONS];
    unsigned region_count;
    uint32_t sequence;
    volatile uint32_t state;
};
static struct queued_frame queue[3];
static int last_submitted_slot = -1;
static uint32_t next_sequence;
static bool display_started;
static volatile uint32_t display_frames, display_us_total;
static uint32_t last_submit_us;
static bool force_redraw = true;
static int adaptive_scan = 1;
static bool field_valid[3];
static unsigned producer_field;

static void display_core(void) {
    for (;;) {
        int slot = -1;
        for (int i = 0; i < 3; i++)
            if (__atomic_load_n(&queue[i].state, __ATOMIC_ACQUIRE) == FRAME_QUEUED)
                if (slot < 0 || (int32_t)(queue[i].sequence - queue[slot].sequence) < 0)
                    slot = i;
        if (slot < 0) {
            __wfe();
            continue;
        }
        struct queued_frame *f = &queue[slot];
        __atomic_store_n(&f->state, FRAME_DISPLAYING, __ATOMIC_RELEASE);
        uint32_t start = time_us_32();
        for(unsigned i=0;i<f->region_count;i++) {
            struct dirty_region r=f->regions[i];
            unsigned w=lcd_large?480:320;
            unsigned x0=((r.x0*w+319)/320)&~3u,x1=(((r.x1*w+319)/320)+3)&~3u;
            lcd_indexed_scanline(f->pixels+(r.y0/3)*320,f->palette,r.y0,x0,x1);
        }
        display_us_total += time_us_32() - start;
        if (f->region_count)
            __atomic_fetch_add(&display_frames, 1, __ATOMIC_RELAXED);
        __atomic_store_n(&f->state, FRAME_FREE, __ATOMIC_RELEASE);
        __sev();
    }
}
static void display_drain(void) {
    if (!display_started)
        return;
    while (__atomic_load_n(&queue[0].state, __ATOMIC_ACQUIRE) != FRAME_FREE ||
           __atomic_load_n(&queue[1].state, __ATOMIC_ACQUIRE) != FRAME_FREE ||
           __atomic_load_n(&queue[2].state, __ATOMIC_ACQUIRE) != FRAME_FREE)
        tight_loop_contents();
}
static void show_colors(void) {
    display_drain();
    field_valid[0]=field_valid[1]=field_valid[2]=false;
    lcd_calibration();
    printf("COLORS rgb=RED,GREEN,BLUE cmy=YELLOW,CYAN,MAGENTA spi_hz=%lu\n",
           (unsigned long)lcd_get_clock());
    while (buttons_read() & ((1 << 4) | (1 << 9)))
        sleep_ms(10);
    while (!(buttons_read() & ((1 << 4) | (1 << 9)))) {
        int c = getchar_timeout_us(1000);
        if (c == 'r')
            break;
    }
    while (buttons_read() & ((1 << 4) | (1 << 9)))
        sleep_ms(10);
    lcd_rect(0, 0, 480, 320, 0, 0, 0);
    force_redraw = true;
    lcd_apply_game_mode();
    puts("COLORS_END");
}
void DG_Init(void) {
    previous = buttons_read();
    lcd_rect(0, 0, 480, 320, 0, 0, 0);
    lcd_apply_game_mode();
    multicore_launch_core1(display_core);
    display_started = 1;
}
static void console_poll(void);
void DG_DrawFrame(void) {
    console_poll();
    static uint32_t rgb[256];
    if (palette_changed) {
        for (int i = 0; i < 256; i++)
            rgb[i] = (colors[i].r << 16) | (colors[i].g << 8) | colors[i].b;
        palette_changed = false;
    }
    int slot=-1;
    if((gametic!=last_submitted_tic || force_redraw) &&
       __atomic_load_n(&queue[producer_field].state,__ATOMIC_ACQUIRE)==FRAME_FREE)slot=producer_field;
    if(slot>=0) {
        struct queued_frame *f=&queue[slot];
        __atomic_store_n(&f->state,FRAME_WRITING,__ATOMIC_RELEASE);
        uint32_t start=time_us_32();
        bool full=force_redraw || !field_valid[slot] || memcmp(f->palette,rgb,sizeof(rgb));
        unsigned height=lcd_large?320:240;
        f->region_count=0;
        for(unsigned y=slot;y<height;y+=3) {
            uint8_t *old=f->pixels+(y/3)*320,*src=DG_ScreenBuffer+(y*200/height)*320;
            unsigned lo=0,hi=320;
            if(!full){while(lo<hi&&old[lo]==src[lo])lo++;while(hi>lo&&old[hi-1]==src[hi-1])hi--;}
            if(lo<hi)f->regions[f->region_count++]=(struct dirty_region){lo,y,hi,y+1};
            memcpy(old,src,320);
        }
        memcpy(f->palette,rgb,sizeof(rgb));
        field_valid[slot]=true;producer_field=(producer_field+1)%3;
        f->sequence=next_sequence++;
        force_redraw=false;last_submitted_tic=gametic;last_submitted_slot=slot;
        last_submit_us=time_us_32()-start;
        __atomic_store_n(&f->state,FRAME_QUEUED,__ATOMIC_RELEASE);__sev();
    }
    frames++;
    static uint32_t last, last_frames, last_display;
    uint32_t now = to_ms_since_boot(get_absolute_time());
    if (now - last > 5000) {
        uint32_t elapsed = now - last;
        uint32_t shown = __atomic_load_n(&display_frames, __ATOMIC_RELAXED);
        printf("PERF render_fps_x100=%lu display_fps_x100=%lu render_frames=%u displayed=%lu "
               "copy_us=%lu spi_hz=%lu gametic=%d map=%d buttons=%03x\n",
               (unsigned long)((frames - last_frames) * 100000 / elapsed),
               (unsigned long)((shown - last_display) * 100000 / elapsed), frames,
               (unsigned long)shown, (unsigned long)last_submit_us, (unsigned long)lcd_get_clock(),
               gametic, gamemap, buttons_read());
        last = now;
        last_frames = frames;
        last_display = shown;
    }
}
void DG_SleepMs(uint32_t ms) { sleep_ms(ms); }
uint32_t DG_GetTicksMs(void) { return to_ms_since_boot(get_absolute_time()); }
void DG_SetWindowTitle(const char *title) { printf("TITLE %s\n", title); }
int DG_GetKey(int *pressed, unsigned char *key) {
    if (!pending) {
        snapshot = buttons_read() | (DG_GetTicksMs() < remote_until ? remote_buttons : 0);
        pending = snapshot ^ previous;
        previous = snapshot;
    }
    if (!pending)
        return 0;
    unsigned i = __builtin_ctz(pending);
    pending &= ~(1u << i);
    *pressed = !!(snapshot & (1u << i));
    static const unsigned char keys[] = {KEY_UPARROW, KEY_DOWNARROW, KEY_LEFTARROW, KEY_RIGHTARROW,
                                         KEY_FIRE,    KEY_USE,       KEY_RSHIFT,    'w',
                                         KEY_TAB,     KEY_ESCAPE,    ',',           '.'};
    *key = keys[i];
    if (*pressed) {
        if (menuactive && i == 4)
            *key = KEY_ENTER;
        if (menuactive && i == 5)
            *key = KEY_BACKSPACE;
        held_keys[i] = *key;
    } else if (held_keys[i])
        *key = held_keys[i];
    return 1;
}
void doom_run(const char *path) {
    key_nextweapon = 'w';
    printf("LAUNCH %s\n", path);
    lcd_rect(0, 0, 480, 320, 8, 16, 28);
    lcd_text(24, 100, "LOADING DOOM", 4);
    char *args[] = {"doom",
                    "-iwad",
                    (char *)path,
                    "-config",
                    "/games/doom/default.cfg",
                    "-extraconfig",
                    "/games/doom/extra.cfg",
                    "-savedir",
                    "/games/doom",
                    "-nogui",
                    "-nosound",
                    "-mb",
                    "6",
                    NULL};
    doomgeneric_Create(13, args);
    key_menu_confirm = KEY_ENTER;
    key_menu_abort = KEY_BACKSPACE;
    M_StartControlPanel();
    while (1) {
        doomgeneric_Tick();
    }
}

void gba_fatal(const char *msg) {
    printf("FATAL %s\n", msg);
    display_drain();
    lcd_rect(0, 0, 480, 320, 16, 0, 0);
    lcd_text(20, 20, "DOOM ERROR", 4);
    char line[38];
    for (int y = 90; *msg && y < 260; y += 24) {
        unsigned n = 0;
        while (*msg && n < 36)
            line[n++] = *msg++;
        line[n] = 0;
        for (unsigned j = 0; j < n; j++)
            if (line[j] >= 'a' && line[j] <= 'z')
                line[j] -= 32;
        lcd_text(20, y, line, 2);
    }
    while (1) {
        sleep_ms(100);
        if (buttons_read() & (1 << 9))
            watchdog_reboot(0, 0, 100);
    }
}

static void console_poll(void) {
    static char line[96];
    static unsigned n;
    int c;
    while ((c = getchar_timeout_us(0)) >= 0) {
        if (c == '\n' || c == '\r') {
            line[n] = 0;
            unsigned mask, ms;
            if (sscanf(line, "key %x %u", &mask, &ms) == 2) {
                remote_buttons = mask & 4095;
                remote_until = DG_GetTicksMs() + ms;
                printf("KEY %03x %u\n", mask, ms);
            } else if (!strcmp(line, "colors")) {
                show_colors();
            } else if (sscanf(line, "rate %u", &ms) == 1) {
                if (ms >= 1000000 && ms <= 75000000) {
                    display_drain();
                    lcd_set_clock(ms);
                    force_redraw = true;
                    printf("RATE %lu\n", (unsigned long)lcd_get_clock());
                }
            } else if (!strncmp(line, "mode ", 5)) {
                display_drain();
                lcd_fast_mode = atoi(line + 5) ? 2 : 0;
                if (lcd_fast_mode < 0 || lcd_fast_mode > 2)
                    lcd_fast_mode = 0;
                lcd_apply_game_mode();
                force_redraw = true;
                printf("MODE %d\n", lcd_fast_mode);
            } else if (!strncmp(line, "scan ", 5)) {
                display_drain(); adaptive_scan=1;
                field_valid[0]=field_valid[1]=field_valid[2]=false; force_redraw=true;
            } else if (!strncmp(line, "size ", 5)) {
                display_drain();
                lcd_large = atoi(line + 5) != 0;
                lcd_rect(0,0,480,320,0,0,0);
                field_valid[0]=field_valid[1]=field_valid[2]=false;
                force_redraw = true;
            } else if (!strcmp(line, "assets")) {
                extern void verify_asset_caches(void);
                verify_asset_caches();
            } else if (!strcmp(line, "lcdregs")) {
                display_drain();
                lcd_read_diagnostics();
            } else if (!strcmp(line, "shot")) {
                printf("FRAME 320 200 4\n");
                fflush(stdout);
                for (unsigned i = 0; i < 320 * 200; i++) {
                    struct color c = colors[DG_ScreenBuffer[i]];
                    putchar_raw(c.b);
                    putchar_raw(c.g);
                    putchar_raw(c.r);
                    putchar_raw(0);
                }
                puts("\nFRAME_END");
            } else if (!strcmp(line, "status")) {
                printf("DOOM frames=%u gametic=%d state=%d map=%d menu=%d\n", frames, gametic,
                       gamestate, gamemap, menuactive);
                if (players[consoleplayer].mo)
                    printf("PLAYER x=%ld y=%ld angle=%lu health=%d ammo=%d\n",
                           (long)players[consoleplayer].mo->x, (long)players[consoleplayer].mo->y,
                           (unsigned long)players[consoleplayer].mo->angle,
                           players[consoleplayer].health, players[consoleplayer].ammo[0]);
            } else if (!strcmp(line, "menu")) {
                watchdog_reboot(0, 0, 100);
                while (1)
                    tight_loop_contents();
            }
            n = 0;
        } else if (n < sizeof(line) - 1)
            line[n++] = c;
    }
    static uint32_t since;
    if ((buttons_read() & 0x300) == 0x300) {
        if (!since)
            since = DG_GetTicksMs();
        if (DG_GetTicksMs() - since > 1500) {
            watchdog_reboot(0, 0, 100);
            while (1)
                tight_loop_contents();
        }
    } else
        since = 0;
}

void _exit(int code) {
    printf("EXIT %d\n", code);
    watchdog_reboot(0, 0, 150);
    while (1)
        tight_loop_contents();
}