run gb_emu in webasm

https://web.dev/drawing-to-canvas-in-emscripten/ 有了前面這個構想,現在的webasm不像2018那麼舊了,剛剛看了怎麼console 部分上面還有一塊區域,原來wasm 也整合了sdl2,剛好我們的gb_emu 也是用sdl2引擎那麼我們能不能直接移植上去呢我們來看一下。

cpp
#include <SDL2/SDL.h>
#include <SDL2/SDL2_gfxPrimitives.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

SDL_Window *window;
SDL_Renderer *renderer;

SDL_Point center = {.x = 100, .y = 100};
const int radius = 100;

void redraw() {
  SDL_SetRenderDrawColor(renderer, /* RGBA: black */ 0x00, 0x00, 0x00, 0xFF);
  SDL_RenderClear(renderer);
  filledCircleRGBA(renderer, center.x, center.y, radius,
                   /* RGBA: green */ 0x00, 0x80, 0x00, 0xFF);
  SDL_RenderPresent(renderer);
}

uint32_t ticksForNextKeyDown = 0;

bool handle_events() {
  SDL_Event event;
  SDL_PollEvent(&event);
  if (event.type == SDL_QUIT) {
    return false;
  }
  if (event.type == SDL_KEYDOWN) {
    uint32_t ticksNow = SDL_GetTicks();
    if (SDL_TICKS_PASSED(ticksNow, ticksForNextKeyDown)) {
      // Throttle keydown events for 10ms.
      ticksForNextKeyDown = ticksNow + 10;
      switch (event.key.keysym.sym) {
        case SDLK_UP:
          center.y -= 1;
          break;
        case SDLK_DOWN:
          center.y += 1;
          break;
        case SDLK_RIGHT:
          center.x += 1;
          break;
        case SDLK_LEFT:
          center.x -= 1;
          break;
      }
      redraw();
    }
  }
  return true;
}

void run_main_loop() {
#ifdef __EMSCRIPTEN__
  emscripten_set_main_loop([]() { handle_events(); }, 0, true);
#else
  while (handle_events())
    ;
#endif
}

int main() {
  SDL_Init(SDL_INIT_VIDEO);

  SDL_CreateWindowAndRenderer(300, 300, 0, &window, &renderer);

  redraw();
  run_main_loop();

  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);

  SDL_Quit();
}

立刻有一個example 來達成用鍵盤來操控html上的圖形

bash
emcc --bind foo.cpp -o foo.html -s USE_SDL=2 -s USE_SDL_GFX=2

那麼來移植

bash
 emcc --bind main.c ./lib/emu.c ./lib/cart.c -I ./include -o foo.html -s USE_SDL=2 -s USE_SDL_GFX=2 --preload-file LG.gb 
bash
 emcc --bind main.c ./lib/cpu_fetch.c ./lib/interrupts.c ./lib/ppu_pipeline.c ./lib/timer.c ./lib/dma.c ./lib/bus.c ./lib/cart.c ./lib/cpu_proc.c ./lib/emu.c ./lib/io.c ./lib/ppu_sm.c ./lib/ui.c ./lib/cpu_util.c ./lib/gamepad.c ./lib/lcd.c ./lib/ram.c ./lib/cpu.c ./lib/dbg.c ./lib/instructions.c ./lib/ppu.c ./lib/stack.c -I ./include  -o foo.html -s USE_SDL=2 -s USE_SDL_GFX=2 --preload-file LG.gb 

這邊可以看到可以load卡帶進前端了。

讀取inst

稍微改動了大部分結構,有空再補,到這邊我們直接就gb_emu run 在 web上了,不過記憶體有點洩漏,可能要花更多一點時間排除

c
emcc --bind main.c ./lib/cpu_fetch.c ./lib/interrupts.c ./lib/ppu_pipeline.c ./lib/timer.c ./lib/dma.c ./lib/bus.c ./lib/cart.c ./lib/cpu_proc.c ./lib/emu.c ./lib/io.c ./lib/ppu_sm.c ./lib/ui.c ./lib/cpu_util.c ./lib/gamepad.c ./lib/lcd.c ./lib/ram.c ./lib/cpu.c ./lib/dbg.c ./lib/instructions.c ./lib/ppu.c ./lib/stack.c -I ./include  -o foo.html -s USE_SDL=2 -s USE_SDL_GFX=2 --preload-file LG.gb   -sALLOW_MEMORY_GROWTH -s ASYNCIFY
c
#include "../include/common.h"

