PPU rewrite

This commit is contained in:
FyloZ 2024-06-21 13:47:28 -04:00
parent dcb01b4c6a
commit eb0e0a42c0
Signed by: william
GPG Key ID: 835378AE9AF4AE97
7 changed files with 288 additions and 158 deletions

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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);

2
main.c
View File

@ -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()) {

403
ppu/ppu.c
View File

@ -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);
@ -379,3 +363,134 @@ 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];
}
}