diff --git a/Makefile b/Makefile index 3156af0..ef09cb0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CC = gcc LD = $(CC) -CFLAGS = -Wall -Werror -Wshadow -I.. -g #-DE6502_DEBUG +CFLAGS = -Og -g -Wall -Werror -Wshadow -I.. #-DE6502_DEBUG LDFLAGS = OBJDIR = obj diff --git a/src/nes.c b/src/nes.c index d8a5c52..7a305b1 100644 --- a/src/nes.c +++ b/src/nes.c @@ -1,3 +1,5 @@ +#include + #include "nes.h" @@ -38,6 +40,7 @@ void nes_mem_write(nes* sys, uint16_t addr, uint8_t val) { nes_ppu_write(&sys->ppu, nes_mem_ppu_start + addr, val); } else if (addr == nes_ppu_dma_reg) { +// printf("PPU: OAM DMA $%02x00 > $%02x\n", val, sys->ppu.oam_addr); for (int i = 0; i < nes_ppu_oam_size; ++i) { sys->ppu.oam[(uint8_t)(i + sys->ppu.oam_addr)] = nes_mem_read(sys, ((uint16_t)val << 8) + i); diff --git a/src/ppu.c b/src/ppu.c index 02e96a9..40cb626 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -25,6 +25,7 @@ uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) { ppu->latch = 0; } else if (oam_reg_data == addr) { +// printf("PPU: OAM READ %02x > %02x\n", ppu->oam_addr, val); val = ppu->oam[ppu->oam_addr]; } else if (ppu_reg_data == addr) { @@ -65,13 +66,15 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) { // fprintf(stdout, "PPU: W-> $%04x %02x\n", addr, val); if (ppu_reg_ctrl == addr) { +// printf("PPU: CTRL %02x\n", val); ppu->control = val; - // TODO: Trigger NMI if it's enabled during VBlank? } else if (oam_reg_addr == addr) { +// printf("PPU: OAM ADDR %02x\n", val); ppu->oam_addr = val; } else if (oam_reg_data == addr) { +// printf("PPU: OAM %02x < %02x\n", ppu->oam_addr, val); ppu->oam[ppu->oam_addr++] = val; } else if (ppu_reg_mask == addr) { @@ -79,8 +82,10 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) { } else if (ppu_reg_scroll == addr) { if (ppu->latch) { +// printf("PPU: Scroll Y %02x\n", val); ppu->scroll_y = val; } else { +// printf("PPU: Scroll X %02x\n", val); ppu->scroll_x = val; } ppu->latch = !ppu->latch; @@ -91,9 +96,22 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) { ppu->addr |= val; // printf("PPU: VRAM ADDR %04x\n", ppu->addr); + // Take advantage of the quick split quirk + ppu->scroll_x &= 0b00000111; + ppu->scroll_x |= (val & 0b00011111); + ppu->scroll_y &= 0b11000111; + ppu->scroll_y |= (val & 0b11100000) >> 2; + } else { ppu->addr &= 0x00FFU; ppu->addr |= (uint16_t)val << 8; + + // Take advantage of the quick split quirk + ppu->control &= ~ppu_Control_Nametable_Mask; + ppu->control |= (val & 0b1100) >> 2; + ppu->scroll_y &= 0b00111000; + ppu->scroll_y |= (val & 0b11) << 6; + ppu->scroll_y |= (val & 0b110000) >> 4; } ppu->latch = !ppu->latch; @@ -105,18 +123,34 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) { uint8_t pal_addr = (ppu->addr - nes_ppu_mem_pal_start) & (nes_ppu_mem_pal_size - 1); -// fprintf(stderr, "PPU %04x PAL %02x < %02x\n", ppu->addr, pal_addr, val); +// printf("PPU: PAL %02x < %02x\n", pal_addr, val); ppu->palette[pal_addr] = val; if ((pal_addr & 0b11) == 0) { ppu->palette[pal_addr & 0xF] = val; } } else if (ppu->addr >= nes_ppu_mem_vram_start) { - uint16_t vram_addr = ppu->addr - nes_ppu_mem_vram_start; + uint16_t vram_addr = ppu->addr - + nes_ppu_mem_vram_start; if (vram_addr >= nes_ppu_mem_vram_size) { printf("!!! PPU: VRAM OOB: %04x\n", vram_addr); vram_addr &= (nes_ppu_mem_vram_size - 1); } +/* + { + int page = vram_addr >> 10; + int loc = vram_addr & 0x3FFU; + if (loc < 960) { + int y = loc / 32; + int x = loc % 32; + printf("PPU: VRAM Page %d @ %2d,%2d : %02x\n", + page, y, x, val); + } else { + printf("PPU: VRAM Attr %2d : %02x\n", + loc - 960, val); + } + } +*/ // printf("PPU: VRAM %04x < %02x\n", vram_addr, val); ppu->vram[vram_addr] = val; } diff --git a/src/sdl_render.c b/src/sdl_render.c index e3b5e7d..a198b3b 100644 --- a/src/sdl_render.c +++ b/src/sdl_render.c @@ -215,38 +215,19 @@ static void render_background_area(const nes_ppu* ppu, int page, static void render_background_line(const nes_ppu* ppu, int line, void* buffer, int pitch) { - int scroll_x = ppu->scroll_x; - if (ppu->control & ppu_Control_Scroll_Page_X) { - // TODO: This looks like a kludge. Why check for 0? - if (scroll_x != 0) scroll_x += nes_ppu_render_w; - } - -/* - int scroll_y = ppu->scroll_y + ( - (ppu->control & ppu_Control_Scroll_Page_Y) ? - nes_ppu_render_h : 0); -*/ - int block_x = scroll_x / 8; -/* - int block_y = scroll_y / 8; - int fine_x = scroll_x % 8; - int fine_y = scroll_y % 8; -*/ - // TODO: Handle vertical scrolling // TODO: Handle column 0 flag buffer += line * pitch * 8U; + int page = (ppu->control & ppu_Control_Nametable_Mask); + int x = ppu->scroll_x / 8; - // Left - - int page = 0; - int x = block_x; - if (x >= nes_ppu_blocks_w) { - x -= nes_ppu_blocks_w; - page += 1; - } +/* + // Kludge + if (ppu->scanline < 33) page = 0; +*/ + // Left render_background_area( ppu, page, buffer, pitch, x, line, @@ -254,10 +235,9 @@ static void render_background_line(const nes_ppu* ppu, int line, ); // Right - buffer += (nes_ppu_blocks_w - x) * 8U; render_background_area( - ppu, 1U - page, buffer, pitch, + ppu, page ^ 1, buffer, pitch, 0, line, 1U + x, 1 ); @@ -340,6 +320,8 @@ typedef enum { // Check sprite (0 only) collision on a scanline // This assumes that we've verified that this sprite // intersects with this scanline. +// Scanline is 0-239 from inside the rendering window +// (though we should never see this called with 0). static int eval_sprite_line(const nes_ppu* ppu, int line, const oam_sprite* sprite, const uint8_t* chr, @@ -352,20 +334,14 @@ static int eval_sprite_line(const nes_ppu* ppu, int line, uint8_t lo = chr[0U + y]; uint8_t hi = chr[8U + y]; - int w = nes_ppu_render_w - sprite->x; - if (w > 8) w = 8; - back += sprite->x; - - for (int x = 0; x < w; ++x) { + for (int x = sprite->x; x < nes_ppu_render_w; ++x) { int pal_idx = (sprite->attr & oam_Attr_Flip_X) ? (((hi & 1) << 1) | (lo & 1)) : ( ((hi & 0x80) >> 6) | ((lo & 0x80) >> 7)); - if ( hit_pos < 0 && - pal_idx && - *back != 0xFFU ) { - hit_pos = x + sprite->x; + if (pal_idx && *back != 0xFFU) { + hit_pos = x; break; } ++back; @@ -444,81 +420,98 @@ static void render_sprites(nes_ppu* ppu, } } +static void update_sprite_hit(nes_ppu* ppu, + const void* back_line, + int back_pitch) { + const oam_sprite* sprite = (oam_sprite*)ppu->oam; + int line = (ppu->scanline - nes_ppu_prerender) / 8U; + int x_fine = ppu->scroll_x % 8; + int index = sprite->index; + if (ppu->control & ppu_Control_Sprite_Bank) { + index += 0x100U; + } + const uint8_t* chr = &ppu->chr_mem[index * 16U]; + int render_line = ppu->scanline - nes_ppu_prerender; + if ( ppu->hit_dot == 0 && + (sprite->y + 1) + 7 >= render_line && + (sprite->y + 1) - 7 <= render_line) { + int hit = -1; + const uint8_t* back = (uint8_t*)back_line + x_fine; + back += 8U * line * back_pitch; + for (int y = 0; y < 8; ++y) { + hit = eval_sprite_line( + ppu, ppu->scanline + y, + sprite, chr, back + ); + if (hit >= 0) { + ppu->hit_line = ppu->scanline + y; + ppu->hit_dot = hit; + break; + } + back += back_pitch; + } + } +} + static int sdl_render(nes_Renderer* rend, nes_ppu* ppu) { int status = 0; sdl_render_data* data = (sdl_render_data*)rend->data; - int line = (ppu->scanline - nes_ppu_prerender) / 8U; - - printf("Scanline %3d -> Line %2d @ X %d\n", - ppu->scanline, line, ppu->scroll_x); - if (ppu->scanline < nes_ppu_prerender) { +// printf("Scanline %3d -> Prerender\n", ppu->scanline); + // Emulate the happy part of the backdrop override quirk int pal_idx = (ppu->addr >= nes_ppu_mem_pal_start) ? (ppu->addr & (nes_ppu_mem_pal_size - 1)) : 0; SDL_Color ext = nes_palette[ppu->palette[pal_idx]]; - SDL_FillRect(data->target, NULL, - ((int)ext.r << 16) | ((int)ext.g << 8) | ext.b); + SDL_FillRect(data->target, NULL, ((int)ext.r << 16) | + ((int)ext.g << 8) | + ext.b); } else if (ppu->scanline < nes_ppu_prerender + nes_ppu_height) { + int line = (ppu->scanline - (int)nes_ppu_prerender) / 8; + +// printf("Scanline %3d -> Line %2d @ X %d\n", ppu->scanline, line, ppu->scroll_x); + if (ppu->mask & ppu_Mask_Back) { - // TODO: Only re-render background if VRAM/scroll changes + // TODO: Only re-render if VRAM/scroll changes render_background_line(ppu, line, data->background->pixels, data->background->pitch); + int x_fine = ppu->scroll_x % 8; + // Check for Sprite 0 Hit - // TODO: Account for fine X scroll - oam_sprite* sprite = (oam_sprite*)ppu->oam; - int bank = (ppu->control & ppu_Control_Sprite_Bank) ? - 0x100 : 0; - int index = bank + sprite->index; - uint8_t* chr = &ppu->chr_mem[index * 16U]; - int sprite_line = ppu->scanline - 2; - if ( ppu->hit_dot == 0 && - sprite->y + 7 >= sprite_line && - sprite->y - 7 <= sprite_line) { - int hit = -1; - uint8_t* back = data->background->pixels; - back += 8U * line * data->background->pitch; - for (int y = 0; y < 8; ++y) { - hit = eval_sprite_line( - ppu, ppu->scanline + y, - sprite, chr, back - ); - if (hit >= 0) { - ppu->hit_line = ppu->scanline + y; - ppu->hit_dot = hit; - break; - } - back += data->background->pitch; - } + if (ppu->mask & ppu_Mask_Sprite) { + update_sprite_hit(ppu, + data->background->pixels, + data->background->pitch); } - } - } else { - if (ppu->mask & ppu_Mask_Back) { - int scroll_x = 0; //ppu->scroll_x; + // Gotta render it now while scroll is set SDL_Rect back_rect = { - .x = scroll_x, - .y = 0, - .w = nes_ppu_render_w - scroll_x, - .h = nes_ppu_render_h, + .x = x_fine, + .y = line * 8U, + .w = nes_ppu_render_w, + .h = 8U, }; SDL_Rect render_rect = { .x = 0, - .y = 0, - .w = nes_ppu_render_w - scroll_x, - .h = nes_ppu_render_h, + .y = line * 8U, + .w = nes_ppu_render_w, + .h = 8U, }; SDL_BlitSurface(data->background, &back_rect, data->target, &render_rect); } + + } else { +// printf("Scanline %3d -> Postrender\n", ppu->scanline); + if (ppu->mask & ppu_Mask_Sprite) { render_sprites(ppu, data->sprite8, data->target, data->background->pixels,