// emcc --bind main.c ./lib/emu.c ./lib/cart.c ./lib/cart.c ./lib/cpu_proc.c ./lib/emu.c ./lib/io.c ./lib/ppu_sm.c ./lib/ui.c ./lib/cpu_util.c ./lib/gamepad.c ./lib/lcd.c ./lib/ram.c ./lib/cpu.c ./lib/dbg.c ./lib/instructions.c ./lib/ppu.c ./lib/stack.c -I ./include   -o foo.html -s USE_SDL=2 -s USE_SDL_GFX=2 --preload-file LG.gb 
//  emcc --bind main.c ./lib/emu.c ./lib/cart.c ./lib/cart.c ./lib/cpu_proc.c ./lib/emu.c ./lib/io.c ./lib/ppu_sm.c ./lib/ui.c ./lib/cpu_util.c ./lib/gamepad.c ./lib/lcd.c ./lib/ram.c ./lib/cpu.c ./lib/dbg.c ./lib/instructions.c ./lib/ppu.c ./lib/stack.c -I ./include   -o foo.html -s USE_SDL=2 -s USE_SDL_GFX=2 --preload-file LG.gb 
// TODO Add Windows Alternative...

/*
  Emu components:

  |Cart|
  |CPU|
  |Address Bus|
  |PPU|
  |Timer|

*/

static emu_context ctx;

emu_context *emu_get_context()
{
    return &ctx;
}

void *cpu_run(void *p)
{

    timer_init();
    cpu_init();
    ppu_init();

    ctx.running = true;
    ctx.paused = false;
    ctx.too = false;
    ctx.ticks = 0;
    //   cpu_set_flags(ctx,  ctx.regs.a, 1, (val & 0x0F) == 0x0F, -1);
    // fp = fopen("regist.txt", "w");
    while (ctx.running)
    {

        if (ctx.too)
            break;

        if (ctx.paused)
        {
            delay(10);
            continue;
        }
        if (!cpu_step())
        {

            printf("CPU Stopped\n");
            return 0;
        }
        // if (count2 >= 2000000 && count2 <= 4000000)
        //     continue;
    }

    return 0;
}

int emu_run()
{
    // if (argc < 2)
    // {
    //     printf("Usage: emu <rom_file>\n");
    //     return -1;
    // }
    if (!cart_load("LG.gb"))
    // if (!cart_load(argv[1]))
    {
        printf("Failed to load ROM file: %s\n", "LG.gb");
        return -2;
    }

    printf("Cart loaded..\n");

    ui_init();

    timer_init();
    cpu_init();
    ppu_init();

    ctx.running = true;
    ctx.paused = false;
    ctx.too = false;
    ctx.ticks = 0;
    u32 prev_frame = 0;
    int count2 = 0;
    //   cpu_set_flags(ctx,  ctx.regs.a, 1, (val & 0x0F) == 0x0F, -1);
    // fp = fopen("regist.txt", "w");

    while (ctx.running)
    {

        if (ctx.too)
            break;

        if (ctx.paused)
        {
            delay(10);
            continue;
        }
        if (!cpu_step())
        {

            printf("CPU Stopped\n");
            return 0;
        }
        count2++;
        if (count2 >=1000)
        {
        // usleep(100);
        ui_handle_events();
//  printf("CPU Stopped\n");
        if (prev_frame != ppu_get_context()->current_frame)
        {
            //   printf("CPU Stopped\n");
            ui_update();
        }

        prev_frame = ppu_get_context()->current_frame;
        count2 =0;}
            // continue;
    }
    // pthread_t t1;

    // if (pthread_create(&t1, NULL, cpu_run, NULL))
    // {
    //     fprintf(stderr, "FAILED TO START MAIN CPU THREAD!\n");
    //     return -1;
    // }

    // u32 prev_frame = 0;

    // while (!ctx.die)
    // {
    //     while (ctx.too)
    //         ;

    //     usleep(100);
    //     ui_handle_events();

    //     if (prev_frame != ppu_get_context()->current_frame)
    //     {
    //         ui_update();
    //     }

    //     prev_frame = ppu_get_context()->current_frame;
    //     // printf("%d\n",prev_frame);
    // }

    return 0;
}

void emu_cycles(int cpu_cycles)
{

    for (int i = 0; i < cpu_cycles; i++)
    {
        for (int n = 0; n < 4; n++)
        {
            ctx.ticks++;
            timer_tick();
            ppu_tick();
        }

        dma_tick();
    }
}

主要還是pthread_create 改成固定呼叫,javascrpit 是單執行續,似乎有出套件可以變多執行緒