From 89a99b160df4759fd11d677a60e2f4c2254da705 Mon Sep 17 00:00:00 2001 From: Samuel Walker Date: Sat, 1 Feb 2025 19:05:25 -0700 Subject: [PATCH] Finish ppu, and mbc1 mapping --- include/cart.h | 6 +- include/gamepad.h | 22 ++++ include/lcd.h | 2 +- include/ppu.h | 62 ++++++++- lib/cart.c | 160 ++++++++++++++++++++++- lib/cpu_fetch.c | 2 +- lib/cpu_proc.c | 6 +- lib/gamepad.c | 65 ++++++++++ lib/io.c | 19 +++ lib/ppu.c | 14 ++ lib/ppu_pipeline.c | 271 +++++++++++++++++++++++++++++++++++++++ lib/ppu_sm.c | 95 +++++++++++++- lib/ui.c | 73 +++++++++++ roms/01-read_timing.gb | Bin 0 -> 32768 bytes roms/02-write_timing.gb | Bin 0 -> 32768 bytes roms/03-modify_timing.gb | Bin 0 -> 32768 bytes roms/instr_timing.gb | Bin 0 -> 32768 bytes 17 files changed, 779 insertions(+), 18 deletions(-) create mode 100644 include/gamepad.h create mode 100644 lib/gamepad.c create mode 100644 lib/ppu_pipeline.c create mode 100644 roms/01-read_timing.gb create mode 100644 roms/02-write_timing.gb create mode 100644 roms/03-modify_timing.gb create mode 100644 roms/instr_timing.gb diff --git a/include/cart.h b/include/cart.h index f991e20..88fdfea 100644 --- a/include/cart.h +++ b/include/cart.h @@ -24,4 +24,8 @@ bool cart_load(char *cart); rom_header *get_rom_header(); u8 cart_read(u16 address); -void cart_write(u16 address, u8 value); \ No newline at end of file +void cart_write(u16 address, u8 value); + +bool cart_need_save(); +bool cart_battery_load(); +bool cart_battery_save(); \ No newline at end of file diff --git a/include/gamepad.h b/include/gamepad.h new file mode 100644 index 0000000..8460056 --- /dev/null +++ b/include/gamepad.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +typedef struct { + bool start; + bool select; + bool a; + bool b; + bool up; + bool down; + bool left; + bool right; +} gamepad_state; + +void gamepad_init(); +bool gamepad_button_sel(); +bool gamepad_dir_sel(); +void gamepad_set_sel(u8 value); + +gamepad_state *gamepad_get_state(); +u8 gamepad_get_output(); \ No newline at end of file diff --git a/include/lcd.h b/include/lcd.h index b6c260c..921fa55 100644 --- a/include/lcd.h +++ b/include/lcd.h @@ -41,7 +41,7 @@ lcd_context *lcd_get_context(); #define LCDC_LCD_ENABLE (BIT(lcd_get_context()->lcdc, 7)) #define LCDS_MODE ((lcd_mode)(lcd_get_context()->lcds & 0b11)) -#define LCDS_MODE_SET(mode) { lcd_get_context()->lcds &= ~0b11; lcd_get_context()->lcdc |= mode; } +#define LCDS_MODE_SET(mode) { lcd_get_context()->lcds &= ~0b11; lcd_get_context()->lcds |= mode; } #define LCDS_LYC (BIT(lcd_get_context()->lcds, 2)) #define LCDS_LYC_SET(b) BIT_SET(lcd_get_context()->lcds, 2, b) diff --git a/include/ppu.h b/include/ppu.h index 101949f..aa005de 100644 --- a/include/ppu.h +++ b/include/ppu.h @@ -7,23 +7,73 @@ static const int TICKS_PER_LINE = 456; static const int YRES = 144; static const int XRES = 160; +u32 target_frame_time; + +typedef enum { + FS_TILE, + FS_DATA0, + FS_DATA1, + FS_IDLE, + FS_PUSH +} fetch_state; + +typedef struct _fifo_entry { + struct _fifo_entry *next; + u32 value; +} fifo_entry; + +typedef struct { + fifo_entry *head; + fifo_entry *tail; + u32 size; //32 bit color +} fifo; + +typedef struct { + fetch_state cur_fetch_state; + fifo pixel_fifo; + u8 line_x; + u8 pushed_x; + u8 fetch_x; + u8 bgw_fetch_data[3]; + u8 fetch_entry_data[6]; //oam data + u8 map_y; + u8 map_x; + u8 tile_y; + u8 fifo_x; +} pixel_fifo_context; + typedef struct { u8 y; u8 x; u8 tile; - unsigned f_cgb_pn : 3; - unsigned f_cgb_vram_bank : 1; - unsigned f_pn : 1; - unsigned f_x_flip : 1; - unsigned f_y_flip : 1; - unsigned f_bgp : 1; + u8 f_cgb_pn : 3; + u8 f_cgb_vram_bank : 1; + u8 f_pn : 1; + u8 f_x_flip : 1; + u8 f_y_flip : 1; + u8 f_bgp : 1; } oam_entry; +typedef struct _oam_line_entry { + oam_entry entry; + struct _oam_line_entry *next; +} oam_line_entry; + typedef struct { oam_entry oam_ram[40]; u8 vram[0x2000]; + u8 line_sprite_count; // 0 to 10 sprites + oam_line_entry *line_sprites; //linked list of sprites + oam_line_entry line_entry_array[10]; //memory to use for list + + u8 fetched_entry_count; + oam_entry fetched_entries[3]; //entries fetched during pipeline + u8 window_line; + + pixel_fifo_context pfc; + u32 current_frame; u32 line_ticks; u32 *video_buffer; diff --git a/lib/cart.c b/lib/cart.c index f6c0247..36eadb9 100644 --- a/lib/cart.c +++ b/lib/cart.c @@ -6,10 +6,39 @@ typedef struct { u32 rom_size; u8 *rom_data; rom_header *header; + + //mbc1 data + bool ram_enabled; + bool ram_banking; + + u8 *rom_bank_x; + u8 banking_mode; + + u8 rom_bank_value; + u8 ram_bank_value; + + u8 *ram_bank; + u8 *ram_banks[16]; + + //battery + bool battery; + bool need_save; } cart_context; static cart_context ctx; +bool cart_need_save () { + return ctx.need_save; +} + +bool cart_mbc1() { + return BETWEEN(ctx.header->type, 1, 3); +} + +bool cart_battery() { + return ctx.header->type == 3; +} + static const char *ROM_TYPES[] = { "ROM ONLY", "MBC1", @@ -132,6 +161,23 @@ const char *cart_lic_name() { return "UNKNOWN"; } +void cart_setup_bamking() { + for (int i=0; i<16; i++) { + ctx.ram_banks[1] = 0; + + if((ctx.header->ram_size == 2 && i == 0) || + (ctx.header->ram_size == 3 && i < 4) || + (ctx.header->ram_size == 5 && i < 8) || + (ctx.header->ram_size == 4)) { + ctx.ram_banks[i] = malloc(0x2000); + memset(ctx.ram_banks[i], 0, 0x2000); + } + } + + ctx.ram_bank = ctx.ram_banks[0]; + ctx.rom_bank_x = ctx.rom_data + 0x4000; +} + bool cart_load(char *cart) { snprintf(ctx.filename, sizeof(ctx.filename), "%s", cart); FILE *fp = fopen(cart, "r"); @@ -153,6 +199,8 @@ bool cart_load(char *cart) { ctx.header = (rom_header *)(ctx.rom_data + 0x100); ctx.header->title[15] = 0; + ctx.battery = cart_battery(); + ctx.need_save = false; printf("Cartridge Loaded:\n"); printf("\t Title : %s\n", ctx.header->title); @@ -162,6 +210,8 @@ bool cart_load(char *cart) { printf("\t LIC Code : %2.2X (%s)\n", ctx.header->lic_code, cart_lic_name()); printf("\t ROM Vers : %2.2X\n", ctx.header->version); + cart_setup_bamking(); + u16 x = 0; for (u16 i=0X0134; i<=0X014C; i++) { x = x - ctx.rom_data[i] -1; @@ -172,17 +222,119 @@ bool cart_load(char *cart) { if((x & 0xFF) != ctx.header->checksum) { fprintf(stderr, "WARNING!!! The header checksum does not match! ROM may be corrupt or invalid!\n"); } + + if(ctx.battery) { + cart_battery_load(); + } return true; } u8 cart_read(u16 address){ - //for now ROM ONLY type supported... + if(!cart_mbc1() || address < 0x4000) { + return ctx.rom_data[address]; + } + + if((address & 0xE000) == 0xA000) { + if (!ctx.ram_enabled) { + return 0xFF; + } + + if(!ctx.ram_bank) { + return 0xFF; + } + + return ctx.ram_bank[address - 0xA000]; + } + + return ctx.rom_bank_x[address - 0x4000]; - return ctx.rom_data[address]; } void cart_write(u16 address, u8 value){ - printf("UNSUPPORTED cart_write(%04X)\n", address); - //NO_IMPL + if(!cart_mbc1()) { + return; + } + + if(address < 0x2000) { + ctx.ram_enabled = ((value & 0xF) == 0xA); + } + + if((address & 0xE000) == 0x2000) { + //rom bank + if(value == 0) { + value = 1; + } + + value &= 0b11111; + ctx.rom_bank_value = value; + ctx.rom_bank_x = ctx.rom_data + (0x4000 * ctx.rom_bank_value); + } + + if((address & 0xE000) == 0x4000) { + //ram bank number + ctx.ram_bank_value = value & 0xb11; + if(ctx.ram_banking) { + if(cart_need_save) { + cart_battery_save(); + } + ctx.ram_bank = ctx.ram_banks[ctx.ram_bank_value]; + } + } + + if((address & 0xE000) == 0x6000) { + //bamking mode select + + ctx.banking_mode = value & 1; + + ctx.ram_banking = ctx.banking_mode; + + if (ctx.ram_banking) { + if(cart_need_save) { + cart_battery_save(); + } + ctx.ram_bank = ctx.ram_banks[ctx.ram_bank_value]; + } + } + + if((address & 0xE000) == 0xA000) { + if(!ctx.ram_enabled) + return; + + if(!ctx.ram_bank) + return; + + ctx.ram_bank[address - 0xA000] = value; + + if(ctx.battery) { + ctx.need_save = true; + } + } +} + +bool cart_battery_load(){ + char fn[1048]; + sprintf(fn, "%s.battery", ctx.filename); + FILE *fp = fopen(fn, "rb"); + + if(!fp) { + fprintf(stderr, "unable to open: %s\n", fn); + } + + fread(ctx.ram_bank, 0x2000, 1, fp); + fclose(fp); +} + +bool cart_battery_save(){ + char fn[1048]; + sprintf(fn, "%s.battery", ctx.filename); + FILE *fp = fopen(fn, "wb"); + + if(!fp) { + fprintf(stderr, "unable to open: %s\n", fn); + } + + fwrite(ctx.ram_bank, 0x2000, 1, fp); + fclose(fp); + } \ No newline at end of file diff --git a/lib/cpu_fetch.c b/lib/cpu_fetch.c index 0d5e3a6..c52f146 100644 --- a/lib/cpu_fetch.c +++ b/lib/cpu_fetch.c @@ -75,7 +75,7 @@ void fetch_data() { return; case AM_R_A8: ctx.fetched_data = bus_read(ctx.regs.pc); - emu_cycles(2); + emu_cycles(1); ctx.regs.pc++; return; case AM_A8_R: diff --git a/lib/cpu_proc.c b/lib/cpu_proc.c index 0681363..588f323 100644 --- a/lib/cpu_proc.c +++ b/lib/cpu_proc.c @@ -70,7 +70,6 @@ static void proc_halt(cpu_context *ctx) { static void proc_stop(cpu_context *ctx) { printf("CPU STOP!\n"); - NO_IMPL } static void proc_rlca(cpu_context *ctx) { @@ -384,7 +383,6 @@ static void proc_pop(cpu_context *ctx) { u16 n = (hi << 8) | lo; cpu_set_reg(ctx->cur_inst->reg_1, n); - emu_cycles(2); if (ctx->cur_inst->reg_1 == RT_AF) { cpu_set_reg(ctx->cur_inst->reg_1, n & 0xFFF0); @@ -397,6 +395,8 @@ static void proc_push(cpu_context *ctx) { emu_cycles(1); stack_push((val & 0xFF)); emu_cycles(1); + + emu_cycles(1); } static void proc_ldh(cpu_context *ctx) { @@ -423,8 +423,8 @@ static void proc_ld(cpu_context *ctx) { emu_cycles(1); } else { bus_write(ctx->mem_dest, ctx->fetched_data); - emu_cycles(1); } + emu_cycles(1); return; } diff --git a/lib/gamepad.c b/lib/gamepad.c new file mode 100644 index 0000000..8491168 --- /dev/null +++ b/lib/gamepad.c @@ -0,0 +1,65 @@ +#include +#include + +typedef struct { + bool button_sel; + bool dir_sel; + gamepad_state controller; +} gamepad_context; + +static gamepad_context ctx = {0}; + +void gamepad_init(); + +bool gamepad_button_sel(){ + return ctx.button_sel; +} + +bool gamepad_dir_sel(){ + return ctx.dir_sel; +} + +void gamepad_set_sel(u8 value){ + ctx.button_sel = value & 0x20; + ctx.dir_sel = value & 0x10; +} + +gamepad_state *gamepad_get_state(){ + return &ctx.controller; +} + +u8 gamepad_get_output() { + u8 output = 0xCF; + + if(!gamepad_button_sel()) { + if (gamepad_get_state()->start) { + output &= ~(1 << 3); + } + if (gamepad_get_state()->select) { + output &= ~(1 << 2); + } + if (gamepad_get_state()->a) { + output &= ~(1 << 0); + } + if (gamepad_get_state()->b) { + output &= ~(1 << 1); + } + } + + if(!gamepad_dir_sel()) { + if (gamepad_get_state()->left) { + output &= ~(1 << 1); + } + if (gamepad_get_state()->right) { + output &= ~(1 << 0); + } + if (gamepad_get_state()->up) { + output &= ~(1 << 2); + } + if (gamepad_get_state()->down) { + output &= ~(1 << 3); + } + } + + return output; +} \ No newline at end of file diff --git a/lib/io.c b/lib/io.c index 45fc128..1c5a3e2 100644 --- a/lib/io.c +++ b/lib/io.c @@ -3,10 +3,14 @@ #include #include #include +#include static char serial_data[2]; u8 io_read(u16 address){ + if(address == 0xFF00) { + return gamepad_get_output(); + } if(address == 0xFF01) { return serial_data[0]; } @@ -27,14 +31,24 @@ u8 io_read(u16 address){ return cpu_get_int_flags(); } + if(BETWEEN(address, 0xFF10, 0xFF3F)) { + //ignore sound + return 0; + } + printf("UNSUPPORTED io_read(%04X)\n", address); return 0; } void io_write(u16 address, u8 value){ + if(address == 0xFF00) { + gamepad_set_sel(value); + return; + } if(address == 0xFF01) { serial_data[0] = value; + printf("%c", value); return; } @@ -58,6 +72,11 @@ void io_write(u16 address, u8 value){ return; } + if(BETWEEN(address, 0xFF10, 0xFF3F)) { + //ignore sound + return; + } + printf("UNSUPPORTED io_write(%04X)\n", address); } diff --git a/lib/ppu.c b/lib/ppu.c index 83f7a8c..152e69f 100644 --- a/lib/ppu.c +++ b/lib/ppu.c @@ -3,6 +3,9 @@ #include #include +void pipeline_fifo_reset(); +void pipeline_process(); + static ppu_context ctx; ppu_context *ppu_get_context() { @@ -14,6 +17,17 @@ void ppu_init() { ctx.line_ticks = 0; ctx.video_buffer = malloc(YRES * XRES * sizeof(u32)); + ctx.pfc.line_x = 0; + ctx.pfc.pushed_x = 0; + ctx.pfc.fetch_x = 0; + ctx.pfc.pixel_fifo.size = 0; + ctx.pfc.pixel_fifo.head = ctx.pfc.pixel_fifo.tail = NULL; + ctx.pfc.cur_fetch_state = FS_TILE; + + ctx.line_sprites = 0; + ctx.fetched_entry_count = 0; + ctx.window_line = 0; + lcd_init(); LCDS_MODE_SET(MODE_OAM); diff --git a/lib/ppu_pipeline.c b/lib/ppu_pipeline.c new file mode 100644 index 0000000..4ba19d0 --- /dev/null +++ b/lib/ppu_pipeline.c @@ -0,0 +1,271 @@ +#include +#include +#include + +bool window_visible() { + return LCDC_WIN_ENABLE && lcd_get_context()->win_x >= 0 && + lcd_get_context()->win_x <= 166 && lcd_get_context()->win_y >= 0 && + lcd_get_context()->win_y < YRES; +} + +void pixel_fifo_push(u32 value) { + fifo_entry *next = malloc(sizeof(fifo_entry)); + next->next = NULL; + next->value = value; + + if(!ppu_get_context()->pfc.pixel_fifo.head) { + //first entry + ppu_get_context()->pfc.pixel_fifo.head = ppu_get_context()->pfc.pixel_fifo.tail = next; + } else { + ppu_get_context()->pfc.pixel_fifo.tail->next = next; + ppu_get_context()->pfc.pixel_fifo.tail = next; + } + + ppu_get_context()->pfc.pixel_fifo.size++; +} + +u32 pixel_fifo_pop() { + if(ppu_get_context()->pfc.pixel_fifo.size <= 0) { + fprintf(stderr, "ERR in pixel FIFO\n"); + } + + fifo_entry *popped = ppu_get_context()->pfc.pixel_fifo.head; + ppu_get_context()->pfc.pixel_fifo.head = popped->next; + ppu_get_context()->pfc.pixel_fifo.size--; + + u32 val = popped->value; + free(popped); + return val; +} + +u32 fetch_sprite_pixels(int bit, u32 color, u8 bg_color) { + for (int i = 0; i < ppu_get_context()->fetched_entry_count; i++) { + int sp_x = (ppu_get_context()->fetched_entries[i].x - 8) + + ((lcd_get_context()->scroll_x % 8)); + + if (sp_x + 8 < ppu_get_context()->pfc.fifo_x) { + //past this pixel already + continue; + } + + int offset = ppu_get_context()->pfc.fifo_x - sp_x; + + if (offset < 0 || offset > 7) { + continue; + } + + bit = 7 - offset; + + if (ppu_get_context()->fetched_entries[i].f_x_flip) { + bit = offset; + } + + u8 lo = !!(ppu_get_context()->pfc.fetch_entry_data[i * 2] & (1 << bit)); + u8 hi = !!(ppu_get_context()->pfc.fetch_entry_data[(i * 2)+1] & (1 << bit)) << 1; + + bool bg_priority = ppu_get_context()->fetched_entries[i].f_bgp; + + if (!(hi|lo)) { + //transparant + continue; + } + + if(!bg_priority || bg_color == 0) { + color = (ppu_get_context()->fetched_entries[i].f_pn) ? + lcd_get_context()->sp2_colors[hi|lo] : lcd_get_context()->sp1_colors[hi|lo]; + + if(hi | lo) { + break; + } + } + } + + return color; +} + +bool pipeline_fifo_add() { + if(ppu_get_context()->pfc.pixel_fifo.size > 8) { + //fifo is full! + return false; + } + + int x = ppu_get_context()->pfc.fetch_x - (8 - lcd_get_context()->scroll_x % 8); + + for (int i=0; i<8; i++) { + int bit = 7 - i; + u8 lo = !!(ppu_get_context()->pfc.bgw_fetch_data[1] & (1 << bit)); + u8 hi = !!(ppu_get_context()->pfc.bgw_fetch_data[2] & (1 << bit)) << 1; + u32 color = lcd_get_context()->bg_colors[hi | lo]; + + if (!LCDC_BGW_ENABLE) { + color = lcd_get_context()->bg_colors[0]; + } + + if(LCDC_OBJ_ENABLE) { + color = fetch_sprite_pixels(bit, color, hi | lo); + } + + if (x >= 0) { + pixel_fifo_push(color); + ppu_get_context()->pfc.fifo_x++; + } + } + + return true; +} + +void pipeline_load_sprite_tile() { + oam_line_entry *le = ppu_get_context()->line_sprites; + + while(le) { + int sp_x = (le->entry.x - 8) + (lcd_get_context()->scroll_x % 8); + + if ((sp_x >= ppu_get_context()->pfc.fetch_x && sp_x < ppu_get_context()->pfc.fetch_x + 8) || + ((sp_x + 8) >= ppu_get_context()->pfc.fetch_x && (sp_x + 8) < ppu_get_context()->pfc.fetch_x + 8)) { + ppu_get_context()->fetched_entries[ppu_get_context()->fetched_entry_count++] = le->entry; + } + + le = le->next; + + if (!le || ppu_get_context()->fetched_entry_count >= 3) { + //max checking 3 sprites on pixels + break; + } + } +} + +void pipeline_load_sprite_data(u8 offset) { + int cur_y = lcd_get_context()->ly; + u8 sprite_height = LCDC_OBJ_HEIGHT; + + for (int i = 0; i < ppu_get_context()->fetched_entry_count; i++) { + u8 ty = ((cur_y + 16) - ppu_get_context()->fetched_entries[i].y) * 2; + + if (ppu_get_context()->fetched_entries[i].f_y_flip) { + ty = ((sprite_height * 2) - 2) - ty; + } + + u8 tile_index = ppu_get_context()->fetched_entries[i].tile; + + if (sprite_height == 16) { + tile_index &= ~(1); + } + + ppu_get_context()->pfc.fetch_entry_data[(i * 2) + offset] = + bus_read(0x8000 + (tile_index * 16) + ty + offset); + } +} + +void pipeline_load_window_tile() { + if(!window_visible()) + return; + + u8 window_y = lcd_get_context()->win_y; + + if (ppu_get_context()->pfc.fetch_x + 7 >= lcd_get_context()->win_x && + ppu_get_context()->pfc.fetch_x + 7 < lcd_get_context()->win_x + YRES + 14) { + if(lcd_get_context()->ly >= window_y && lcd_get_context()->ly < window_y + XRES) { + u8 w_tile_y = ppu_get_context()->window_line / 8; + + + ppu_get_context()->pfc.bgw_fetch_data[0] = bus_read(LCDC_WIN_MAP_AREA + + ((ppu_get_context()->pfc.fetch_x + 7 - lcd_get_context()->win_x) / 8) + + (w_tile_y * 32)); + + if(LCDC_BGW_DATA_AREA == 0x8800){ + ppu_get_context()->pfc.bgw_fetch_data[0] += 128; + } + } + } +} + +void pipeline_fetch() { + switch(ppu_get_context()->pfc.cur_fetch_state) { + case FS_TILE: { + ppu_get_context()->fetched_entry_count = 0; + if (LCDC_BGW_ENABLE) { + ppu_get_context()->pfc.bgw_fetch_data[0] = bus_read(LCDC_BG_MAP_AREA + + (ppu_get_context()->pfc.map_x / 8) + + (((ppu_get_context()->pfc.map_y / 8)) * 32)); + + if(LCDC_BGW_DATA_AREA == 0x8800) { + ppu_get_context()->pfc.bgw_fetch_data[0] += 128; + } + + pipeline_load_window_tile(); + } + + if(LCDC_OBJ_ENABLE && ppu_get_context()->line_sprite_count) { + pipeline_load_sprite_tile(); + } + + ppu_get_context()->pfc.cur_fetch_state = FS_DATA0; + ppu_get_context()->pfc.fetch_x += 8; + } break; + + case FS_DATA0: { + ppu_get_context()->pfc.bgw_fetch_data[1] = bus_read(LCDC_BGW_DATA_AREA + + (ppu_get_context()->pfc.bgw_fetch_data[0] * 16) + + ppu_get_context()->pfc.tile_y); + + pipeline_load_sprite_data(0); + + ppu_get_context()->pfc.cur_fetch_state = FS_DATA1; + } break; + + case FS_DATA1: { + ppu_get_context()->pfc.bgw_fetch_data[2] = bus_read(LCDC_BGW_DATA_AREA + + (ppu_get_context()->pfc.bgw_fetch_data[0] * 16) + + ppu_get_context()->pfc.tile_y + 1); + + pipeline_load_sprite_data(1); + + ppu_get_context()->pfc.cur_fetch_state = FS_IDLE; + } break; + + case FS_IDLE: { + ppu_get_context()->pfc.cur_fetch_state = FS_PUSH; + } break; + + case FS_PUSH: { + if (pipeline_fifo_add()) { + ppu_get_context()->pfc.cur_fetch_state = FS_TILE; + } + } break; + } +} + +void pipeline_push_pixel() { + if (ppu_get_context()->pfc.pixel_fifo.size > 8) { + u32 pixel_data = pixel_fifo_pop(); + + if(ppu_get_context()->pfc.line_x >= (lcd_get_context()->scroll_x % 8)) { + ppu_get_context()->video_buffer[ppu_get_context()->pfc.pushed_x + + (lcd_get_context()->ly * XRES)] = pixel_data; + + ppu_get_context()->pfc.pushed_x++; + } + + ppu_get_context()->pfc.line_x++; + } +} + +void pipeline_process() { + ppu_get_context()->pfc.map_y = (lcd_get_context()->ly + lcd_get_context()->scroll_y); + ppu_get_context()->pfc.map_x = (ppu_get_context()->pfc.fetch_x + lcd_get_context()->scroll_x); + ppu_get_context()->pfc.tile_y = ((lcd_get_context()->ly + lcd_get_context()->scroll_y) % 8) * 2; + + if(!(ppu_get_context()->line_ticks & 1)) { + pipeline_fetch(); + } + + pipeline_push_pixel(); +} + +void pipeline_fifo_reset() { + while(ppu_get_context()->pfc.pixel_fifo.size) { + pixel_fifo_pop(); + } + + ppu_get_context()->pfc.pixel_fifo.head = 0; +} \ No newline at end of file diff --git a/lib/ppu_sm.c b/lib/ppu_sm.c index b64e3e8..8953c85 100644 --- a/lib/ppu_sm.c +++ b/lib/ppu_sm.c @@ -2,8 +2,17 @@ #include #include #include +#include +#include + +void pipeline_fifo_reset(); +void pipeline_process(); void increment_ly() { + if (window_visible() && lcd_get_context()->ly >= lcd_get_context()->win_y && lcd_get_context()->ly < lcd_get_context()->win_y + YRES) { + ppu_get_context()->window_line++; + } + lcd_get_context()->ly++; if(lcd_get_context()->ly == lcd_get_context()->ly_compare) { @@ -17,15 +26,93 @@ void increment_ly() { } } +void load_line_sprites() { + int cur_y = lcd_get_context()->ly; + + u8 sprite_height = LCDC_OBJ_HEIGHT; + memset(ppu_get_context()->line_entry_array, 0, sizeof(ppu_get_context()->line_entry_array)); + for (int i=0; i<40; i++) { + oam_entry e = ppu_get_context()->oam_ram[i]; + if (!e.x) { + //x = 0 means not visible + continue; + } + + if (ppu_get_context()->line_sprite_count >= 10) { + //max of 10 sprites per line + break; + } + + if(e.y <= cur_y + 16 && e.y + sprite_height > cur_y + 16) { + //this sprite is on the current line + oam_line_entry *entry = &ppu_get_context()->line_entry_array[ + ppu_get_context()->line_sprite_count++ + ]; + + entry->entry = e; + entry->next = NULL; + + if(!ppu_get_context()->line_sprites || + ppu_get_context()->line_sprites->entry.x > e.x) { + entry->next = ppu_get_context()->line_sprites; + ppu_get_context()->line_sprites = entry; + continue; + } + + //do some sorting + + oam_line_entry *le = ppu_get_context()->line_sprites; + oam_line_entry *prev = le; + + while(le) { + if (le->entry.x > e.x) { + prev->next = entry; + entry->next = le; + break; + } + + if(!le->next) { + le->next = entry; + break; + } + + prev = le; + le = le->next; + } + } + } +} + void ppu_mode_oam() { if(ppu_get_context()->line_ticks >= 80) { LCDS_MODE_SET(MODE_XFER); + + ppu_get_context()->pfc.cur_fetch_state = FS_TILE; + ppu_get_context()->pfc.line_x = 0; + ppu_get_context()->pfc.fetch_x = 0; + ppu_get_context()->pfc.pushed_x = 0; + ppu_get_context()->pfc.fifo_x = 0; + } + + if(ppu_get_context()->line_ticks == 1) { + //read oam on the first tick only... + ppu_get_context()->line_sprites = 0; + ppu_get_context()->line_sprite_count = 0; + + load_line_sprites(); } } void ppu_mode_xfer() { - if(ppu_get_context()->line_ticks >= 80 + 172) { + pipeline_process(); + + if(ppu_get_context()->pfc.pushed_x >= XRES) { + pipeline_fifo_reset(); LCDS_MODE_SET(MODE_HBLANK); + + if(LCDS_STAT_INT(SS_HBLANK)) { + cpu_request_interrupt(IT_LCD_STAT); + } } } @@ -36,13 +123,14 @@ void ppu_mode_vblank() { if(lcd_get_context()->ly >= LINES_PER_FRAME) { LCDS_MODE_SET(MODE_OAM); lcd_get_context()->ly = 0; + ppu_get_context()->window_line = 0; } ppu_get_context()->line_ticks = 0; } } -static u32 target_frame_time = 1000/60; +u32 target_frame_time = 1000/60; static long prev_frame_time = 0; static long start_timer = 0; static long frame_count = 0; @@ -75,6 +163,9 @@ void ppu_mode_hblank() { start_timer = end; frame_count = 0; printf("FPS: %ld\n", fps); + if(cart_need_save()){ + cart_battery_save(); + } } frame_count++; diff --git a/lib/ui.c b/lib/ui.c index 1412216..493dbb7 100644 --- a/lib/ui.c +++ b/lib/ui.c @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include @@ -23,6 +25,19 @@ void ui_init(){ printf("TTF INIT\n"); SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, 0, &sdlWindow, &sdlRenderer); + + screen = SDL_CreateRGBSurface(0, SCREEN_WIDTH, + SCREEN_HEIGHT, 32, + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); + sdlTexture = SDL_CreateTexture(sdlRenderer, + SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, + SCREEN_WIDTH, + SCREEN_HEIGHT); + SDL_CreateWindowAndRenderer(16 * 8 * scale, 32 * 8 * scale, 0, &sdlDebugWindow, &sdlDebugRenderer); debugScreen = SDL_CreateRGBSurface(0, (16 * 8 * scale) + (16 * scale), (32 * 8 * scale) + (64 * scale), 32, @@ -95,15 +110,73 @@ void update_debug_window() { } void ui_update() { + SDL_Rect rc; + rc.x = rc.y = 0; + rc.w = rc.h = 2048; + + u32 *video_buffer = ppu_get_context()->video_buffer; + + for(int line_num = 0; line_num < YRES; line_num++) { + for(int x = 0; x < XRES; x++) { + rc.x = x * scale; + rc.y = line_num * scale; + rc.w = scale; + rc.h = scale; + + SDL_FillRect(screen, &rc, video_buffer[x + (line_num * XRES)]); + } + } + + SDL_UpdateTexture(sdlTexture, NULL, screen->pixels, screen->pitch); + SDL_RenderClear(sdlRenderer); + SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL); + SDL_RenderPresent(sdlRenderer); + update_debug_window(); } +static bool ff = false; + +void ui_on_key(bool down, u32 key_code) { + if(key_code == SDLK_SPACE && down == true) { + ff = !ff; + if(ff){ + target_frame_time = 1000/300; + } else { + target_frame_time = 1000/60; + } + printf("target frame time: %d\n", target_frame_time); + } + switch(key_code){ + case SDLK_z: gamepad_get_state()->b = down; break; + case SDLK_x: gamepad_get_state()->a = down; break; + case SDLK_RETURN: gamepad_get_state()->start = down; break; + case SDLK_TAB: gamepad_get_state()->select = down; break; + + case SDLK_UP: gamepad_get_state()->up = down; break; + case SDLK_DOWN: gamepad_get_state()->down = down; break; + case SDLK_LEFT: gamepad_get_state()->left = down; break; + case SDLK_RIGHT: gamepad_get_state()->right = down; break; + } +} + + void ui_handle_events(){ SDL_Event e; while(SDL_PollEvent(&e) > 0) { + + if(e.type == SDL_KEYDOWN) { + ui_on_key(true, e.key.keysym.sym); + } + + if(e.type == SDL_KEYUP) { + ui_on_key(false, e.key.keysym.sym); + } + if(e.type == SDL_WINDOWEVENT && e.window.event == SDL_WINDOWEVENT_CLOSE) { emu_get_context()->die = true; } + } } diff --git a/roms/01-read_timing.gb b/roms/01-read_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..d0836aabd5807ad6781955db20358fd20a384b66 GIT binary patch literal 32768 zcmeH}Z)_9i9mgNr@g*2z65z!{NZ>-+C>SE#Rbv)&Pf-`ai>YLbIRXRC`u6eFLRP)m_eZa|#byq!L{vvf83%nnk0S>Q!n*a3gc$lm>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c<=@76DtGTM8#0>`Jg3fj`eP=gPdNeJVm`{VDbDzR?t0Ozq>=NZs*rqSKs` zmh4UFLdupbiSBf`TX$Y%KM|Q ztjs=pZbf}u{cNDQ`{jK<-8WWQwCc`OE$HTIA(yvl4i9Q}E#Nv{BT{(I6ag10XzF?& z@%xJS$o!kfL*kES{EGUIx+7LhvDFg2mgu*{2d3kRJ=ViDxUCmGOEgW(@v^+?P4bHO z3Sail@lU)Qzv)f!MX#8}v`LG9j5lIG1?WKer zaJ?drdPNLujGegp@$*(JBA&NO2_%C`l>Ke5CmzghykZbq2Zs&F+_lKhkY%-|F-=b<66^2kNHP3s=>S)!Fye zkUeS&*Rro#aa6l8`=0ux^TiX*PixH8Sh*eA3^kzvZ5nEpAJ-Y9DJ)xY#HBfH!)u^%32Y9W4}_at z;ih&TZ>YCn`{MT8&z)jm=U1WZdxOgANaHotvmEie!Wwh$bQTU4@NVoY^sx&uCeEV& zWvq!_+g96wU9a`H+WlIuD=G#4!#?&~rEOxL`rxs!df&kj_>t0yLvUI<53&EOu)3@T zYNI$b2m*hjO{Z(1sOUwdqkHQ+yLZEr6;|<9i@)01>*Zni`JVP!`LG=>`a@mi;kt7P zpMmK+(?~{s2nKtrUf&Kk-e^}h)EnwxL@XA(`sJZ-XC`n6B4PZ(OR>7v>y|oD zgQoU%yUMn2h1Cbz4pdjcC!jSMGnOuvpRPMM<>WI11AF##?Qna$cb`Ly`<2B<-#RWE z<1C#{`=7sbBaMqOuIVWy$C;7l3BB|x>p9SqJY#&9MWa!FUaSY}dSYxWi9R{Tn2~Cx zaQqnOc{psfL_)_64&!>$brw{ja5&6TMrPbFQjE*>JP%tw$g_NuM-_}mds68x=X4!;`Mu6GUC$sS!&59XSW2ePQ%sQ!>#$}FYZ<(tC(e(K;JO+#JZVMH2C-NqJAbx>#`orj|Hbrd|v*g;EhLb zkMD|phyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko v0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvzQ|4QIrKpA>a literal 0 HcmV?d00001 diff --git a/roms/02-write_timing.gb b/roms/02-write_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..cfe7c0fa5716f723c3482f915ab9543679e269cc GIT binary patch literal 32768 zcmeI4Uu+b|8Nk20^Q|xDeAqyxDcG=QC+=ae*bUyAY&2tlfLy>|yIGpI;x=861pMLyX(bYLh6Id z6E{{?-hb`Fg`+>2NZq`=^1*9wTsZ$A{{pdD)Fa`T`OHgSnETSJhkC(E^$c(6Q$OF< zt=`HzV5MQyf#{b$b4rj15CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+Sp zM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+Sp zM1Tko0V43fMZi%Pm*d)nYedsUGa_< z&t>jION4;GxV#m)A`Shu*?s=n9LhY(!aiB}J-LqCw>Eo0-Lf|KuG+mee_l}umK*2YA4EOlmKOj&!Pm?2l!O={&ef2__Za9wzz&# zj9EU9iCq>C&yA2LMI@ZC7WCwcQ;SXKE0=tk@tan&74g)REbNh|$8$#xcR>x2+||@U z|FLJXdSD-BB9lI6JD6Jq9Wr>b_ zt4sZx0{NON&Mflom$4MAD-sWS;{lK3-I5rwy!CkCmLoeqkLBOWC~F5>FRQ-oiXWEN z!Mo8@y5-=({@6*wd970g9o}%OQ@yX*QZrrRrIOz~JMxW* zDcnJ6C6-EG)z$F+win}yUW4PGgMQwX63&R7^Htkjv%5Z&$9B!v}zILstq6%pB8<$}gbrKwtKl`E5uh zli|8lAKLY$U^0t+77v)I2fB$QgiKL}Aa83$V! z&(w7W=xe#PivCPprY`4Y3T-YJZ)a$>6GmnAN)+;;o`;@nu5xc509uxzZK%4!(SL{+ z>jz2NkoE8|F6w3x^**hD*rg4Qa|M|oE{xA0ixF@ikv_IbgqM@`XG2xT^*1E|?Az<5 zKUsfUFBqXebSQPn19f@ua33Xs6zVbzZ}(U~Ac5tAJgXsq_BiH)n;f2>6l0pS3&SIq zNyd_x7OpcseZ75j@f|?C?Z_{cwtr;m`1r{qPn{fpZhYcsm;BiJ)d-)fMtz=;f8{;- z!D%|rmBe>TB7I4I=UMCC^$C5^4-p^&M1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y z2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y M2oM1x@LwSCFNP9pv;Y7A literal 0 HcmV?d00001 diff --git a/roms/03-modify_timing.gb b/roms/03-modify_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..28f1ae66df4cf2df43b3b164e9e10fae46848bcc GIT binary patch literal 32768 zcmeH}|8G;*6~K?}_$3X-B!H$naNwD=Q3zysWo6dpK1Ce@sZ$9*ZBwNfC(@GCcp%k z025#WOn?b60Vco%m;e)C0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k z025#WOyK{EfUPbphZA>qC*W$}Z;RkuT=cXLWyq{Qs($J#O}W+7uD53Dj+d2P<}_)} ze{*mIXwA}0X|{B(H0OAQ8ARbj@^l_r!%#Wk51O!89rN^ z`{7hgeMtSNues-!`+u>2ynM~7JJZ#mo2`ai(WW^fsM*zk>v)aM{F9~(xR`>bZj7D& zSeEaZ|L{ae{?Qz`sQ#<&$eJm)S+d8Hy_S5}bUeA&x@Uv0^@3-~rfE4|MpV2>QTEP? z6>mzs?`6deZ(3aQ3K?8BX^Ho|!eieTpLm7CPl#LcNz+^UwmfLnSGP1elfhzpF<}Q> zuOK2`0Vg&^Pt0F`%8GW%r>tTElfg=q18uJ)?#>QpP@Qzj6|c81e7(fpIumQb5s4{NBeJzE%fd1*yzc7U&8L#y5gS7 zUF?4VEfE4rT;2#=B|~p*=76_0i~St-xdT-A{eBa-Z*At1x^->#U3K%?xp}o?ZSEa4 zWDlFtwd{Fo0IS`Ud0V~h{PUa553As6P-@4%1$z^FpEiTNS$asDC_NZA7UgeEnX}~M zi*nfXw2<6wYW}&AbW-Fo+ph&ZdGgFc-JV|bDC0kv^+v!|qg2?DXGYQ|Pjo^Vk=)bN zUgUDv-$D$jXzitT$E#=%4>u<+^ndPYg1n!fxF7)f>*c!h2tY9OEfPx#gxZ#(B)&#s z7l}R+-ym^_ght{862BqwDv8%gyh-9M5*J8ZCQ&4DmBccMzmr%cQ6upwi3SS!=0a;r z0m|_!3v$Hnoo*acZ`*HJ(z4H9?o~rmmlrF?_8r-`Z{I*;No|ui%^dCvRs9_(pT&CV z^#GKoHeLszocYh}WD7{S6(xYY2CARi1Jwrhrd?4$)@-?AAIJWqvNMd@yxEq)L? zT)>1;qL)Hty~izfU=5lY-{C6T9}cT`wVha98Lx@fVqCHOV(GEEbAuL-_4V!D+r886 z*|BE|W85iUyMG@FY9Jt;PWxA6x{<~QKxleO$qF#iBB2*wfY_l}a?JQHL?RJ?T{MPv zu_%luaZHW_7^!B;z)wQt&|#}35<0%*HZ%ZThoBNcheMDuM$Z^V3Iy4U9P0ET2f2ud zC^#R9rP4W!mD95d=_y^$()t`NOC)f;K@W|Z5=l~AdN#%cP?UtATroU8_)eCsI9M;n z|M{q{fj|d2!+@+|(1Y{3uA|>f+AxfC2IK2lUB|rqQ3p-eM=>PeS2QyqMd^zaDAdpn zZN||SOQn{BVy&@UEEd5ylgptY<%=!l$HN7_ekOg$*D)l;Pmv#lNR8rP^Wzz*lmPU# z+^C^HBSopxcS@nf1>^1V&33^MRj&jgWK|CrD*8i$BXp?k=Cgm z&5w(^kwZO}$Rc*K!Ff$VCWs5;3rJ!F+()F3Z5-jHss5y|rf~f&0)S&{J^7>hTY6Ua z{h>qBi3jR*@NgfAKooTfqqRn?ACSOumd>gVpf!*A;3oU$Cn`jO?ELV^Wn72|riJVD zPhM{wU3>?Su{>Q}iGzdBjhz@VCZGP*$a5p3r#k6U_eue6wiNWV4*GAxr5mSiU(d@w z=jBU_^!G$G?*D##XDpZi6JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C0!)AjFaajO z1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C0!)Aje5DEe E8=ZrgSO5S3 literal 0 HcmV?d00001 diff --git a/roms/instr_timing.gb b/roms/instr_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..61d2b207161f63a9b4d89f9a8ed81dc3bc6e50c5 GIT binary patch literal 32768 zcmeI4Z)_9i9mk)$vyTuQlK`r25W+dMZa`tUt=2`GdqNbIp$SrLh)UI}Ts2~r;U-yv zh-n-zkZJ3-7hAPA>zh^6q)vM?*1cF7kdU3TO|IcVLn_fBmDN_8Otola7u}!~+50|c zhZozVOt5KU`aQYle)oO;{Qi9I;mE64-B~7@b)VMT{4n3Fjq>GBVzEmtY@IU7rdWWr zu%@-@i1tbp86ZH_o3w_U*yojZ4?xf8)*bGfx=@4t?|RH~J1AIC$XDew03m zE_n#f{`Gm!K18?uZ7dyZ-MOJ%|9n%6ek)>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm`6D2w3{;f+4S=-kB;^qWkrKo z$7k>OoM@N-nXV7D^^fTyF zyG7ym`iFRZ<;nBsmWN5g5Bz}q& zLF|hx{BEksvPZ0L-L)rnFJ08tJIq<~r6-n}^0)i=53EPng)ZL<{4dt{ZXv_-7v|2* zLE2Xr!}g(xknVK0bgeU3OPAjWcN$LTIQM0XSL_hGBs~8Co^RP5j9<1c=xaM_o|Qaa z&+gTcIy@~-ghEcIF<;UhvDbDd@Xn2|;P_QQ(%6NElz7nbP_J16@?OL0EQKATyYx6V zXG>kqSW}0Slfq70*a?Llu&69rx^wf14yX7TuqqmiJYEuDcnV-^hx3 zTeCmS!u>=x#Lfg6&%*vBSc5e-mA2p*mD(NSc4@1_pUi3B3b9|RzOioo?xTMFwv99O z9kmswYQXqDI{Vk%uU|K$QZG&+g1}#I&2AgZsb)@X+Pv|1ot^NM@#{DPyeDhCUf2Ua z&l(>U_FG4C-cVa%PsKiq@5OM_#@ zx+C6~L&CVjOK$PbRwv(t8Vo(O*-DTWXTTopQ-xNf}GOSp5t*{rzPb?k^hj;8~ z+v059+&PXIxA9I_KAxIrOk?qQ+#8Blw@jr*B)fUJ+M8)jK!k0Xbe-DmV(22i$qL3q#BAb(`2$5fWtj38XG(ri$$3x_G>9< zF_B`afEG|O9%zroQ;3x^ld8~B(@cu>DX}aP!TOlEgf-MakBBR-hK(>0C8CL35gr$w z#m;&h)QkAv9W)K5!9gk(W64-dTo^Y^6Mhr%SS%J#AikM2P2}ZSlNqKth>#4k*cM|^ zk-ipXs%Y4UJ;T_GQbkL#{Bm_6pU<<{$y5p(MZRcJFCG?n`egiRPa~v=9~FK8Q8W<; z&5IX{Ml}Xs3)N-#i$z81VqTM^{y(6JPBdn5_$Vh_d*6-KS524h<#po za2eLL0Mf!b^V46|k1n1W;|H?hLq9M$dSayS#K0>9gU6c1*WWEj#_59W8cpKY4o7_4 zE0&bw*RuSNbK*Z9t$Od@j_2q>Z;~RR$#nzE8d?^^(DEwUTlPP_DT;zTB}rk5(y%L< zA%6*fnn=`vc~;j_Lt_H7ij12q`Q^H04GrUs%MA^APkRv?x?Vb_ytcX(eq>n$7H#$Q ztF4n2#i#gW{GfgnR)Jm745JGEk`c5|@`?I|mV{PhpQzurTK!m$BtZ{ZdlnC({uRAi ze*ZY{mH!{C->>+5D(dn3eE4XAQykFRck@BXx)A=>@g`;GhH`yTZBLHmgS z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y i5g-CYfCvx)B0vO)01+SpM1Tko0U|&Ih`@i7z~2Eshv>Bc literal 0 HcmV?d00001