diff --git a/cpu/memory.c b/cpu/memory.c index b338995..36c521d 100644 --- a/cpu/memory.c +++ b/cpu/memory.c @@ -98,7 +98,7 @@ void mem_set_byte(address addr, byte data) { byte ppu_reg = relative_addr % 8; ppu_write_reg(ppu_reg, data); } else if (addr == PPU_REGISTER_OAM_DMA_ADDR) { - ppu_write_reg_oam_addr(data); + ppu_write_oamaddr(data); // Writing to this address triggers an upload to the PPU memory cpu_trigger_oam_dma(); diff --git a/gui/gui.c b/gui/gui.c index 78ed32f..321f6ce 100644 --- a/gui/gui.c +++ b/gui/gui.c @@ -30,7 +30,7 @@ bool gui_init() { return false; } - gui.debug_enabled = true; + gui.debug_enabled = false; main_window_init(&gui.main_window, gui.font); diff --git a/gui/main_window.c b/gui/main_window.c index 6771176..6024f26 100644 --- a/gui/main_window.c +++ b/gui/main_window.c @@ -35,13 +35,13 @@ void main_window_render_delay(SDL_Renderer *renderer) { char_map_render(renderer, buffer); } -void main_window_render(NesMainWindow *window, PpuPixel *pixels) { +void main_window_render(NesMainWindow *window, PPUPixel *pixels) { SDL_RenderClear(window->sdl_context.renderer); unsigned int frame_buffer[240 * 256]; for (int i = 0; i < 240 * 256; i++) { - PpuPixel pixel = pixels[i]; + PPUPixel pixel = pixels[i]; unsigned int *data = &frame_buffer[i]; *data = 0xff000000; diff --git a/gui/main_window.h b/gui/main_window.h index 7fd3492..97a82fa 100644 --- a/gui/main_window.h +++ b/gui/main_window.h @@ -9,8 +9,8 @@ #include "../include/ppu.h" #include "window.h" -#define MAIN_WINDOW_WIDTH 240 -#define MAIN_WINDOW_HEIGHT 256 +#define MAIN_WINDOW_WIDTH 256 +#define MAIN_WINDOW_HEIGHT 240 #define MAIN_WINDOW_SCALE 2 typedef struct nes_main_window { @@ -22,7 +22,7 @@ typedef struct nes_main_window { void main_window_init(NesMainWindow *window, TTF_Font *font); void main_window_uninit(NesMainWindow *window); -void main_window_render(NesMainWindow *window, PpuPixel* pixels); +void main_window_render(NesMainWindow *window, PPUPixel* pixels); void main_window_present(NesMainWindow *window); #endif //NES_EMULATOR_MAIN_WINDOW_H diff --git a/include/ppu.h b/include/ppu.h index 41a5d90..3592ebf 100644 --- a/include/ppu.h +++ b/include/ppu.h @@ -73,11 +73,17 @@ typedef struct ppu_pixel { byte r; byte g; byte b; -} PpuPixel; +} PPUPixel; + +typedef struct ppu_tile_queue { + PPUTileFetch first_fetch; + PPUTileFetch second_fetch; + PPUTileFetch displayed_fetch; +} PPUTileQueue; typedef struct ppu { PPUMemory memory; - PpuPixel pixels[256 * 240]; + PPUPixel pixels[256 * 240]; byte registers[8]; byte oam_dma_register; @@ -88,10 +94,19 @@ typedef struct ppu { byte x; bool w; - address ppu_address; + byte x_scroll; + byte fine_x_scroll; + byte y_scroll; + byte ppu_addr_increment; - PPUTileFetch tile_fetch; - PPUTileFetch next_tile_fetch; + address ppu_address; + address temp_ppu_addr; + address bg_pattern_table_addr; + + PPUTileFetch fetch; + PPUTileQueue tile_queue; +// PPUTileFetch tile_fetch; +// PPUTileFetch fetch; unsigned long frame; unsigned int scanline; unsigned int cycle; @@ -138,7 +153,7 @@ byte ppu_read_reg(byte reg); void ppu_write_reg(byte reg, byte data); -void ppu_write_reg_oam_addr(byte data); +void ppu_write_oamaddr(byte data); void ppu_write(address addr, byte data); diff --git a/main.c b/main.c index 757ef60..ba14030 100644 --- a/main.c +++ b/main.c @@ -23,7 +23,7 @@ #include "gui.h" int main() { - char *rom_path = "../test_roms/dk_japan.nes"; + char *rom_path = "../test_roms/dk_jp.nes"; log_set_level(LOG_INFO); if (!gui_init()) { diff --git a/ppu/ppu.c b/ppu/ppu.c index ea78e79..66fd4ec 100644 --- a/ppu/ppu.c +++ b/ppu/ppu.c @@ -19,7 +19,6 @@ #include "../include/ppu.h" #include "../cpu/cpu.h" #include "../include/rom.h" -#include "../gui/gui.h" #include "pattern_table.h" #define PPU_VISIBLE_FRAME_END 240 @@ -76,82 +75,153 @@ static inline unsigned int ppu_pixel_get_index(unsigned int scanline, unsigned i return scanline * PPU_VISIBLE_FRAME_END + cycle; } -static inline byte ppu_pixel_get_mask(unsigned int cycle) { - byte tile_fine_x = (cycle - 1) % 8; +static inline byte ppu_pixel_get_mask(unsigned int tile_fine_x) { return 1 << (PATTERN_TILE_SIZE - tile_fine_x - 1); } -static inline void ppu_pixel_set_color(PpuPixel *pixel, byte pt_low, byte pt_high, byte bitmask) { - byte p1_byte = pt_low & bitmask; - byte p2_byte = pt_high & bitmask; +static inline void ppu_pixel_set_color(PPUPixel *pixel, byte pt_low, byte pt_high) { + for (int i = 0; i < 8; i++) { + PPUPixel *spixel = pixel + i; + byte bitmask = ppu_pixel_get_mask(i); - if (p1_byte && p2_byte) { - pixel->r = 255; - pixel->g = 255; - pixel->b = 255; - } else if (p2_byte) { - pixel->r = 255; - pixel->g = 0; - pixel->b = 0; - } else if (p1_byte) { - pixel->r = 0; - pixel->g = 255; - pixel->b = 255; - } else { - pixel->r = 0; - pixel->g = 0; - pixel->b = 0; + byte p1_byte = pt_low & bitmask; + byte p2_byte = pt_high & bitmask; + + if (p1_byte && p2_byte) { + spixel->r = 255; + spixel->g = 255; + spixel->b = 255; + } else if (p2_byte) { + spixel->r = 255; + spixel->g = 0; + spixel->b = 0; + } else if (p1_byte) { + spixel->r = 0; + spixel->g = 255; + spixel->b = 255; + } else { + spixel->r = 0; + spixel->g = 0; + spixel->b = 0; + } } } void ppu_draw_tile() { - PPUTileFetch tile_fetch = ppu_state.tile_fetch; + PPUTileFetch fetch = ppu_state.tile_queue.displayed_fetch; - unsigned int pixel_index = ppu_pixel_get_index(ppu_state.scanline, ppu_state.cycle); - PpuPixel *pixel = &ppu_state.pixels[pixel_index]; - byte pixel_mask = ppu_pixel_get_mask(ppu_state.cycle); - ppu_pixel_set_color(pixel, tile_fetch.pattern_table_tile_low, tile_fetch.pattern_table_tile_high, pixel_mask); + unsigned int pixel_index = ppu_state.scanline * PPU_VISIBLE_FRAME_END + ppu_state.cycle; + PPUPixel *pixel = &ppu_state.pixels[pixel_index]; + ppu_pixel_set_color(pixel, fetch.pattern_table_tile_low, fetch.pattern_table_tile_high); +} + +byte ppu_get_pattern(byte tile_index, byte high) { + byte tile_row_index = (ppu_state.scanline + ppu_state.y_scroll) % 8; + address pattern_addr = ppu_state.bg_pattern_table_addr | tile_index << 4 | high << 3 | tile_row_index; + return ppu_read(pattern_addr); +} + +void ppu_fetch_tile() { + byte fetch_cycle = (ppu_state.cycle - 1) % 8; + + if (fetch_cycle == 1) { + address nametable_addr = (ppu_state.ppu_address & 0xfff) | 0x2000; + ppu_state.fetch.nametable = ppu_read(nametable_addr); + } else if (fetch_cycle == 3) { + // PPU address: + // yyy NN YYYYY XXXXX + // ||| || ||||| +++++-- coarse X scroll + // ||| || +++++-------- coarse Y scroll + // ||| ++-------------- nametable select + // +++----------------- fine Y scroll + // + // The attribute table is at the end of the nametable and contains 64 bytes + // It controls the palette assignation of a 4x4 tiles area + byte tile_col = ppu_state.ppu_address & 0x1f; + byte tile_attr_col = (tile_col >> 2) & 0x7; + + byte tile_row = (ppu_state.ppu_address & 0x3e0) >> 5; + byte tile_attr_row = (tile_row >> 2) & 0x7; + + // 0x23c0 is the base address of the first attribute table + address attr_addr = 0x23c0 | (ppu_state.ppu_address & 0x0c00) | (tile_attr_row << 3) | tile_attr_col; + ppu_state.fetch.attribute_table = ppu_read(attr_addr); + } else if (fetch_cycle == 5) { + ppu_state.fetch.pattern_table_tile_low = ppu_get_pattern(ppu_state.fetch.nametable, 0); + } else if (fetch_cycle == 7) { + ppu_state.fetch.pattern_table_tile_high = ppu_get_pattern(ppu_state.fetch.nametable, 1); + ppu_state.tile_queue.displayed_fetch = ppu_state.fetch; + ppu_draw_tile(); + + if ((ppu_state.ppu_address & 0x1f) == 0x1f) { + ppu_state.ppu_address &= ~0x1f; + ppu_state.ppu_address ^= 0x0400; + } else { + ppu_state.ppu_address++; + } + } + +// address read_addr; +// switch (fetch_cycle) { +// case 1: +// read_addr = 0x2000 + ((ppu_state.scanline / 8) * 0x20) + (ppu_state.cycle / 8); +// ppu_state.fetch.nametable = ppu_read(read_addr); +// break; +// case 3: +// read_addr = 0x23c0 + (ppu_state.cycle % 8); +// ppu_state.fetch.attribute_table = ppu_read(read_addr); +// break; +// case 5: +// read_addr = 0x1000 * ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_BG_PATTERN_TABLE_ADDR); +// read_addr += ppu_state.fetch.nametable * 16 + ppu_state.scanline % 8; +// ppu_state.fetch.pattern_table_tile_low = ppu_read(read_addr); +// break; +// case 7: +// read_addr = 0x1000 * ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_BG_PATTERN_TABLE_ADDR); +// read_addr += ppu_state.fetch.nametable * 16 + ppu_state.scanline % 8 + 8; +// ppu_state.fetch.pattern_table_tile_high = ppu_read(read_addr); +// ppu_tile_push(); +// break; +// default: +// break; +// } } void ppu_visible_frame(unsigned int cycle) { + if (!ppu_read_flag(PPU_REGISTER_MASK, PPU_MASK_SHOW_BG)) { + // Background rendering is off + return; + } + if (cycle == 0) { // Idle... - } else if (cycle < 256) { - if (ppu_read_flag(PPU_REGISTER_MASK, PPU_MASK_SHOW_BG)) { - ppu_draw_tile(); + } else if (cycle <= 256) { + ppu_fetch_tile(); - if (cycle <= 248) { - address read_addr; - byte tile_fetch_cycle = (cycle - 1) % 8; - switch (tile_fetch_cycle) { - case 1: - read_addr = 0x2000 + ((ppu_state.scanline / 8) * 0x20) + (ppu_state.cycle / 8); - ppu_state.next_tile_fetch.nametable = ppu_read(read_addr); - break; - case 3: - read_addr = 0x23c0 + (ppu_state.cycle % 8); - ppu_state.next_tile_fetch.attribute_table = ppu_read(read_addr); - break; - case 5: - read_addr = 0x1000 * ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_BG_PATTERN_TABLE_ADDR); - read_addr += ppu_state.next_tile_fetch.nametable * 16 + ppu_state.scanline % 8; - ppu_state.next_tile_fetch.pattern_table_tile_low = ppu_read(read_addr); - break; - case 7: - read_addr = 0x1000 * ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_BG_PATTERN_TABLE_ADDR); - read_addr += ppu_state.next_tile_fetch.nametable * 16 + ppu_state.scanline % 8 + 8; - ppu_state.next_tile_fetch.pattern_table_tile_high = ppu_read(read_addr); - ppu_state.tile_fetch = ppu_state.next_tile_fetch; - break; - default: - break; + if (cycle == 256) { + if ((ppu_state.ppu_address & 0x7000) != 0x7000) { + ppu_state.ppu_address += 0x1000; + } else { + ppu_state.ppu_address &= ~0x7000; + + if ((ppu_state.ppu_address & 0x3e0) != 0x3a0) { + ppu_state.ppu_address += 0x20; + } else { + ppu_state.ppu_address &= ~0x3e0; +// ppu_state.ppu_address ^= 0x0800; } } } } else if (cycle <= 320) { // OAMADDR is cleared on sprite loading for pre-render and visible lines ppu_write_reg(PPU_REGISTER_OAM_ADDR, 0); + + if (cycle == 257) { + ppu_state.ppu_address = (ppu_state.ppu_address & 0xfbe0) | (ppu_state.temp_ppu_addr & ~0xfbe0); + ppu_state.x_scroll = 0; + } } else if (cycle <= 336) { + ppu_fetch_tile(); } } @@ -182,6 +252,8 @@ void ppu_cycle() { ppu_post_render(ppu_state.cycle, ppu_state.scanline); } else if (ppu_state.scanline == PPU_PRE_RENDER_LINE) { ppu_pre_render(ppu_state.cycle); + ppu_visible_frame(ppu_state.cycle); + ppu_state.ppu_address = ppu_state.temp_ppu_addr; } int frame_width = PPU_LINE_WIDTH; @@ -200,101 +272,13 @@ void ppu_cycle() { ppu_state.scanline++; } - if (ppu_state.scanline >= frame_height) { + if (ppu_state.scanline > frame_height) { ppu_state.scanline = 0; ppu_state.frame++; ppu_state.odd_frame = !ppu_state.odd_frame; } } -bool ppu_read_flag(size_t reg, byte mask) { - return ppu_state.registers[reg] & mask; -} - -byte ppu_read_reg(byte reg) { - assert(reg >= 0); - assert(reg <= PPU_REGISTER_SIZE); - - if (reg == PPU_REGISTER_STATUS) { - ppu_state.w = false; - byte status = ppu_state.registers[PPU_REGISTER_STATUS]; - ppu_state.registers[PPU_REGISTER_STATUS] &= ~PPU_STATUS_VBLANK; - return status; - } - - if (reg == PPU_REGISTER_DATA) { - // Access to VRAM memory is slow, so reading it a first time generally return the memory at the previous address. - // So we get the data first, then update the register. - byte data = ppu_state.registers[reg]; - ppu_state.registers[reg] = ppu_read(ppu_state.ppu_address); - if (ppu_state.ppu_address > 0x3eff) { - // But the palette data is returned immediately - data = ppu_state.registers[reg]; - } - - // We then need to increment the VRAM address - byte increment = 1; - if (ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_VRAM_ADDR_INCREMENT)) { - increment = 32; - } - - ppu_state.ppu_address += increment; - ppu_state.ppu_address %= PPU_VRAM_SIZE; - - return data; - } - - return ppu_state.registers[reg]; -} - -void ppu_write_reg(byte reg, byte data) { - ppu_state.registers[reg] = data; - - if (reg == PPU_REGISTER_CTRL && ppu_read_flag(PPU_REGISTER_STATUS, PPU_STATUS_VBLANK) && - !ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_GEN_VBLANK_NMI) && - data & PPU_CTRL_GEN_VBLANK_NMI) { - // The VBlank flag is still set, and the GEN_VBLANK_NMI was set from 0 to 1 - cpu_trigger_nmi(); - } else if (reg == PPU_REGISTER_SCROLL) { - if (!ppu_state.w) { - ppu_state.x = data; - } else { - ppu_state.t = data; - } - ppu_state.w = !ppu_state.w; - } else if (reg == PPU_REGISTER_ADDR) { - address addr = ppu_state.ppu_address; - if (!ppu_state.w) { - addr = data; - } else { - addr = data | (addr << 8); - } - - ppu_state.ppu_address = addr % PPU_VRAM_SIZE; - ppu_state.w = !ppu_state.w; - } else if (reg == PPU_REGISTER_DATA) { - address addr = ppu_state.ppu_address; - ppu_write(addr, data); - - byte increment = 1; - if (ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_VRAM_ADDR_INCREMENT)) { - increment = 32; - } - - addr += increment; - ppu_state.ppu_address = addr % PPU_VRAM_SIZE; - } else if (reg == PPU_REGISTER_OAM_DATA) { - byte oam_addr = ppu_state.registers[PPU_REGISTER_OAM_ADDR]; - ppu_write_reg(PPU_REGISTER_OAM_ADDR, oam_addr + 1); - } - - ppu_state.registers[reg] = data; -} - -void ppu_write_reg_oam_addr(byte data) { - ppu_state.oam_dma_register = data; -} - void ppu_write(address addr, byte data) { assert(addr < PPU_VRAM_SIZE); @@ -378,4 +362,135 @@ byte ppu_read(address addr) { // assert(false); return 0; +} + +bool ppu_read_flag(size_t reg, byte mask) { + return ppu_state.registers[reg] & mask; +} + +/* + * d8888b. d88888b d888b d888888b .d8888. d888888b d88888b d8888b. .d8888. + * 88 `8D 88' 88' Y8b `88' 88' YP `~~88~~' 88' 88 `8D 88' YP + * 88oobY' 88ooooo 88 88 `8bo. 88 88ooooo 88oobY' `8bo. + * 88`8b 88~~~~~ 88 ooo 88 `Y8b. 88 88~~~~~ 88`8b `Y8b. + * 88 `88. 88. 88. ~8~ .88. db 8D 88 88. 88 `88. db 8D + * 88 YD Y88888P Y888P Y888888P `8888Y' YP Y88888P 88 YD `8888Y' + */ + +void ppu_write_ctrl(byte data) { + ppu_state.temp_ppu_addr = (ppu_state.temp_ppu_addr & 0xf3ff) | ((data & PPU_CTRL_BASE_NAMETABLE_ADDR) << 10); + ppu_state.bg_pattern_table_addr = (data & PPU_CTRL_BG_PATTERN_TABLE_ADDR) << 0x8; // 0x0000 or 0x1000 + + ppu_state.ppu_addr_increment = (data & PPU_CTRL_VRAM_ADDR_INCREMENT) ? 0x20 : 1; + + if (ppu_read_flag(PPU_REGISTER_STATUS, PPU_STATUS_VBLANK) && + !ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_GEN_VBLANK_NMI) && + data & PPU_CTRL_GEN_VBLANK_NMI) { + // The VBlank flag is still set, and the GEN_VBLANK_NMI was set from 0 to 1 + cpu_trigger_nmi(); + } +} + +void ppu_write_scroll(byte data) { + ppu_state.w = !ppu_state.w; + + // TODO: Understand and fix with a game using scrolling + if (ppu_state.w) { + ppu_state.temp_ppu_addr = (ppu_state.temp_ppu_addr & 0xffe0) | (data >> 3); + ppu_state.fine_x_scroll = data & 0x7; + } else { + ppu_state.temp_ppu_addr = ppu_state.temp_ppu_addr & 0xc1f; + ppu_state.temp_ppu_addr |= (data & 0xf8) << 2; + ppu_state.temp_ppu_addr |= (data & 0x7) << 12; + ppu_state.y_scroll = data; + } +} + +void ppu_write_addr(byte data) { + ppu_state.w = !ppu_state.w; + + if (ppu_state.w) { + ppu_state.temp_ppu_addr = (ppu_state.temp_ppu_addr & 0x00ff) | (data & 0x3f) << 8; + } else { + ppu_state.temp_ppu_addr = (ppu_state.temp_ppu_addr & 0xff00) | data; + ppu_state.ppu_address = ppu_state.temp_ppu_addr; + } +} + +void ppu_write_data(byte data) { + address addr = ppu_state.ppu_address; + ppu_write(addr, data); + + ppu_state.ppu_address = addr + ppu_state.ppu_addr_increment; +} + +void ppu_write_oamdata(byte data) { + byte oam_addr = ppu_state.registers[PPU_REGISTER_OAM_ADDR]; + ppu_write_reg(PPU_REGISTER_OAM_ADDR, oam_addr + 1); +} + +void ppu_write_oamaddr(byte data) { + ppu_state.oam_dma_register = data; +} + +void ppu_write_reg(byte reg, byte data) { + assert(reg >= 0); + assert(reg <= PPU_REGISTER_SIZE); + + switch (reg) { + case PPU_REGISTER_CTRL: + ppu_write_ctrl(data); + break; + case PPU_REGISTER_SCROLL: + ppu_write_scroll(data); + break; + case PPU_REGISTER_ADDR: + ppu_write_addr(data); + break; + case PPU_REGISTER_DATA: + ppu_write_data(data); + break; + case PPU_REGISTER_OAM_DATA: + ppu_write_oamdata(data); + break; + default: + break; + } + + ppu_state.registers[reg] = data; +} + +byte ppu_read_status() { + ppu_state.w = false; + byte status = ppu_state.registers[PPU_REGISTER_STATUS]; + ppu_state.registers[PPU_REGISTER_STATUS] &= ~PPU_STATUS_VBLANK; + return status; +} + +byte ppu_read_data() { + // Access to VRAM memory is slow, so reading it a first time generally return the memory at the previous address. + // So we get the data first, then update the register. + byte data = ppu_state.registers[PPU_REGISTER_DATA]; + ppu_state.registers[PPU_REGISTER_DATA] = ppu_read(ppu_state.ppu_address); + if (ppu_state.ppu_address > 0x3eff) { + // But the palette data is returned immediately + data = ppu_state.registers[PPU_REGISTER_DATA]; + } + + ppu_state.ppu_address = ppu_state.ppu_address + ppu_state.ppu_addr_increment; + return data; +} + +byte ppu_read_reg(byte reg) { + assert(reg >= 0); + assert(reg <= PPU_REGISTER_SIZE); + + switch (reg) { + case PPU_REGISTER_STATUS: + return ppu_read_status(); + case PPU_REGISTER_DATA: + return ppu_read_data(); + default: + return ppu_state.registers[reg]; + } } \ No newline at end of file