From ee6127cdf6f4df0b2361e5f7adcedfe0e68b43a5 Mon Sep 17 00:00:00 2001 From: Nathaniel Walizer Date: Mon, 2 Dec 2024 01:03:44 -0800 Subject: [PATCH] Fix basic background rendering --- src/ppu.c | 54 +++++++----- src/ppu.h | 12 ++- src/sdl_render.c | 209 ++++++++++++++++++++++++++--------------------- 3 files changed, 158 insertions(+), 117 deletions(-) diff --git a/src/ppu.c b/src/ppu.c index 279646e..8417375 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -28,24 +28,31 @@ uint8_t nes_ppu_read(nes_ppu* ppu, uint16_t addr) { val = ppu->oam[ppu->oam_addr]; } else if (ppu_reg_data == addr) { - val = ppu->data; - - if (ppu->addr < nes_ppu_mem_vram_start) { - ppu->data = ppu->chr_mem[ppu->addr]; - } else if (ppu->addr < nes_ppu_mem_vram_start + - nes_ppu_mem_vram_size) { - ppu->data = ppu->vram[ppu->addr - - nes_ppu_mem_vram_start]; - } else if (ppu->addr < nes_ppu_mem_pal_start) { - ppu->data = ppu->chr_mem[ppu->addr]; - } else if (ppu->addr < nes_ppu_mem_size) { + if (ppu->addr >= nes_ppu_mem_pal_start) { +// printf("PPU: PAL READ %04x > %02x\n", ppu->addr, val); uint8_t pal_addr = (ppu->addr - nes_ppu_mem_pal_start) & (nes_ppu_mem_pal_size - 1); - ppu->data = ppu->palette[pal_addr]; + val = ppu->palette[pal_addr]; + + } else { + val = ppu->data; + + if (ppu->addr < nes_ppu_mem_vram_start) { +// printf("PPU: CHR MEM READ %04x > %02x\n", ppu->addr, val); + ppu->data = ppu->chr_mem[ppu->addr]; + } else if (ppu->addr < nes_ppu_mem_vram_start + + nes_ppu_mem_vram_size) { +// printf("PPU: VRAM READ %04x > %02x\n", ppu->addr, val); + ppu->data = ppu->vram[ppu->addr - + nes_ppu_mem_vram_start]; + } else if (ppu->addr < nes_ppu_mem_pal_start) { +// printf("PPU: BLANK READ %04x > %02x\n", ppu->addr, val); + ppu->data = ppu->chr_mem[ppu->addr]; + } } - ppu->addr += (ppu->status & ppu_Control_VRAM_Inc) ? + ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ? 32 : 1; } @@ -72,11 +79,9 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) { } else if (ppu_reg_scroll == addr) { if (ppu->latch) { - ppu->scroll &= 0xFF00U; - ppu->scroll |= val; + ppu->scroll_y = val; } else { - ppu->scroll &= 0x00FFU; - ppu->scroll |= (uint16_t)val << 8; + ppu->scroll_x = val; } ppu->latch = !ppu->latch; @@ -94,25 +99,29 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) { } else if (ppu_reg_data == addr) { if (ppu->addr >= nes_ppu_mem_size) { - printf("!!! PPU: MEM OOB: %04x", ppu->addr); + printf("!!! PPU: MEM OOB: %04x\n", ppu->addr); } else if (ppu->addr >= nes_ppu_mem_pal_start) { uint8_t pal_addr = (ppu->addr - nes_ppu_mem_pal_start) & (nes_ppu_mem_pal_size - 1); -// fprintf(stderr, "PPU PAL %02x < %02x\n", pal_addr, val); +// fprintf(stderr, "PPU %04x PAL %02x < %02x\n", ppu->addr, 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; if (vram_addr >= nes_ppu_mem_vram_size) { - printf("!!! PPU: VRAM OOB: %04x", vram_addr); + printf("!!! PPU: VRAM OOB: %04x\n", vram_addr); vram_addr &= (nes_ppu_mem_vram_size - 1); } +// printf("PPU: VRAM %04x < %02x\n", vram_addr, val); ppu->vram[vram_addr] = val; } - ppu->addr += (ppu->status & ppu_Control_VRAM_Inc) ? + ppu->addr += (ppu->control & ppu_Control_VRAM_Inc) ? 32 : 1; } } @@ -121,7 +130,8 @@ void nes_ppu_reset(nes_ppu* ppu) { ppu->control = 0; ppu->mask = 0; ppu->latch = 0; - ppu->scroll = 0; + ppu->scroll_x = 0; + ppu->scroll_y = 0; ppu->data = 0; ppu->frame = 0; ppu->scanline = 0; diff --git a/src/ppu.h b/src/ppu.h index 46ba264..b2362d6 100644 --- a/src/ppu.h +++ b/src/ppu.h @@ -21,7 +21,7 @@ #define nes_ppu_frame_cycles (nes_ppu_dots * nes_ppu_frame) -#define nes_ppu_scan_w (320U) // Includes full overscan +#define nes_ppu_scan_w (320U) // Effective NTSC horizontal rez #define nes_ppu_scan_h nes_ppu_height #define nes_ppu_blocks_w (32U) @@ -31,10 +31,13 @@ #define nes_ppu_render_h (nes_ppu_blocks_h * 8U) #define nes_ppu_mem_size (0x4000U) +#define nes_ppu_mem_chr_start (0x0000U) +#define nes_ppu_mem_chr_size (0x2000U) +#define nes_ppu_mem_vram_start (0x2000U) +#define nes_ppu_mem_vram_size (0x1000U) #define nes_ppu_mem_pal_start (0x3F00U) #define nes_ppu_mem_pal_size (0x0020U) -#define nes_ppu_mem_vram_start (0x2000U) -#define nes_ppu_mem_vram_size (0x0800U) + #define nes_ppu_oam_size (256U) @@ -89,7 +92,8 @@ typedef struct { uint8_t control; uint8_t mask; uint8_t status; - uint16_t scroll; + uint8_t scroll_x; + uint8_t scroll_y; uint16_t addr; uint8_t data; uint8_t oam_addr; diff --git a/src/sdl_render.c b/src/sdl_render.c index f286b0d..944805b 100644 --- a/src/sdl_render.c +++ b/src/sdl_render.c @@ -87,17 +87,9 @@ static int sdl_render_init(nes_Renderer* rend) { 0, nes_ppu_render_w, nes_ppu_render_h, 8, SDL_PIXELFORMAT_INDEX8 ); -/* - data->texture = SDL_CreateTexture( - the_render_data.renderer, - SDL_PIXELFORMAT_RGB24, - SDL_TEXTUREACCESS_STREAMING, - nes_ppu_render_w, - nes_ppu_render_h - ); -*/ + if (NULL == data->background) { - fprintf(stderr, "SDL: Failed to create background\n"); + fprintf(stderr, "SDL: Failed to create background 0\n"); SDL_DestroyRenderer(data->renderer); SDL_DestroyWindow(data->window); SDL_Quit(); @@ -109,18 +101,24 @@ static int sdl_render_init(nes_Renderer* rend) { data->sprite8 = SDL_CreateRGBSurfaceWithFormat( 0, 8, 8, 8, SDL_PIXELFORMAT_INDEX8 ); - SDL_SetPaletteColors(data->sprite8->format->palette, - nes_palette, 0U, 64U); - SDL_SetColorKey(data->sprite8, SDL_TRUE, 0xFFU); + if (NULL == data->sprite8) { + fprintf(stderr, "SDL: Failed to create sprite\n"); + SDL_FreeSurface(data->background); + SDL_DestroyRenderer(data->renderer); + SDL_DestroyWindow(data->window); + SDL_Quit(); + status = -1; + } } if (0 == status) { data->target = SDL_CreateRGBSurfaceWithFormat( - 0U, nes_ppu_scan_w, nes_ppu_scan_h, 24U, + 0U, nes_ppu_render_w, nes_ppu_render_h, 24U, SDL_PIXELFORMAT_RGB888 ); if (NULL == data->target) { fprintf(stderr, "SDL: Failed to create target\n"); + SDL_FreeSurface(data->sprite8); SDL_FreeSurface(data->background); SDL_DestroyRenderer(data->renderer); SDL_DestroyWindow(data->window); @@ -133,7 +131,14 @@ static int sdl_render_init(nes_Renderer* rend) { SDL_SetPaletteColors(data->background->format->palette, nes_palette, 0U, 64U); SDL_SetColorKey(data->background, SDL_TRUE, 0xFFU); + + SDL_SetPaletteColors(data->sprite8->format->palette, + nes_palette, 0U, 64U); + SDL_SetColorKey(data->sprite8, SDL_TRUE, 0xFFU); + + // TODO: Move this to a separate component SDL_SetEventFilter(filter, NULL); + rend->data = &the_render_data; } @@ -143,6 +148,7 @@ static int sdl_render_init(nes_Renderer* rend) { static void sdl_render_done(nes_Renderer* rend) { sdl_render_data* data = (sdl_render_data*)rend->data; SDL_FreeSurface(data->target); + SDL_FreeSurface(data->sprite8); SDL_FreeSurface(data->background); SDL_DestroyRenderer(data->renderer); SDL_DestroyWindow(data->window); @@ -159,6 +165,55 @@ typedef enum { } Render_Mode; +static void render_bg_sprite(nes_ppu* ppu, int index, + const uint8_t* pal, + void* loc, int pitch) { + uint8_t* sprite = &ppu->chr_mem[index * 16U]; + uint8_t* dst_line = (uint8_t*)loc; + + for (int y = 8; y > 0; --y) { + uint8_t lo = sprite[0U]; + uint8_t hi = sprite[8U]; + uint8_t* dst = dst_line; + for (int x = 8; x > 0; --x) { + int pal_idx = ( ((hi & 0x80) >> 6) | + ((lo & 0x80) >> 7)); + *dst++ = (pal_idx ? pal[pal_idx] : 0xFFU); + hi <<= 1; + lo <<= 1; + } + dst_line += pitch; + ++sprite; + } +} + +// TODO: Don't re-render background unless VRAM/scroll changes + +static void render_background(nes_ppu* ppu, int nametable, + void* buffer, int pitch) { + int bank = (ppu->control & ppu_Control_Back_Bank) ? 0x100 : 0; + // TODO: Support beyond nametable 0 + const uint8_t* index = &ppu->vram[nametable * 0x400U]; + const uint8_t* attrs = index + 960U; + uint8_t* dst_line = (uint8_t*)buffer; + for (int y = 0; y < nes_ppu_blocks_h; ++y) { + uint8_t* dst = dst_line; + for (int x = 0; x < nes_ppu_blocks_w; ++x) { + if (x > 0 || (ppu->mask & ppu_Mask_Left_Back)) { + int attr_idx = ((y / 4) * 8) + (x / 4); + int shift = 2 * ((y & 0b10) | ((x & 0b10) >> 1)); + int pal_idx = (attrs[attr_idx] >> shift) & 3; + const uint8_t* pal = &ppu->palette[pal_idx * 4]; + render_bg_sprite(ppu, bank + *index, pal, + dst, pitch); + } + ++index; + dst += 8; + } + dst_line += pitch * 8; + } +} + static int render_sprite(nes_ppu* ppu, int index, const uint8_t* pal, Render_Mode mode, void* loc, int pitch, @@ -167,13 +222,17 @@ static int render_sprite(nes_ppu* ppu, int index, uint8_t* sprite = &ppu->chr_mem[index * 16U]; uint8_t* dst_line = (uint8_t*)loc; const uint8_t* back_line = (uint8_t*)back_loc; + int dx = 1; if (mode & Render_Mode_Flip_X) { dst_line += 7; back_line += 7; + dx = -dx; } if (mode & Render_Mode_Flip_Y) { dst_line += (7 * pitch); back_line += (7 * back_pitch); + pitch = -pitch; + back_pitch = -back_pitch; } for (int y = 8; y > 0; --y) { @@ -196,56 +255,18 @@ static int render_sprite(nes_ppu* ppu, int index, nes_pal_idx = 0xFFU; } *dst = nes_pal_idx; - if (mode & Render_Mode_Flip_X) { - dst--; - back--; - } else { - dst++; - back++; - } + dst += dx; + back += dx; hi <<= 1; lo <<= 1; } - if (mode & Render_Mode_Flip_Y) { - dst_line -= pitch; - back_line -= back_pitch; - } else { - dst_line += pitch; - back_line += back_pitch; - } + dst_line += pitch; + back_line += back_pitch; ++sprite; } return hit_pos; } -// TODO: Don't re-render background unless VRAM has changed - -static void render_background(nes_ppu* ppu, - void* buffer, int pitch) { - int bank = (ppu->control & ppu_Control_Back_Bank) ? 0x100 : 0; - // TODO: Support beyond nametable 0 - const uint8_t* index = &ppu->vram[0U]; - const uint8_t* attrs = &ppu->vram[960U]; - uint8_t* dst_line = (uint8_t*)buffer; - for (int y = 0; y < nes_ppu_blocks_h; ++y) { - uint8_t* dst = dst_line; - for (int x = 0; x < nes_ppu_blocks_w; ++x) { - if (x > 0 || (ppu->mask & ppu_Mask_Left_Back)) { - int attr_idx = ((y / 4) * 8) + (x / 4); - int shift = 2 * ((y & 0b10) | ((x & 0b10) >> 1)); - int pal_idx = (attrs[attr_idx] >> shift) & 3; - const uint8_t* pal = &ppu->palette[pal_idx * 4]; - render_sprite(ppu, bank + *index, pal, - Render_Mode_Background, - dst, pitch, NULL, 0); - } - ++index; - dst += 8; - } - dst_line += pitch * 8; - } -} - typedef struct { uint8_t y; uint8_t index; @@ -317,8 +338,7 @@ static void render_sprites(nes_ppu* ppu, } SDL_Rect target_rect = { - .x = sprite->x + - ((nes_ppu_scan_w - nes_ppu_render_w) / 2), + .x = sprite->x, .y = y, .w = 8, .h = 8, @@ -328,43 +348,50 @@ static void render_sprites(nes_ppu* ppu, } } -static SDL_Rect render_rect = { - .x = (nes_ppu_scan_w - nes_ppu_render_w) / 2, - .y = 0, - .w = nes_ppu_render_w, - .h = nes_ppu_render_h, -}; - -static SDL_Rect back_rect = { - .x = 0, - .y = 0, - .w = nes_ppu_render_w, - .h = nes_ppu_render_h, -}; - static int sdl_render(nes_Renderer* rend, nes_ppu* ppu) { sdl_render_data* data = (sdl_render_data*)rend->data; - if (ppu->mask & (ppu_Mask_Back | ppu_Mask_Sprite)) { - SDL_FillRect(data->target, NULL, 0x808080/*nes_palette[0]*/); - if (ppu->mask & ppu_Mask_Back) { - render_background(ppu, data->background->pixels, - data->background->pitch); - SDL_BlitSurface(data->background, &back_rect, - data->target, &render_rect); - } - if (ppu->mask & ppu_Mask_Sprite) { - render_sprites(ppu, data->sprite8, data->target, - data->background->pixels, - data->background->pitch); - } - SDL_Texture* texture = SDL_CreateTextureFromSurface( - data->renderer, data->target - ); - SDL_RenderCopy(data->renderer, texture, NULL, NULL); - SDL_RenderPresent(data->renderer); - SDL_DestroyTexture(texture); + // 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); + if (ppu->mask & ppu_Mask_Back) { + // TODO: Only render visible parts of background + render_background(ppu, 0, data->background->pixels, + data->background->pitch); + + int scroll_x = 0; //ppu->scroll_x; + + SDL_Rect back_rect = { + .x = scroll_x, + .y = 0, + .w = nes_ppu_render_w - scroll_x, + .h = nes_ppu_render_h, + }; + + SDL_Rect render_rect = { + .x = 0, + .y = 0, + .w = nes_ppu_render_w - scroll_x, + .h = nes_ppu_render_h, + }; + + SDL_BlitSurface(data->background, &back_rect, + data->target, &render_rect); + } + if (ppu->mask & ppu_Mask_Sprite) { + render_sprites(ppu, data->sprite8, data->target, + data->background->pixels, + data->background->pitch); } + SDL_Texture* texture = SDL_CreateTextureFromSurface( + data->renderer, data->target + ); + SDL_RenderCopy(data->renderer, texture, NULL, NULL); + SDL_RenderPresent(data->renderer); + SDL_DestroyTexture(texture); // TODO: Handle this in the input loop, or anywhere else SDL_Event event = {0};