Browse Source

Fix basic background rendering

master
Nathaniel Walizer 1 year ago
parent
commit
ee6127cdf6
3 changed files with 158 additions and 117 deletions
  1. +32
    -22
      src/ppu.c
  2. +8
    -4
      src/ppu.h
  3. +118
    -91
      src/sdl_render.c

+ 32
- 22
src/ppu.c View File

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


+ 8
- 4
src/ppu.h View File

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


+ 118
- 91
src/sdl_render.c View File

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


Loading…
Cancel
Save