From 0239f9773fbd5083aded353d9e33ea5ae8de0436 Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Sun, 16 Mar 2025 12:56:05 -0700 Subject: [PATCH] Add OAM DMA; More sprite rendering preparation --- src/f6502.c | 38 ++++++++++++-- src/linux/port.c | 32 ++++++++--- src/nes.c | 23 ++++++-- src/port.h | 1 + src/ppu.c | 134 +++++++++++++++++++++++++++++++++++++++++++++-- src/ppu.h | 6 ++- 6 files changed, 213 insertions(+), 21 deletions(-) diff --git a/src/f6502.c b/src/f6502.c index 2fd489d..84ffeda 100644 --- a/src/f6502.c +++ b/src/f6502.c @@ -1,4 +1,5 @@ #include +#include #include "f6502.h" #include "f6502_consts.h" @@ -196,12 +197,14 @@ static inline bool f6502_write(nes_Memory* mem, if (addr >= NES_PPU_PAL_START) { // Copy to render reference addr &= 0x1FU; - mem->ppu.palette[addr] = val; + uint8_t* pal = mem->ppu.palette; if (0 == (addr & 0xF)) { - uint8_t* pal = mem->ppu.palette; + // TODO: Just initialize this pal[0] = pal[4] = pal[8] = pal[12] = pal[16] = pal[20] = pal[24] = - pal[28] = val; + pal[28] = 0xFFU; + } else { + pal[addr] = val; } // Memory-mapped mirroring @@ -225,6 +228,35 @@ static inline bool f6502_write(nes_Memory* mem, case 0x4000: // TODO: APU + switch (addr & 0x1FU) { + case 0x14: + { uint8_t* src = NULL; + // OAM DMA + switch (val >> 5) { + case 0: // 0x0000 - 0x1FFF RAM + src = &mem->ram[((int)val << 8) & 0x7FFU]; + break; + + case 3: // 0x6000 - 0x7FFF SRAM + if (mem->sram_bank) { + src = &mem->sram_bank[((int)val << 8) & 0x1FFFU]; + } + break; + + case 4: // 0x8000 - 0x9FFF ROM + case 5: // 0xA000 - 0xBFFF ROM + case 6: // 0xC000 - 0xDFFF ROM + case 7: // 0xE000 - 0xFFFF ROM + src = &mem->rom_bank[(val >> 5) & 3][((int)val << 8) & 0x1FFFU]; + break; + } + if (NULL != src) { + memcpy(mem->ppu.oam, src, NES_PPU_OAM_SIZE); + } + // TODO: Spend 513 cycles + } + break; + } break; case 0x6000: diff --git a/src/linux/port.c b/src/linux/port.c index d6ff8ca..89362c1 100644 --- a/src/linux/port.c +++ b/src/linux/port.c @@ -34,7 +34,7 @@ typedef struct { SDL_Renderer* renderer; SDL_Texture* texture; SDL_Surface* target; - SDL_Surface* scanline; + SDL_Surface* screen; } platform_data; // TODO: Return an action enum? @@ -76,7 +76,18 @@ static SDL_Color nes_palette[64] = { {0xB3,0xEE,0xFF}, {0xDD,0xDD,0xDD}, {0x11,0x11,0x11}, {0x11,0x11,0x11} }; +int nese_frame_start(void* plat_data, uint8_t background) { + platform_data* plat = (platform_data*)plat_data; + + SDL_Color ext = nes_palette[background]; + SDL_FillRect(plat->target, NULL, ((int)ext.r << 16) | + ((int)ext.g << 8) | ext.b); + + return 0; +} + int nese_line_ready(void* plat_data, uint8_t* buffer, int line) { +/* platform_data* plat = (platform_data*)plat_data; SDL_Rect rect = { @@ -87,6 +98,7 @@ int nese_line_ready(void* plat_data, uint8_t* buffer, int line) { }; SDL_BlitSurface(plat->scanline, NULL, plat->target, &rect); +*/ return 0; } @@ -112,6 +124,8 @@ int nese_frame_ready(void* plat_data) { t_last = t_now; */ + SDL_BlitSurface(plat->screen, NULL, plat->target, NULL); + SDL_UnlockTexture(plat->texture); SDL_RenderCopy(plat->renderer, plat->texture, NULL, NULL); @@ -160,8 +174,8 @@ static int plat_init(platform_data* plat) { if (0 == status) { plat->renderer = SDL_CreateRenderer( plat->window, -1, - SDL_RENDERER_ACCELERATED | - SDL_RENDERER_PRESENTVSYNC + SDL_RENDERER_ACCELERATED/* | + SDL_RENDERER_PRESENTVSYNC*/ ); if (NULL == plat->renderer) { LOGE("SDL: Failed to create renderer"); @@ -186,19 +200,21 @@ static int plat_init(platform_data* plat) { } if (0 == status) { - plat->scanline = SDL_CreateRGBSurfaceWithFormatFrom( - plat->sys->ppu.line_data, nes_ppu_render_w, 1, + plat->screen = SDL_CreateRGBSurfaceWithFormatFrom( + plat->sys->ppu.screen_data, + nes_ppu_render_w, nes_ppu_render_h, 8, nes_ppu_render_w, SDL_PIXELFORMAT_INDEX8 ); - if (NULL == plat->scanline) { - LOGE("SDL: Failed to create scanline"); + if (NULL == plat->screen) { + LOGE("SDL: Failed to create screen"); status = -1; } else { SDL_SetPaletteColors( - plat->scanline->format->palette, + plat->screen->format->palette, nes_palette, 0U, 64U ); + SDL_SetColorKey(plat->screen, SDL_TRUE, 0xFFU); } } diff --git a/src/nes.c b/src/nes.c index 048116d..ac38659 100644 --- a/src/nes.c +++ b/src/nes.c @@ -60,7 +60,11 @@ static int nes_hsync(nes* sys, void* plat) { nes_ppu_render_line(&sys->ppu, &sys->core.memory.ppu); nese_line_ready( - plat, sys->ppu.line_data, + plat, + sys->ppu.screen_data + + ( nes_ppu_render_w * + ( sys->ppu.scanline - + nes_ppu_visible_line)), sys->ppu.scanline - nes_ppu_visible_line ); } @@ -73,11 +77,20 @@ static int nes_hsync(nes* sys, void* plat) { switch (sys->ppu.scanline) { case nes_ppu_prerender_line: + { nes_PPU_Memory* mem = &sys->core.memory.ppu; + f6502_set_NMI(&sys->core, 0); - sys->core.memory.ppu.status &= ~( ppu_Status_VBlank | - ppu_Status_Hit); - nes_ppu_find_hit_line(&sys->ppu, &sys->core.memory.ppu); - break; + + mem->status &= ~(ppu_Status_VBlank | ppu_Status_Hit); + nes_ppu_find_hit_line(&sys->ppu, mem); + + // Emulate the happy part of the backdrop override quirk + int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? + (mem->addr & 0x1FU) : 0; + // Don't use the rendering palette (masked transparency) + status = nese_frame_start(plat, mem->pal_bank[0x300 + pal_idx]); + + } break; case nes_ppu_postrender_line: status = nese_frame_ready(plat); diff --git a/src/port.h b/src/port.h index 61a59ff..8389e69 100644 --- a/src/port.h +++ b/src/port.h @@ -9,6 +9,7 @@ void* nese_map_file(FILE* file, int size); int nese_unmap_file(void* addr, int size); +int nese_frame_start(void*, uint8_t background); int nese_line_ready(void*, uint8_t* buffer, int line); int nese_frame_ready(void*); int nese_update_input(void*, nes_Input*); diff --git a/src/ppu.c b/src/ppu.c index 5912669..908744b 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -58,15 +58,21 @@ void nes_ppu_find_hit_line(nes_PPU* ppu, nes_PPU_Memory* mem) { void nes_ppu_render_line(nes_PPU* ppu, nes_PPU_Memory* mem) { - uint8_t* ptr = ppu->line_data; + uint8_t* scanline_ptr = ppu->screen_data + + ( nes_ppu_render_w * + (ppu->scanline - nes_ppu_visible_line)); + uint8_t* ptr = scanline_ptr; if (!(mem->mask & ppu_Mask_Back)) { + memset(ptr, 0xFFU, nes_ppu_render_w); +/* // Emulate the happy part of the backdrop override quirk int pal_idx = ((mem->addr & 0x3F00U) == 0x3F00U) ? (mem->addr & 0x1FU) : 0; // Don't use the rendering palette (masked transparency) memset(ptr, mem->pal_bank[0x300 + pal_idx], nes_ppu_render_w); +*/ } else { int back_bank = nes_PPU_Nametable_Bank_Index + ((mem->addr >> 10) & 3); @@ -187,11 +193,133 @@ void nes_ppu_render_line(nes_PPU* ppu, nes_PPU_Memory* mem) { // TODO: Mapper ppu_bus if (!(mem->mask & ppu_Mask_Left_Back)) { - memset(ppu->line_data, mem->palette[0], 8); + memset(scanline_ptr, 0xFFU, 8); } - // TODO: Draw Sprites + // TODO: Mapper VROM switch +/* + if (mem->mask & ppu_Mask_Sprite) { + // This time, we do need overflow protection +// uint8_t sprite_data[nes_ppu_render_w + 8] = {0}; + + const int sprite_height = (mem->ctrl & ppu_Control_Sprite_Size) ? + 16 : 8; + const int scanline = ppu->scanline - nes_ppu_visible_line; + + int bank = !!(mem->ctrl & ppu_Control_Sprite_Bank) << 2; + + int n_sprites = 0; + const oam_sprite* sprites = (oam_sprite*)mem->oam; + const oam_sprite* sprite = sprites + + NES_PPU_SPRITE_COUNT - 1; + for ( ; sprite >= sprites && n_sprites < 8; --sprite) { + int y = sprite->y + 1; + if (y > scanline || y + sprite_height <= scanline) { + continue; + } + + int y_off = scanline - y; + if (sprite->attr & oam_Attr_Flip_Y) y_off = sprite_height - y_off - 1; + + int ch = sprite->index; + if (mem->ctrl & ppu_Control_Sprite_Size) { + bank = (ch & 1) << 2; + ch &= 0xFEU; + } + + bank += (ch >> 6); + const int addr_off = ((ch & 0x3fU) << 4) + ((y_off & 8) << 1) + (y_off & 7); + const uint8_t* data = mem->bank[bank] + addr_off; + const uint8_t pl0 = data[0]; + const uint8_t pl1 = data[8]; + const int pat0 = (pl0 & 0x55) | ((pl1 << 1) & 0xAA); + const int pat1 = ((pl0 >> 1) & 0x55) | (pl1 & 0xAA); + + const uint8_t* pal = &mem->palette[0x10 + ((sprite->attr & oam_Attr_Pal_Mask) << 2)]; + uint8_t* dst = scanline_ptr + sprite->x; + + const int over_x = ((int)sprite->x + 8) - nes_ppu_render_w; + + // TODO: X Flip + switch (over_x) { + default: + { int pal_idx = (pat1 << 0) & 3; + if ( ( !(sprite->attr & oam_Attr_Background) || + dst[7] == 0xFFU) + && pal_idx) { + dst[7] = pal[pal_idx]; + } + } + case 1: + { int pal_idx = (pat0 << 0) & 3; + if ( ( !(sprite->attr & oam_Attr_Background) || + dst[6] == 0xFFU) + && pal_idx) { + dst[6] = pal[pal_idx]; + } + } + case 2: + { int pal_idx = (pat1 << 2) & 3; + if ( ( !(sprite->attr & oam_Attr_Background) || + dst[5] == 0xFFU) + && pal_idx) { + dst[5] = pal[pal_idx]; + } + } + case 3: + { int pal_idx = (pat0 << 2) & 3; + if ( ( !(sprite->attr & oam_Attr_Background) || + dst[4] == 0xFFU) + && pal_idx) { + dst[4] = pal[pal_idx]; + } + } + case 4: + { int pal_idx = (pat1 << 4) & 3; + if ( ( !(sprite->attr & oam_Attr_Background) || + dst[3] == 0xFFU) + && pal_idx) { + dst[3] = pal[pal_idx]; + } + } + case 5: + { int pal_idx = (pat0 << 4) & 3; + if ( ( !(sprite->attr & oam_Attr_Background) || + dst[2] == 0xFFU) + && pal_idx) { + dst[2] = pal[pal_idx]; + } + } + case 6: + { int pal_idx = (pat1 << 6) & 3; + if ( ( !(sprite->attr & oam_Attr_Background) || + dst[1] == 0xFFU) + && pal_idx) { + dst[1] = pal[pal_idx]; + } + } + case 7: + { int pal_idx = (pat0 << 6) & 3; + if ( ( !(sprite->attr & oam_Attr_Background) || + dst[0] == 0xFFU) + && pal_idx) { + dst[0] = pal[pal_idx]; + } + } + } + + ++n_sprites; + } + + if (n_sprites >= 8) { + mem->status |= ppu_Status_Overflow; + } else { + mem->status &= ~ppu_Status_Overflow; + } + } +*/ + // Increment internal registers if (mem->mask & (ppu_Mask_Sprite | ppu_Mask_Back)) { uint16_t mask = 0b10000011111; mem->addr = (mem->addr & ~mask) | (mem->t & mask); diff --git a/src/ppu.h b/src/ppu.h index ab03d54..25192bf 100644 --- a/src/ppu.h +++ b/src/ppu.h @@ -74,10 +74,12 @@ typedef enum { #define NES_CHR_ROM_PAGE_SIZE (0x0400U) #define NES_VRAM_PAGE_SIZE (0x0400U) +#define NES_PPU_SPRITE_COUNT (64U) + #define NES_PPU_CHR_SIZE (0x2000U) #define NES_PPU_VRAM_SIZE (0x1000U) #define NES_PPU_RAM_SIZE (0x4000U) -#define NES_PPU_OAM_SIZE (64U * 4U) +#define NES_PPU_OAM_SIZE (NES_PPU_SPRITE_COUNT * 4U) #define NES_PPU_PAL_START (0x3F00U) typedef struct { @@ -131,7 +133,7 @@ void nes_ppu_set_mirroring(nes_PPU_Memory* mem, typedef struct { int scanline; int hit_line; - uint8_t line_data[nes_ppu_render_w]; + uint8_t screen_data[nes_ppu_render_w * nes_ppu_render_h]; } nes_PPU; void nes_ppu_find_hit_line(nes_PPU*, nes_PPU_Memory*);