Browse Source

Finish smooth X scrolling

master
Nathaniel Walizer 1 year ago
parent
commit
2f07024354
4 changed files with 115 additions and 85 deletions
  1. +1
    -1
      Makefile
  2. +3
    -0
      src/nes.c
  3. +37
    -3
      src/ppu.c
  4. +74
    -81
      src/sdl_render.c

+ 1
- 1
Makefile View File

@@ -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


+ 3
- 0
src/nes.c View File

@@ -1,3 +1,5 @@
#include <stdio.h>

#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);


+ 37
- 3
src/ppu.c View File

@@ -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;
}


+ 74
- 81
src/sdl_render.c View File

@@ -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,


Loading…
Cancel
Save