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 0000000..d0836aa Binary files /dev/null and b/roms/01-read_timing.gb differ diff --git a/roms/02-write_timing.gb b/roms/02-write_timing.gb new file mode 100644 index 0000000..cfe7c0f Binary files /dev/null and b/roms/02-write_timing.gb differ diff --git a/roms/03-modify_timing.gb b/roms/03-modify_timing.gb new file mode 100644 index 0000000..28f1ae6 Binary files /dev/null and b/roms/03-modify_timing.gb differ diff --git a/roms/instr_timing.gb b/roms/instr_timing.gb new file mode 100644 index 0000000..61d2b20 Binary files /dev/null and b/roms/instr_timing.gb differ