More debug tools and finally fix rendering!
This commit is contained in:
parent
1add13dd20
commit
32c9cebd19
|
@ -155,3 +155,4 @@ environment_run.sh.env
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/cmake,clion,conan
|
# End of https://www.toptal.com/developers/gitignore/api/cmake,clion,conan
|
||||||
/test_roms/
|
/test_roms/
|
||||||
|
/logs/
|
|
@ -9,9 +9,11 @@ only tested on Linux. Here is how to run the project:
|
||||||
- Run the emulator: ```./nes_emulator```
|
- Run the emulator: ```./nes_emulator```
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
- `p`: Pauses the emulation
|
- `P`: Pauses the emulation (CPU)
|
||||||
- `o`: Go to the next palette in the pattern viewer
|
- `Ctrl+P`: Pauses the emulation and the PPU rendering
|
||||||
- `t`: Show tile IDs
|
- `O`: Go to the next palette in the pattern viewer
|
||||||
|
- `T`: Show tile IDs
|
||||||
|
- `N`: Switch low/high pattern data
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- GCC compiler
|
- GCC compiler
|
||||||
|
|
|
@ -41,7 +41,7 @@ void cpu_init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_registers(byte op, unsigned long cycle_count) {
|
void print_registers(byte op, unsigned long cycle_count) {
|
||||||
log_debug("%#02x %#02x %s \t A:%#02x X:%#02x Y:%#02x F:%#02x SP:%#02x \t [%d]",
|
log_trace("%#02x %#02x %s \t A:%#02x X:%#02x Y:%#02x F:%#02x SP:%#02x \t [%d]",
|
||||||
cpu_state.program_counter - 1, // The PC as been incremented when printing
|
cpu_state.program_counter - 1, // The PC as been incremented when printing
|
||||||
op,
|
op,
|
||||||
get_op_code_name(op),
|
get_op_code_name(op),
|
||||||
|
|
|
@ -25,17 +25,17 @@ typedef struct dbg_nametable {
|
||||||
} DebugNameTable;
|
} DebugNameTable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the debug nametable.
|
* Initializes the debug tile_id.
|
||||||
*/
|
*/
|
||||||
void dbg_nametable_init();
|
void dbg_nametable_init();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the debug nametable. Updates the tiles from the PPU memory.
|
* Updates the debug tile_id. Updates the tiles from the PPU memory.
|
||||||
*/
|
*/
|
||||||
void dbg_nametable_update();
|
void dbg_nametable_update();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a nametable bank to a buffer.
|
* Renders a tile_id bank to a buffer.
|
||||||
* @param bank
|
* @param bank
|
||||||
* @param buffer
|
* @param buffer
|
||||||
*/
|
*/
|
||||||
|
|
44
gui/gui.c
44
gui/gui.c
|
@ -76,25 +76,45 @@ void gui_post_sysinit() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool lctrl = false;
|
||||||
|
|
||||||
int gui_input() {
|
int gui_input() {
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
PPUDebugFlags *ppu_debug = &ppu_get_state()->debug;
|
||||||
|
#endif
|
||||||
|
|
||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE) {
|
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_KEYUP) {
|
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_LCTRL) {
|
||||||
if (event.key.keysym.sym == SDLK_p) {
|
lctrl = true;
|
||||||
system_toggle_pause();
|
|
||||||
} else {
|
|
||||||
#if DEBUG
|
|
||||||
if (event.key.keysym.sym == SDLK_t) {
|
|
||||||
PPUDebugFlags *ppu_debug = &ppu_get_state()->debug;
|
|
||||||
ppu_debug->flags.tile_debugger = !ppu_debug->flags.tile_debugger;
|
|
||||||
} else {
|
|
||||||
pattern_window_key_up(&gui.pattern_window, event.key.keysym.sym);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.type == SDL_KEYUP) {
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_LCTRL:
|
||||||
|
lctrl = false;
|
||||||
|
break;
|
||||||
|
case SDLK_p:
|
||||||
|
system_toggle_pause(lctrl);
|
||||||
|
break;
|
||||||
|
#if DEBUG
|
||||||
|
case SDLK_t:
|
||||||
|
ppu_debug->flags.tile_debugger = !ppu_debug->flags.tile_debugger;
|
||||||
|
break;
|
||||||
|
case SDLK_n:
|
||||||
|
ppu_debug->flags.tile_debugger_pattern_half = (ppu_debug->flags.tile_debugger_pattern_half + 1) % 3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pattern_window_key_up(&gui.pattern_window, event.key.keysym.sym);
|
||||||
|
break;
|
||||||
|
#else
|
||||||
|
default:
|
||||||
|
break;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,6 +124,8 @@ int gui_input() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void gui_render() {
|
void gui_render() {
|
||||||
|
main_window_render(&gui.main_window, ppu_get_state()->pixels);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
dbg_palette_init();
|
dbg_palette_init();
|
||||||
pattern_window_render(&gui.pattern_window);
|
pattern_window_render(&gui.pattern_window);
|
||||||
|
@ -113,8 +135,6 @@ void gui_render() {
|
||||||
|
|
||||||
gui.tick++;
|
gui.tick++;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
main_window_render(&gui.main_window, ppu_get_state()->pixels);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void gui_present() {
|
void gui_present() {
|
||||||
|
|
|
@ -36,7 +36,7 @@ void main_window_render_delay(SDL_Renderer *renderer) {
|
||||||
|
|
||||||
void main_window_render(NesMainWindow *window, pixel *pixels) {
|
void main_window_render(NesMainWindow *window, pixel *pixels) {
|
||||||
SDL_RenderClear(window->sdl_context.renderer);
|
SDL_RenderClear(window->sdl_context.renderer);
|
||||||
SDL_UpdateTexture(window->texture, NULL, pixels, 240 * sizeof(unsigned int));
|
SDL_UpdateTexture(window->texture, NULL, pixels, 256 * sizeof(pixel));
|
||||||
SDL_RenderCopy(window->sdl_context.renderer, window->texture, NULL, NULL);
|
SDL_RenderCopy(window->sdl_context.renderer, window->texture, NULL, NULL);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
|
@ -25,4 +25,8 @@ void main_window_uninit(NesMainWindow *window);
|
||||||
void main_window_render(NesMainWindow *window, pixel* pixels);
|
void main_window_render(NesMainWindow *window, pixel* pixels);
|
||||||
void main_window_present(NesMainWindow *window);
|
void main_window_present(NesMainWindow *window);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
void main_window_render_delay(SDL_Renderer *renderer);
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif //NES_EMULATOR_MAIN_WINDOW_H
|
#endif //NES_EMULATOR_MAIN_WINDOW_H
|
||||||
|
|
|
@ -63,7 +63,7 @@ typedef struct ppu_memory {
|
||||||
} PPUMemory;
|
} PPUMemory;
|
||||||
|
|
||||||
typedef struct ppu_tile_fetch {
|
typedef struct ppu_tile_fetch {
|
||||||
byte nametable;
|
byte tile_id;
|
||||||
byte attribute_table;
|
byte attribute_table;
|
||||||
byte pattern_table_tile_low;
|
byte pattern_table_tile_low;
|
||||||
byte pattern_table_tile_high;
|
byte pattern_table_tile_high;
|
||||||
|
@ -73,6 +73,7 @@ typedef struct ppu_tile_fetch {
|
||||||
typedef union {
|
typedef union {
|
||||||
struct {
|
struct {
|
||||||
byte tile_debugger: 1;
|
byte tile_debugger: 1;
|
||||||
|
byte tile_debugger_pattern_half: 2;
|
||||||
} flags;
|
} flags;
|
||||||
byte flags_byte;
|
byte flags_byte;
|
||||||
} PPUDebugFlags;
|
} PPUDebugFlags;
|
||||||
|
|
|
@ -26,6 +26,7 @@ typedef struct system {
|
||||||
Mapper mapper;
|
Mapper mapper;
|
||||||
unsigned long cycle_count;
|
unsigned long cycle_count;
|
||||||
bool paused;
|
bool paused;
|
||||||
|
bool ppu_paused;
|
||||||
} System;
|
} System;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +41,7 @@ void system_next_frame();
|
||||||
/**
|
/**
|
||||||
* Toggle pause for the system. If not paused, CPU and PPU cycles will be stopped until this method is called again.
|
* Toggle pause for the system. If not paused, CPU and PPU cycles will be stopped until this method is called again.
|
||||||
*/
|
*/
|
||||||
void system_toggle_pause();
|
void system_toggle_pause(bool pause_ppu);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* De-initialize the components of a system.
|
* De-initialize the components of a system.
|
||||||
|
|
67
main.c
67
main.c
|
@ -15,17 +15,80 @@
|
||||||
*
|
*
|
||||||
* =====================================================================================
|
* =====================================================================================
|
||||||
*/
|
*/
|
||||||
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
#include "include/rom.h"
|
#include "include/rom.h"
|
||||||
#include "include/system.h"
|
#include "include/system.h"
|
||||||
#include "gui.h"
|
#include "gui.h"
|
||||||
|
|
||||||
|
struct log_files {
|
||||||
|
FILE *info;
|
||||||
|
FILE *debug;
|
||||||
|
};
|
||||||
|
struct log_files log_files;
|
||||||
|
|
||||||
|
FILE *add_log_file(char *name, int level) {
|
||||||
|
char *full_name = malloc((5 + strlen(name)) * sizeof(char));
|
||||||
|
strcpy(full_name, "logs/");
|
||||||
|
strcat(full_name, name);
|
||||||
|
|
||||||
|
FILE *log_file = fopen(full_name, "w");
|
||||||
|
if (!log_file) {
|
||||||
|
perror("fopen");
|
||||||
|
int fopen_err = errno;
|
||||||
|
log_error("Failed to open log file '%s': %s", full_name, strerror(fopen_err));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_add_fp(log_file, level);
|
||||||
|
return log_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_logging() {
|
||||||
|
log_set_level(LOG_DEBUG);
|
||||||
|
|
||||||
|
// Print current working directory
|
||||||
|
char cwd[256];
|
||||||
|
if (getcwd(cwd, 256) == NULL) {
|
||||||
|
int getcwd_error = errno;
|
||||||
|
log_error("Failed to read current working directory: %s", strerror(getcwd_error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log_info("Using working directory '%s'", cwd);
|
||||||
|
|
||||||
|
// Make sure log directory exists
|
||||||
|
char *folder_name = "logs/";
|
||||||
|
struct stat sb;
|
||||||
|
if (stat(folder_name, &sb) != 0 || !S_ISDIR(sb.st_mode)) {
|
||||||
|
if (mkdir(folder_name, 0755) == 0) {
|
||||||
|
log_debug("Created logs folder");
|
||||||
|
} else {
|
||||||
|
log_error("Failed to create logs folder: %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_files.info = add_log_file("nes.log", LOG_INFO);
|
||||||
|
#if DEBUG
|
||||||
|
log_files.debug = add_log_file("nes_debug.log", LOG_DEBUG);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_logging() {
|
||||||
|
fclose(log_files.info);
|
||||||
|
fclose(log_files.debug);
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
char *rom_path = "./test_roms/dk_japan.nes";
|
char *rom_path = "./test_roms/dk_japan.nes";
|
||||||
// char *rom_path = "./test_roms/smb.nes";
|
// char *rom_path = "./test_roms/smb.nes";
|
||||||
log_set_level(LOG_INFO);
|
|
||||||
|
init_logging();
|
||||||
|
|
||||||
if (!gui_init()) {
|
if (!gui_init()) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
@ -60,5 +123,7 @@ int main() {
|
||||||
rom_unload();
|
rom_unload();
|
||||||
gui_uninit();
|
gui_uninit();
|
||||||
|
|
||||||
|
close_logging();
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
|
@ -96,4 +96,6 @@
|
||||||
COLOR_LIST_(3) \
|
COLOR_LIST_(3) \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef pixel color_list[0x40];
|
||||||
|
|
||||||
#endif //NES_EMULATOR_COLORS_H
|
#endif //NES_EMULATOR_COLORS_H
|
||||||
|
|
159
ppu/ppu.c
159
ppu/ppu.c
|
@ -7,7 +7,7 @@
|
||||||
// 3. Implement PPUADDR/PPUDATA so that the nametables are filled out
|
// 3. Implement PPUADDR/PPUDATA so that the nametables are filled out
|
||||||
// 4. Now you have some data your PPU can actually read for rendering background. Render it scanline by scanline - just follow the wiki on this. Maybe the timing will be bad, it doesn't matter for this game. Start off with rendering tiles based on the pattern table ID, don't try and fetch patterns.
|
// 4. Now you have some data your PPU can actually read for rendering background. Render it scanline by scanline - just follow the wiki on this. Maybe the timing will be bad, it doesn't matter for this game. Start off with rendering tiles based on the pattern table ID, don't try and fetch patterns.
|
||||||
// 5. Fix the inevitable bugs with your PPUDATA implementation until you see a blocky version of the Donkey Kong screen.
|
// 5. Fix the inevitable bugs with your PPUDATA implementation until you see a blocky version of the Donkey Kong screen.
|
||||||
// 6. Now fetch pattern table data using the nametable data. If it looks "wrong" make sure you are consuming the background address flag. Start off with black and white, then pick two colors to mix for the two bits. Now you should have something like https://i.imgur.com/7OIpHgd.png
|
// 6. Now fetch pattern table data using the tile_id data. If it looks "wrong" make sure you are consuming the background address flag. Start off with black and white, then pick two colors to mix for the two bits. Now you should have something like https://i.imgur.com/7OIpHgd.png
|
||||||
// 7. (Optional) implement palette reads (I'm skipping this for now).
|
// 7. (Optional) implement palette reads (I'm skipping this for now).
|
||||||
// 8. Implement OAMDMA (and OAMDATA I guess, I implemented one on top of the other)
|
// 8. Implement OAMDMA (and OAMDATA I guess, I implemented one on top of the other)
|
||||||
// 9. Now you should have sprite data to render. Implement the logic for copying from primary OAM to scanline OAM. I'm doing it all as one step (not smearing it over up to 256 cycles like the actual hardware). Skip the confusing sprite overflow junk.
|
// 9. Now you should have sprite data to render. Implement the logic for copying from primary OAM to scanline OAM. I'm doing it all as one step (not smearing it over up to 256 cycles like the actual hardware). Skip the confusing sprite overflow junk.
|
||||||
|
@ -21,18 +21,20 @@
|
||||||
#include "../include/rom.h"
|
#include "../include/rom.h"
|
||||||
#include "colors.h"
|
#include "colors.h"
|
||||||
#include "tile_debugger.h"
|
#include "tile_debugger.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
#define PPU_VISIBLE_FRAME_END 240
|
#define PPU_SCANLINE_VISIBLE_MAX 240
|
||||||
#define PPU_POST_RENDER_LINE_START PPU_VISIBLE_FRAME_END
|
#define PPU_SCANLINE_POST_RENDER_MIN PPU_SCANLINE_VISIBLE_MAX
|
||||||
#define PPU_POST_RENDER_LINE_END 242
|
#define PPU_SCANLINE_POST_RENDER_MAX 242
|
||||||
#define PPU_PRE_RENDER_LINE 261
|
#define PPU_SCANLINE_PRE_RENDER 261
|
||||||
#define PPU_LINE_END PPU_PRE_RENDER_LINE
|
#define PPU_SCANLINE_MAX PPU_SCANLINE_PRE_RENDER
|
||||||
#define PPU_LINE_WIDTH 340
|
#define PPU_CYCLE_MAX 340
|
||||||
|
#define PPU_CYCLE_VISIBLE_MAX 256
|
||||||
|
|
||||||
#define NAMETABLE_TILE_SIZE 8
|
#define NAMETABLE_TILE_SIZE 8
|
||||||
|
|
||||||
PPU ppu_state;
|
PPU ppu_state;
|
||||||
pixel color_list[0x40] = COLOR_LIST;
|
color_list colors = COLOR_LIST;
|
||||||
|
|
||||||
void ppu_init() {
|
void ppu_init() {
|
||||||
memset(&ppu_state, 0, sizeof(PPU));
|
memset(&ppu_state, 0, sizeof(PPU));
|
||||||
|
@ -50,12 +52,14 @@ void ppu_status_set(byte mask, bool enabled) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int hits = 0;
|
||||||
void ppu_trigger_vbl_nmi() {
|
void ppu_trigger_vbl_nmi() {
|
||||||
if (!ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_GEN_VBLANK_NMI)) {
|
if (!ppu_read_flag(PPU_REGISTER_CTRL, PPU_CTRL_GEN_VBLANK_NMI)) {
|
||||||
// VBlank NMI generation is disabled
|
// VBlank NMI generation is disabled
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hits++;
|
||||||
cpu_trigger_nmi();
|
cpu_trigger_nmi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +114,7 @@ static inline void ppu_pixel_set_color(pixel *pixel, byte pt_low, byte pt_high,
|
||||||
}
|
}
|
||||||
|
|
||||||
byte color = ppu_read(color_addr);
|
byte color = ppu_read(color_addr);
|
||||||
*(pixel + i) = color_list[color];
|
*(pixel + i) = colors[color];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,16 +124,9 @@ void ppu_draw_tile() {
|
||||||
unsigned int y = ppu_state.scanline;
|
unsigned int y = ppu_state.scanline;
|
||||||
unsigned int x = ppu_state.cycle;
|
unsigned int x = ppu_state.cycle;
|
||||||
|
|
||||||
// if (x > PPU_LINE_WIDTH) {
|
unsigned int pixel_index = (y * PPU_CYCLE_VISIBLE_MAX + x) % (240 * 256);
|
||||||
// x -= PPU_LINE_WIDTH;
|
assert(pixel_index < 240 * 256); // If this goes over, the PPU registers will be overridden
|
||||||
// y++;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (y > PPU_PRE_RENDER_LINE) {
|
|
||||||
// y -= PPU_PRE_RENDER_LINE;
|
|
||||||
// }
|
|
||||||
|
|
||||||
unsigned int pixel_index = y * PPU_VISIBLE_FRAME_END + x;
|
|
||||||
pixel *pixel = &ppu_state.pixels[pixel_index];
|
pixel *pixel = &ppu_state.pixels[pixel_index];
|
||||||
ppu_pixel_set_color(pixel, fetch.pattern_table_tile_low, fetch.pattern_table_tile_high, fetch.attribute_table);
|
ppu_pixel_set_color(pixel, fetch.pattern_table_tile_low, fetch.pattern_table_tile_high, fetch.attribute_table);
|
||||||
}
|
}
|
||||||
|
@ -137,6 +134,11 @@ void ppu_draw_tile() {
|
||||||
byte ppu_get_pattern(byte tile_index, byte high) {
|
byte ppu_get_pattern(byte tile_index, byte high) {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (ppu_state.debug.flags.tile_debugger) {
|
if (ppu_state.debug.flags.tile_debugger) {
|
||||||
|
if ((ppu_state.debug.flags.tile_debugger_pattern_half == 1 && high) ||
|
||||||
|
(ppu_state.debug.flags.tile_debugger_pattern_half == 2 && !high)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return tile_debugger_encode_number_as_pattern(tile_index, ppu_state.scanline % 8);
|
return tile_debugger_encode_number_as_pattern(tile_index, ppu_state.scanline % 8);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -151,16 +153,16 @@ void ppu_fetch_tile(bool render) {
|
||||||
|
|
||||||
if (fetch_cycle == 1) {
|
if (fetch_cycle == 1) {
|
||||||
address nametable_addr = (ppu_state.ppu_address & 0xfff) | 0x2000;
|
address nametable_addr = (ppu_state.ppu_address & 0xfff) | 0x2000;
|
||||||
ppu_state.fetch.nametable = ppu_read(nametable_addr);
|
ppu_state.fetch.tile_id = ppu_read(nametable_addr);
|
||||||
} else if (fetch_cycle == 3) {
|
} else if (fetch_cycle == 3) {
|
||||||
// PPU address:
|
// PPU address:
|
||||||
// yyy NN YYYYY XXXXX
|
// yyy NN YYYYY XXXXX
|
||||||
// ||| || ||||| +++++-- coarse X scroll
|
// ||| || ||||| +++++-- coarse X scroll
|
||||||
// ||| || +++++-------- coarse Y scroll
|
// ||| || +++++-------- coarse Y scroll
|
||||||
// ||| ++-------------- nametable select
|
// ||| ++-------------- tile_id select
|
||||||
// +++----------------- fine Y scroll
|
// +++----------------- fine Y scroll
|
||||||
//
|
//
|
||||||
// The attribute table is at the end of the nametable and contains 64 bytes
|
// The attribute table is at the end of the tile_id and contains 64 bytes
|
||||||
// It controls the palette assignation of a 4x4 tiles area
|
// It controls the palette assignation of a 4x4 tiles area
|
||||||
byte tile_col = ppu_state.ppu_address & 0x1f;
|
byte tile_col = ppu_state.ppu_address & 0x1f;
|
||||||
byte tile_attr_col = (tile_col >> 2) & 0x7;
|
byte tile_attr_col = (tile_col >> 2) & 0x7;
|
||||||
|
@ -172,9 +174,9 @@ void ppu_fetch_tile(bool render) {
|
||||||
address attr_addr = 0x23c0 | (ppu_state.ppu_address & 0x0c00) | (tile_attr_row << 3) | tile_attr_col;
|
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);
|
ppu_state.fetch.attribute_table = ppu_read(attr_addr);
|
||||||
} else if (fetch_cycle == 5) {
|
} else if (fetch_cycle == 5) {
|
||||||
ppu_state.fetch.pattern_table_tile_low = ppu_get_pattern(ppu_state.fetch.nametable, 0);
|
ppu_state.fetch.pattern_table_tile_low = ppu_get_pattern(ppu_state.fetch.tile_id, 0);
|
||||||
} else if (fetch_cycle == 7) {
|
} else if (fetch_cycle == 7) {
|
||||||
ppu_state.fetch.pattern_table_tile_high = ppu_get_pattern(ppu_state.fetch.nametable, 1);
|
ppu_state.fetch.pattern_table_tile_high = ppu_get_pattern(ppu_state.fetch.tile_id, 1);
|
||||||
|
|
||||||
if (render) {
|
if (render) {
|
||||||
ppu_draw_tile();
|
ppu_draw_tile();
|
||||||
|
@ -197,7 +199,7 @@ void ppu_visible_frame(unsigned int cycle) {
|
||||||
|
|
||||||
if (cycle == 0) {
|
if (cycle == 0) {
|
||||||
// Idle...
|
// Idle...
|
||||||
} else if (cycle >= 8 && cycle <= 256) {
|
} else if (cycle <= 256) {
|
||||||
ppu_fetch_tile(true);
|
ppu_fetch_tile(true);
|
||||||
|
|
||||||
if (cycle == 256) {
|
if (cycle == 256) {
|
||||||
|
@ -247,38 +249,30 @@ void ppu_post_render(unsigned int x, unsigned int y) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int cycles = 0;
|
||||||
void ppu_cycle() {
|
void ppu_cycle() {
|
||||||
if (ppu_state.scanline < PPU_VISIBLE_FRAME_END) {
|
if (ppu_state.scanline < PPU_SCANLINE_VISIBLE_MAX) {
|
||||||
ppu_visible_frame(ppu_state.cycle);
|
ppu_visible_frame(ppu_state.cycle);
|
||||||
} else if (ppu_state.scanline >= PPU_POST_RENDER_LINE_START && ppu_state.scanline <= PPU_POST_RENDER_LINE_END) {
|
} else if (ppu_state.scanline >= PPU_SCANLINE_POST_RENDER_MIN && ppu_state.scanline <= PPU_SCANLINE_POST_RENDER_MAX) {
|
||||||
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_SCANLINE_PRE_RENDER) {
|
||||||
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;
|
ppu_state.ppu_address = ppu_state.temp_ppu_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int frame_width = PPU_LINE_WIDTH;
|
|
||||||
int frame_height = PPU_LINE_END;
|
|
||||||
bool rendering_enabled = ppu_read_flag(PPU_REGISTER_MASK, PPU_MASK_SHOW_BG | PPU_MASK_SHOW_SP);
|
|
||||||
if (rendering_enabled && ppu_state.odd_frame) {
|
|
||||||
// With rendering enabled, the odd frames are shorter
|
|
||||||
// TODO: and doing the last cycle of the last dummy nametable fetch there instead
|
|
||||||
// frame_width = PPU_LINE_WIDTH - 2;
|
|
||||||
// frame_height = PPU_LINE_END - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ppu_state.cycle++;
|
ppu_state.cycle++;
|
||||||
if (ppu_state.cycle >= frame_width) {
|
if (ppu_state.cycle >= PPU_CYCLE_MAX) {
|
||||||
ppu_state.cycle = 0;
|
ppu_state.cycle = 0;
|
||||||
ppu_state.scanline++;
|
ppu_state.scanline++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ppu_state.scanline > frame_height) {
|
if (ppu_state.scanline > PPU_SCANLINE_MAX) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cycles++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ppu_write(address addr, byte data) {
|
void ppu_write(address addr, byte data) {
|
||||||
|
@ -379,7 +373,50 @@ bool ppu_read_flag(size_t reg, byte mask) {
|
||||||
* 88 YD Y88888P Y888P Y888888P `8888Y' YP Y88888P 88 YD `8888Y'
|
* 88 YD Y88888P Y888P Y888888P `8888Y' YP Y88888P 88 YD `8888Y'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
static inline bool ppu_reg_bit_changed(byte reg, byte data, byte bit, byte *new_val) {
|
||||||
|
byte old_val = (ppu_state.registers[reg] >> bit) & 1;
|
||||||
|
*new_val = (data >> bit) & 1;
|
||||||
|
|
||||||
|
return old_val != *new_val;
|
||||||
|
}
|
||||||
|
|
||||||
void ppu_write_ctrl(byte data) {
|
void ppu_write_ctrl(byte data) {
|
||||||
|
// Logging
|
||||||
|
log_debug("PPU Ctrl - %#02x", data);
|
||||||
|
byte new_ctrl;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (!ppu_reg_bit_changed(PPU_REGISTER_CTRL, data, i, &new_ctrl)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
log_debug("PPU Ctrl - Base tile_id address = %#04x", 0x2000 + (0x400 * data & 3));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
log_debug("PPU Ctrl - VRAM address increment = %d", new_ctrl ? 32 : 1);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
log_debug("PPU Ctrl - Sprite pattern table address = %#04x", new_ctrl ? 0x1000 : 0);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
log_debug("PPU Ctrl - Background pattern table address = %#04x", new_ctrl ? 0x1000 : 0);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
log_debug("PPU Ctrl - Sprite size = %s", new_ctrl ? "8x16" : "8x8");
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
log_debug("PPU Ctrl - PPU master/slave select = %s", new_ctrl ? "output color" : "backdrop");
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
log_debug("PPU Ctrl - Generate NMI at VBlanks = %s", new_ctrl ? "yes" : "no");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ppu_state.temp_ppu_addr = (ppu_state.temp_ppu_addr & 0xf3ff) | ((data & PPU_CTRL_BASE_NAMETABLE_ADDR) << 10);
|
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.bg_pattern_table_addr = (data & PPU_CTRL_BG_PATTERN_TABLE_ADDR) << 0x8; // 0x0000 or 0x1000
|
||||||
|
|
||||||
|
@ -393,6 +430,45 @@ void ppu_write_ctrl(byte data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ppu_write_mask(byte data) {
|
||||||
|
// Logging
|
||||||
|
byte new_mask;
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (!ppu_reg_bit_changed(PPU_REGISTER_MASK, data, i, &new_mask)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (i) {
|
||||||
|
case 0:
|
||||||
|
log_debug("PPU Mask - Greyscale = %d", new_mask ? "normal" : "greyscale");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
log_debug("PPU Mask - Render background in first vertical tile = %s", new_mask ? "yes" : "no");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
log_debug("PPU Mask - Render sprites in first vertical tile = %s", new_mask ? "yes" : "no");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
log_debug("PPU Mask - Render background = %s", new_mask ? "yes" : "no");
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
log_debug("PPU Mask - Render sprites = %s", new_mask ? "yes" : "no");
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
log_debug("PPU Mask - Emphasize red = %s", new_mask ? "yes" : "no");
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
log_debug("PPU Mask - Emphasize green = %s", new_mask ? "yes" : "no");
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
log_debug("PPU Mask - Emphasize blue = %s", new_mask ? "yes" : "no");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ppu_write_scroll(byte data) {
|
void ppu_write_scroll(byte data) {
|
||||||
ppu_state.w = !ppu_state.w;
|
ppu_state.w = !ppu_state.w;
|
||||||
|
|
||||||
|
@ -443,6 +519,9 @@ void ppu_write_reg(byte reg, byte data) {
|
||||||
case PPU_REGISTER_CTRL:
|
case PPU_REGISTER_CTRL:
|
||||||
ppu_write_ctrl(data);
|
ppu_write_ctrl(data);
|
||||||
break;
|
break;
|
||||||
|
case PPU_REGISTER_MASK:
|
||||||
|
ppu_write_mask(data);
|
||||||
|
break;
|
||||||
case PPU_REGISTER_SCROLL:
|
case PPU_REGISTER_SCROLL:
|
||||||
ppu_write_scroll(data);
|
ppu_write_scroll(data);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -7,11 +7,11 @@
|
||||||
// Contains the patterns of every hexadecimal digit encoded as pattern data.
|
// Contains the patterns of every hexadecimal digit encoded as pattern data.
|
||||||
// The first dimension of the table represents a row in a tile.
|
// The first dimension of the table represents a row in a tile.
|
||||||
byte hex_pattern_table[5][0x10] = {
|
byte hex_pattern_table[5][0x10] = {
|
||||||
{0b111, 0b001, 0b111, 0b111, 0b101, 0b111, 0b111, 0b111, 0b111, 0b111, 0b010, 0b111, 0b111, 0b110, 0b111, 0b111},
|
{0b111, 0b001, 0b111, 0b111, 0b101, 0b111, 0b111, 0b111, 0b111, 0b111, 0b010, 0b110, 0b111, 0b110, 0b111, 0b111},
|
||||||
{0b101, 0b001, 0b001, 0b001, 0b101, 0b100, 0b100, 0b001, 0b101, 0b101, 0b101, 0b101, 0b100, 0b101, 0b100, 0b100},
|
{0b101, 0b001, 0b001, 0b001, 0b101, 0b100, 0b100, 0b001, 0b101, 0b101, 0b101, 0b101, 0b100, 0b101, 0b100, 0b100},
|
||||||
{0b101, 0b001, 0b111, 0b111, 0b111, 0b111, 0b111, 0b010, 0b111, 0b111, 0b111, 0b110, 0b100, 0b101, 0b111, 0b110},
|
{0b101, 0b001, 0b111, 0b111, 0b111, 0b111, 0b111, 0b010, 0b111, 0b111, 0b111, 0b110, 0b100, 0b101, 0b111, 0b110},
|
||||||
{0b101, 0b001, 0b100, 0b001, 0b001, 0b001, 0b101, 0b010, 0b101, 0b001, 0b101, 0b101, 0b100, 0b101, 0b100, 0b100},
|
{0b101, 0b001, 0b100, 0b001, 0b001, 0b001, 0b101, 0b010, 0b101, 0b001, 0b101, 0b101, 0b100, 0b101, 0b100, 0b100},
|
||||||
{0b111, 0b001, 0b111, 0b111, 0b001, 0b111, 0b111, 0b010, 0b111, 0b001, 0b101, 0b111, 0b111, 0b110, 0b111, 0b100},
|
{0b111, 0b001, 0b111, 0b111, 0b001, 0b111, 0b111, 0b010, 0b111, 0b001, 0b101, 0b110, 0b111, 0b110, 0b111, 0b100},
|
||||||
};
|
};
|
||||||
|
|
||||||
byte tile_debugger_encode_number_as_pattern(byte num, byte tile_fine_y) {
|
byte tile_debugger_encode_number_as_pattern(byte num, byte tile_fine_y) {
|
||||||
|
|
18
system.c
18
system.c
|
@ -23,21 +23,27 @@ void system_start() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void system_next_frame() {
|
void system_next_frame() {
|
||||||
if (current_sys.paused) {
|
for (int cpu_c = 0; cpu_c < CPU_CYCLE_PER_FRAME; cpu_c++) {
|
||||||
return;
|
if (!current_sys.paused) {
|
||||||
|
cpu_cycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int cpu_c = 0; cpu_c < CPU_CYCLE_PER_FRAME; cpu_c++) {
|
if (!current_sys.ppu_paused) {
|
||||||
cpu_cycle();
|
|
||||||
|
|
||||||
for (int ppu_c = 0; ppu_c < PPU_CYCLE_PER_CPU_CYCLE; ppu_c++) {
|
for (int ppu_c = 0; ppu_c < PPU_CYCLE_PER_CPU_CYCLE; ppu_c++) {
|
||||||
ppu_cycle();
|
ppu_cycle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void system_toggle_pause() {
|
void system_toggle_pause(bool pause_ppu) {
|
||||||
current_sys.paused = !current_sys.paused;
|
current_sys.paused = !current_sys.paused;
|
||||||
|
|
||||||
|
if (!current_sys.paused) {
|
||||||
|
current_sys.ppu_paused = false;
|
||||||
|
} else if (pause_ppu) {
|
||||||
|
current_sys.ppu_paused = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void system_uninit() {
|
void system_uninit() {
|
||||||
|
|
Loading…
Reference in New Issue