PPU rewrite
This commit is contained in:
parent
dcb01b4c6a
commit
eb0e0a42c0
|
@ -98,7 +98,7 @@ void mem_set_byte(address addr, byte data) {
|
||||||
byte ppu_reg = relative_addr % 8;
|
byte ppu_reg = relative_addr % 8;
|
||||||
ppu_write_reg(ppu_reg, data);
|
ppu_write_reg(ppu_reg, data);
|
||||||
} else if (addr == PPU_REGISTER_OAM_DMA_ADDR) {
|
} 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
|
// Writing to this address triggers an upload to the PPU memory
|
||||||
cpu_trigger_oam_dma();
|
cpu_trigger_oam_dma();
|
||||||
|
|
|
@ -30,7 +30,7 @@ bool gui_init() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.debug_enabled = true;
|
gui.debug_enabled = false;
|
||||||
|
|
||||||
main_window_init(&gui.main_window, gui.font);
|
main_window_init(&gui.main_window, gui.font);
|
||||||
|
|
||||||
|
|
|
@ -35,13 +35,13 @@ void main_window_render_delay(SDL_Renderer *renderer) {
|
||||||
char_map_render(renderer, buffer);
|
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);
|
SDL_RenderClear(window->sdl_context.renderer);
|
||||||
|
|
||||||
unsigned int frame_buffer[240 * 256];
|
unsigned int frame_buffer[240 * 256];
|
||||||
|
|
||||||
for (int i = 0; i < 240 * 256; i++) {
|
for (int i = 0; i < 240 * 256; i++) {
|
||||||
PpuPixel pixel = pixels[i];
|
PPUPixel pixel = pixels[i];
|
||||||
|
|
||||||
unsigned int *data = &frame_buffer[i];
|
unsigned int *data = &frame_buffer[i];
|
||||||
*data = 0xff000000;
|
*data = 0xff000000;
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
#include "../include/ppu.h"
|
#include "../include/ppu.h"
|
||||||
#include "window.h"
|
#include "window.h"
|
||||||
|
|
||||||
#define MAIN_WINDOW_WIDTH 240
|
#define MAIN_WINDOW_WIDTH 256
|
||||||
#define MAIN_WINDOW_HEIGHT 256
|
#define MAIN_WINDOW_HEIGHT 240
|
||||||
#define MAIN_WINDOW_SCALE 2
|
#define MAIN_WINDOW_SCALE 2
|
||||||
|
|
||||||
typedef struct nes_main_window {
|
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_init(NesMainWindow *window, TTF_Font *font);
|
||||||
void main_window_uninit(NesMainWindow *window);
|
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);
|
void main_window_present(NesMainWindow *window);
|
||||||
|
|
||||||
#endif //NES_EMULATOR_MAIN_WINDOW_H
|
#endif //NES_EMULATOR_MAIN_WINDOW_H
|
||||||
|
|
|
@ -73,11 +73,17 @@ typedef struct ppu_pixel {
|
||||||
byte r;
|
byte r;
|
||||||
byte g;
|
byte g;
|
||||||
byte b;
|
byte b;
|
||||||
} PpuPixel;
|
} PPUPixel;
|
||||||
|
|
||||||
|
typedef struct ppu_tile_queue {
|
||||||
|
PPUTileFetch first_fetch;
|
||||||
|
PPUTileFetch second_fetch;
|
||||||
|
PPUTileFetch displayed_fetch;
|
||||||
|
} PPUTileQueue;
|
||||||
|
|
||||||
typedef struct ppu {
|
typedef struct ppu {
|
||||||
PPUMemory memory;
|
PPUMemory memory;
|
||||||
PpuPixel pixels[256 * 240];
|
PPUPixel pixels[256 * 240];
|
||||||
|
|
||||||
byte registers[8];
|
byte registers[8];
|
||||||
byte oam_dma_register;
|
byte oam_dma_register;
|
||||||
|
@ -88,10 +94,19 @@ typedef struct ppu {
|
||||||
byte x;
|
byte x;
|
||||||
bool w;
|
bool w;
|
||||||
|
|
||||||
address ppu_address;
|
byte x_scroll;
|
||||||
|
byte fine_x_scroll;
|
||||||
|
byte y_scroll;
|
||||||
|
byte ppu_addr_increment;
|
||||||
|
|
||||||
PPUTileFetch tile_fetch;
|
address ppu_address;
|
||||||
PPUTileFetch next_tile_fetch;
|
address temp_ppu_addr;
|
||||||
|
address bg_pattern_table_addr;
|
||||||
|
|
||||||
|
PPUTileFetch fetch;
|
||||||
|
PPUTileQueue tile_queue;
|
||||||
|
// PPUTileFetch tile_fetch;
|
||||||
|
// PPUTileFetch fetch;
|
||||||
unsigned long frame;
|
unsigned long frame;
|
||||||
unsigned int scanline;
|
unsigned int scanline;
|
||||||
unsigned int cycle;
|
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(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);
|
void ppu_write(address addr, byte data);
|
||||||
|
|
||||||
|
|
2
main.c
2
main.c
|
@ -23,7 +23,7 @@
|
||||||
#include "gui.h"
|
#include "gui.h"
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
char *rom_path = "../test_roms/dk_japan.nes";
|
char *rom_path = "../test_roms/dk_jp.nes";
|
||||||
log_set_level(LOG_INFO);
|
log_set_level(LOG_INFO);
|
||||||
|
|
||||||
if (!gui_init()) {
|
if (!gui_init()) {
|
||||||
|
|
403
ppu/ppu.c
403
ppu/ppu.c
|
@ -19,7 +19,6 @@
|
||||||
#include "../include/ppu.h"
|
#include "../include/ppu.h"
|
||||||
#include "../cpu/cpu.h"
|
#include "../cpu/cpu.h"
|
||||||
#include "../include/rom.h"
|
#include "../include/rom.h"
|
||||||
#include "../gui/gui.h"
|
|
||||||
#include "pattern_table.h"
|
#include "pattern_table.h"
|
||||||
|
|
||||||
#define PPU_VISIBLE_FRAME_END 240
|
#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;
|
return scanline * PPU_VISIBLE_FRAME_END + cycle;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline byte ppu_pixel_get_mask(unsigned int cycle) {
|
static inline byte ppu_pixel_get_mask(unsigned int tile_fine_x) {
|
||||||
byte tile_fine_x = (cycle - 1) % 8;
|
|
||||||
return 1 << (PATTERN_TILE_SIZE - tile_fine_x - 1);
|
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) {
|
static inline void ppu_pixel_set_color(PPUPixel *pixel, byte pt_low, byte pt_high) {
|
||||||
byte p1_byte = pt_low & bitmask;
|
for (int i = 0; i < 8; i++) {
|
||||||
byte p2_byte = pt_high & bitmask;
|
PPUPixel *spixel = pixel + i;
|
||||||
|
byte bitmask = ppu_pixel_get_mask(i);
|
||||||
|
|
||||||
if (p1_byte && p2_byte) {
|
byte p1_byte = pt_low & bitmask;
|
||||||
pixel->r = 255;
|
byte p2_byte = pt_high & bitmask;
|
||||||
pixel->g = 255;
|
|
||||||
pixel->b = 255;
|
if (p1_byte && p2_byte) {
|
||||||
} else if (p2_byte) {
|
spixel->r = 255;
|
||||||
pixel->r = 255;
|
spixel->g = 255;
|
||||||
pixel->g = 0;
|
spixel->b = 255;
|
||||||
pixel->b = 0;
|
} else if (p2_byte) {
|
||||||
} else if (p1_byte) {
|
spixel->r = 255;
|
||||||
pixel->r = 0;
|
spixel->g = 0;
|
||||||
pixel->g = 255;
|
spixel->b = 0;
|
||||||
pixel->b = 255;
|
} else if (p1_byte) {
|
||||||
} else {
|
spixel->r = 0;
|
||||||
pixel->r = 0;
|
spixel->g = 255;
|
||||||
pixel->g = 0;
|
spixel->b = 255;
|
||||||
pixel->b = 0;
|
} else {
|
||||||
|
spixel->r = 0;
|
||||||
|
spixel->g = 0;
|
||||||
|
spixel->b = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ppu_draw_tile() {
|
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);
|
unsigned int pixel_index = ppu_state.scanline * PPU_VISIBLE_FRAME_END + ppu_state.cycle;
|
||||||
PpuPixel *pixel = &ppu_state.pixels[pixel_index];
|
PPUPixel *pixel = &ppu_state.pixels[pixel_index];
|
||||||
byte pixel_mask = ppu_pixel_get_mask(ppu_state.cycle);
|
ppu_pixel_set_color(pixel, fetch.pattern_table_tile_low, fetch.pattern_table_tile_high);
|
||||||
ppu_pixel_set_color(pixel, tile_fetch.pattern_table_tile_low, tile_fetch.pattern_table_tile_high, pixel_mask);
|
}
|
||||||
|
|
||||||
|
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) {
|
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) {
|
if (cycle == 0) {
|
||||||
// Idle...
|
// Idle...
|
||||||
} else if (cycle < 256) {
|
} else if (cycle <= 256) {
|
||||||
if (ppu_read_flag(PPU_REGISTER_MASK, PPU_MASK_SHOW_BG)) {
|
ppu_fetch_tile();
|
||||||
ppu_draw_tile();
|
|
||||||
|
|
||||||
if (cycle <= 248) {
|
if (cycle == 256) {
|
||||||
address read_addr;
|
if ((ppu_state.ppu_address & 0x7000) != 0x7000) {
|
||||||
byte tile_fetch_cycle = (cycle - 1) % 8;
|
ppu_state.ppu_address += 0x1000;
|
||||||
switch (tile_fetch_cycle) {
|
} else {
|
||||||
case 1:
|
ppu_state.ppu_address &= ~0x7000;
|
||||||
read_addr = 0x2000 + ((ppu_state.scanline / 8) * 0x20) + (ppu_state.cycle / 8);
|
|
||||||
ppu_state.next_tile_fetch.nametable = ppu_read(read_addr);
|
if ((ppu_state.ppu_address & 0x3e0) != 0x3a0) {
|
||||||
break;
|
ppu_state.ppu_address += 0x20;
|
||||||
case 3:
|
} else {
|
||||||
read_addr = 0x23c0 + (ppu_state.cycle % 8);
|
ppu_state.ppu_address &= ~0x3e0;
|
||||||
ppu_state.next_tile_fetch.attribute_table = ppu_read(read_addr);
|
// ppu_state.ppu_address ^= 0x0800;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (cycle <= 320) {
|
} else if (cycle <= 320) {
|
||||||
// OAMADDR is cleared on sprite loading for pre-render and visible lines
|
// OAMADDR is cleared on sprite loading for pre-render and visible lines
|
||||||
ppu_write_reg(PPU_REGISTER_OAM_ADDR, 0);
|
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) {
|
} else if (cycle <= 336) {
|
||||||
|
ppu_fetch_tile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,6 +252,8 @@ void ppu_cycle() {
|
||||||
ppu_post_render(ppu_state.cycle, ppu_state.scanline);
|
ppu_post_render(ppu_state.cycle, ppu_state.scanline);
|
||||||
} else if (ppu_state.scanline == PPU_PRE_RENDER_LINE) {
|
} else if (ppu_state.scanline == PPU_PRE_RENDER_LINE) {
|
||||||
ppu_pre_render(ppu_state.cycle);
|
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;
|
int frame_width = PPU_LINE_WIDTH;
|
||||||
|
@ -200,101 +272,13 @@ void ppu_cycle() {
|
||||||
ppu_state.scanline++;
|
ppu_state.scanline++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ppu_state.scanline >= frame_height) {
|
if (ppu_state.scanline > frame_height) {
|
||||||
ppu_state.scanline = 0;
|
ppu_state.scanline = 0;
|
||||||
ppu_state.frame++;
|
ppu_state.frame++;
|
||||||
ppu_state.odd_frame = !ppu_state.odd_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) {
|
void ppu_write(address addr, byte data) {
|
||||||
assert(addr < PPU_VRAM_SIZE);
|
assert(addr < PPU_VRAM_SIZE);
|
||||||
|
|
||||||
|
@ -378,4 +362,135 @@ byte ppu_read(address addr) {
|
||||||
|
|
||||||
// assert(false);
|
// assert(false);
|
||||||
return 0;
|
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];
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue