Browse Source

Flesh out PPU rendering by using LUTs

master
Nathaniel Walizer 1 year ago
parent
commit
4f46423e6d
4 changed files with 271 additions and 53 deletions
  1. +2
    -1
      src/nese.c
  2. +13
    -13
      src/ppu.c
  3. +37
    -2
      src/ppu.h
  4. +219
    -37
      src/sdl_render.c

+ 2
- 1
src/nese.c View File

@@ -58,7 +58,7 @@ int main(int argc, char* argv[]) {
int last_frame_rendered = -1;
for (int i = 0; i < n_loops && status == 0; ++i) {
int run = 0;
status = nes_run(&sys, 1000, &run);
status = nes_run(&sys, 12, &run);
total_cycles += run;
/*
float us_run = ( run * 1000. * 1000. *
@@ -68,6 +68,7 @@ int main(int argc, char* argv[]) {
us_run, run,
status == 0 ? "OK" : "Halted");
*/
// TODO: Check VBlank or scanline?
if ( status == 0 &&
sys.ppu.frame != last_frame_rendered) {
status = nes_render(rend, &sys.ppu);


+ 13
- 13
src/ppu.c View File

@@ -84,7 +84,7 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val) {
if (ppu->latch) {
ppu->addr &= 0x3F00U;
ppu->addr |= val;
printf("PPU: VRAM ADDR %04x\n", ppu->addr);
// printf("PPU: VRAM ADDR %04x\n", ppu->addr);

} else {
ppu->addr &= 0x00FFU;
@@ -100,6 +100,7 @@ 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 PAL %02x < %02x\n", pal_addr, val);
ppu->palette[pal_addr] = val;

} else if (ppu->addr >= nes_ppu_mem_vram_start) {
@@ -140,6 +141,12 @@ int nes_ppu_run(nes_ppu* ppu, int cycles) {

ppu->cycle += cycles;

if ( 0 != ppu->hit_line &&
ppu->scanline > ppu->hit_line &&
ppu->cycle > ppu->hit_dot) {
ppu->status |= ppu_Status_Hit;
}

while (ppu->cycle >= nes_ppu_dots) {
ppu->cycle -= nes_ppu_dots;
if ( ppu->scanline <= nes_ppu_prerender &&
@@ -153,13 +160,15 @@ int nes_ppu_run(nes_ppu* ppu, int cycles) {
nes_ppu_height +
nes_ppu_postrender +
nes_ppu_vblank) {
ppu->status &= ~ppu_Status_VBlank;
ppu->status &= ~(ppu_Status_VBlank | ppu_Status_Hit);
ppu->hit_line = 0;
ppu->hit_dot = 0;
ppu->scanline = 0;
ppu->frame++;
// TODO: Render callback if vblank was previously set
} else if (ppu->scanline >= nes_ppu_prerender +
nes_ppu_height +
nes_ppu_postrender) {
nes_ppu_height +
nes_ppu_postrender) {
ppu->status |= ppu_Status_VBlank;
if (ppu->control & ppu_Control_VBlank) {
vblank = 1;
@@ -170,15 +179,6 @@ int nes_ppu_run(nes_ppu* ppu, int cycles) {
return vblank;
}

#define nes_ppu_active_cycles \
(nes_ppu_dots * (nes_ppu_prerender + \
nes_ppu_height + \
nes_ppu_postrender))

#define nes_ppu_vblank_cycles (nes_ppu_dots * nes_ppu_vblank)

#define nes_frame_cycles (nes_ppu_active_cycles + \
nes_ppu_vblank_cycles)

int nes_ppu_cycles_til_vblank(nes_ppu* ppu) {
int cycles_til_vblank = nes_ppu_active_cycles - (


+ 37
- 2
src/ppu.h View File

@@ -10,12 +10,25 @@
#define nes_ppu_postrender (1U)
#define nes_ppu_vblank (20U)

#define nes_ppu_render_w (320U) // Includes full overscan
#define nes_ppu_render_h nes_ppu_height
#define nes_ppu_active_cycles \
(nes_ppu_dots * (nes_ppu_prerender + \
nes_ppu_height + \
nes_ppu_postrender))

#define nes_ppu_vblank_cycles (nes_ppu_dots * nes_ppu_vblank)

#define nes_frame_cycles (nes_ppu_active_cycles + \
nes_ppu_vblank_cycles)

#define nes_ppu_scan_w (320U) // Includes full overscan
#define nes_ppu_scan_h nes_ppu_height

#define nes_ppu_blocks_w (32U)
#define nes_ppu_blocks_h (30U)

#define nes_ppu_render_w (nes_ppu_blocks_w * 8U)
#define nes_ppu_render_h (nes_ppu_blocks_h * 8U)

#define nes_ppu_mem_size (0x4000U)
#define nes_ppu_mem_pal_start (0x3F00U)
#define nes_ppu_mem_pal_size (0x0020U)
@@ -24,6 +37,10 @@

#define nes_ppu_oam_size (256U)

#define nes_ppu_oam_sprite_size (4U)
#define nes_ppu_oam_sprite_count (nes_ppu_oam_size / \
nes_ppu_oam_sprite_size)

typedef enum {
ppu_Control_Nametable_Mask = 0b00000011,
ppu_Control_VRAM_Inc = 0b00000100,
@@ -41,16 +58,33 @@ typedef enum {
ppu_Status_VBlank = 0b10000000,
} nes_ppu_Status;

typedef enum {
ppu_Mask_Greyscale = 0b00000001,
ppu_Mask_Left_Back = 0b00000010,
ppu_Mask_Left_Sprite = 0b00000100,
ppu_Mask_Back = 0b00001000,
ppu_Mask_Sprite = 0b00010000,
ppu_Mask_More_Red = 0b00100000,
ppu_Mask_More_Green = 0b01000000,
ppu_Mask_More_Blue = 0b10000000,
} nes_ppu_Mask;

typedef struct {
// Memory
uint8_t* chr_mem;
uint8_t oam[nes_ppu_oam_size];
uint8_t vram[nes_ppu_mem_vram_size];
uint8_t palette[nes_ppu_mem_pal_size];

// Timing
int frame;
int scanline;
int cycle;

int hit_line;
int hit_dot;

// External registers
uint8_t control;
uint8_t mask;
uint8_t status;
@@ -59,6 +93,7 @@ typedef struct {
uint8_t data;
uint8_t oam_addr;

// Internal Registers
uint8_t latch;

} nes_ppu;


+ 219
- 37
src/sdl_render.c View File

@@ -3,14 +3,15 @@
#include "render.h"
#include "ppu.h"

/*
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} __attribute__ (( packed )) sPal;
*/

static sPal nes_palette[64] = {
static SDL_Color nes_palette[64] = {
{0x80,0x80,0x80}, {0x00,0x00,0xBB}, {0x37,0x00,0xBF}, {0x84,0x00,0xA6},
{0xBB,0x00,0x6A}, {0xB7,0x00,0x1E}, {0xB3,0x00,0x00}, {0x91,0x26,0x00},
{0x7B,0x2B,0x00}, {0x00,0x3E,0x00}, {0x00,0x48,0x0D}, {0x00,0x3C,0x22},
@@ -34,10 +35,11 @@ static sPal nes_palette[64] = {


typedef struct {
SDL_Surface* surface;
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* texture;
SDL_Surface* background;
SDL_Surface* sprite8;
SDL_Surface* target;
} sdl_render_data;

static sdl_render_data the_render_data = {0};
@@ -59,8 +61,8 @@ static int sdl_render_init(nes_Renderer* rend) {
"NESe",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
nes_ppu_render_w * 4,
nes_ppu_render_h * 4,
nes_ppu_scan_w * 4,
nes_ppu_scan_h * 4,
0
);
if (NULL == data->window) {
@@ -81,6 +83,11 @@ static int sdl_render_init(nes_Renderer* rend) {
}

if (0 == status) {
data->background = SDL_CreateRGBSurfaceWithFormat(
0, nes_ppu_render_w, nes_ppu_render_h,
8, SDL_PIXELFORMAT_INDEX8
);
/*
data->texture = SDL_CreateTexture(
the_render_data.renderer,
SDL_PIXELFORMAT_RGB24,
@@ -88,8 +95,33 @@ static int sdl_render_init(nes_Renderer* rend) {
nes_ppu_render_w,
nes_ppu_render_h
);
if (NULL == data->texture) {
fprintf(stderr, "SDL: Failed to create texture\n");
*/
if (NULL == data->background) {
fprintf(stderr, "SDL: Failed to create background\n");
SDL_DestroyRenderer(data->renderer);
SDL_DestroyWindow(data->window);
SDL_Quit();
status = -1;
}
}

if (0 == status) {
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 (0 == status) {
data->target = SDL_CreateRGBSurfaceWithFormat(
0U, nes_ppu_scan_w, nes_ppu_scan_h, 24U,
SDL_PIXELFORMAT_RGB888
);
if (NULL == data->target) {
fprintf(stderr, "SDL: Failed to create target\n");
SDL_FreeSurface(data->background);
SDL_DestroyRenderer(data->renderer);
SDL_DestroyWindow(data->window);
SDL_Quit();
@@ -98,6 +130,9 @@ static int sdl_render_init(nes_Renderer* rend) {
}

if (0 == status) {
SDL_SetPaletteColors(data->background->format->palette,
nes_palette, 0U, 64U);
SDL_SetColorKey(data->background, SDL_TRUE, 0xFFU);
SDL_SetEventFilter(filter, NULL);
rend->data = &the_render_data;
}
@@ -107,66 +142,213 @@ 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_DestroyTexture(data->texture);
SDL_FreeSurface(data->target);
SDL_FreeSurface(data->background);
SDL_DestroyRenderer(data->renderer);
SDL_DestroyWindow(data->window);
SDL_Quit();
}

typedef struct {
typedef enum {
Render_Mode_Sprite = 0b000,
Render_Mode_Background = 0b001,
Render_Mode_Behind = 0b010,
Render_Mode_Collide = 0b100,
} Render_Mode;

} oam_sprite;

static void render_sprite(const nes_ppu* ppu, int index,
void* loc, int pitch) {
static int render_sprite(nes_ppu* ppu, int index,
const uint8_t* pal, Render_Mode mode,
void* loc, int pitch,
const void* back_loc, int back_pitch) {
int hit_pos = -1;
uint8_t* sprite = &ppu->chr_mem[index * 16U];
uint8_t* dst_line = (uint8_t*)loc;
const uint8_t* back_line = (uint8_t*)back_loc;
for (int y = 8; y > 0; --y) {
uint8_t hi = sprite[0U];
uint8_t lo = sprite[8U];
sPal* dst = (sPal*)dst_line;
uint8_t lo = sprite[0U];
uint8_t hi = sprite[8U];
uint8_t* dst = dst_line;
const uint8_t* back = back_line;
for (int x = 8; x > 0; --x) {
int pal_idx = (!!(hi & 0x80) << 1) | !!(lo & 0x80);
*dst = nes_palette[16 + (pal_idx * 4)];
++dst;
int pal_idx = ((hi & 0x80) >> 6) | ((lo & 0x80) >> 7);
int nes_pal_idx = (pal_idx ? pal[pal_idx] : 0xFFU);
if ( hit_pos < 0 &&
(ppu->mask & ppu_Mask_Back) &&
(mode & Render_Mode_Collide) &&
nes_pal_idx != 0xFFU &&
*back != 0xFFU ) {
hit_pos = (8 - x) + (8 * (8 - y));
}
if ((mode & Render_Mode_Behind) && *back != 0xFFU) {
nes_pal_idx = 0xFFU;
}
*dst++ = nes_pal_idx;
++back;
hi <<= 1;
lo <<= 1;
}
dst_line += pitch;
back_line += back_pitch;
++sprite;
}
return hit_pos;
}

// TODO: Don't re-render background unless VRAM has changed

static int sdl_render(nes_Renderer* rend, nes_ppu* ppu) {
sdl_render_data* data = (sdl_render_data*)rend->data;

// TODO
void* buffer = NULL;
int pitch = 0;
SDL_LockTexture(data->texture, NULL, &buffer, &pitch);
static void render_background(nes_ppu* ppu,
void* buffer, int pitch) {
int bank = (ppu->control & ppu_Control_Back_Bank) ? 0x100 : 0;
uint8_t* index = &ppu->vram[0];
uint8_t* dst_line =
(uint8_t*)buffer +
((nes_ppu_render_w - (8U * nes_ppu_blocks_w)) / 2);
// 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) {
render_sprite(
ppu, bank + *index, dst, pitch
);
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 += 3 * 8;
dst += 8;
}
dst_line += pitch * 8;
}
SDL_UnlockTexture(data->texture);
}

typedef struct {
uint8_t y;
uint8_t index;
uint8_t attr;
uint8_t x;
} oam_sprite;

SDL_RenderCopy(data->renderer, data->texture, NULL, NULL);
SDL_RenderPresent(data->renderer);
typedef enum {
oam_Attr_Pal_Mask = 0b00000011,
oam_Attr_Background = 0b00100000,
oam_Attr_Flip_X = 0b01000000,
oam_Attr_Flip_Y = 0b10000000,
} oam_Attribute;

typedef enum {
oam_Index_Bank = 0b00000001,
oam_Index_Tile_Mask = 0b11111110,
} oam_Index;

static const SDL_Rect sprite_rect = {
.x = 0,
.y = 0,
.w = 8,
.h = 8,
};

static void render_sprites(nes_ppu* ppu,
SDL_Surface* buffer,
SDL_Surface* target,
const void* back, int back_pitch) {
int bank = (ppu->control & ppu_Control_Sprite_Bank) ?
0x100 : 0;
const oam_sprite* sprites = (const oam_sprite*)ppu->oam;
uint8_t* dst_origin = (uint8_t*)buffer->pixels;
int pitch = buffer->pitch;
const uint8_t* back_origin = (uint8_t*)back;
for ( int i_sprite = nes_ppu_oam_sprite_count - 1;
i_sprite >= 0; --i_sprite) {
const oam_sprite* sprite = &sprites[i_sprite];
if ( !(ppu->mask & ppu_Mask_Left_Sprite) &&
sprite->x < 8) {
continue;
}
int y = (sprite->y + 1);
if (y >= nes_ppu_render_h) continue;
uint8_t* dst = dst_origin;
int back_offset = sprite->x + (y * back_pitch);
const uint8_t* dst_back = back_offset + back_origin;
// TODO: Support 8x16 sprites
int index = bank + sprite->index;
// TODO: Support mirroring
int pal_idx = (sprite->attr & oam_Attr_Pal_Mask);
const uint8_t* pal = &ppu->palette[16 + (pal_idx * 4)];
Render_Mode mode = (sprite->attr & oam_Attr_Background) ?
Render_Mode_Behind :
Render_Mode_Sprite;
if (i_sprite == 0) mode |= Render_Mode_Collide;
int hit_pos = render_sprite(ppu, index, pal, mode,
dst, pitch,
dst_back, back_pitch);
if (hit_pos >= 0) {
ppu->hit_line = y + (hit_pos / 8);
ppu->hit_dot = sprite->x + (hit_pos % 8);
}

SDL_Rect target_rect = {
.x = sprite->x +
((nes_ppu_scan_w - nes_ppu_render_w) / 2),
.y = y,
.w = 8,
.h = 8,
};
SDL_BlitSurface(buffer, &sprite_rect,
target, &target_rect);
}
}

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

/*
// HACK: THIS IS TERRIBLE
static uint32_t last_ms = 0;
uint32_t now_ms = SDL_GetTicks();
if (last_ms != 0) {
int delay = (last_ms + (1000 / 35)) - now_ms;
if (delay > 0) SDL_Delay(delay);
// else fprintf(stderr, "Delta %d\n", now_ms - last_ms);
}
last_ms = now_ms;
*/

SDL_Event event = {0};
return (1 == SDL_PollEvent(&event) && event.type == SDL_QUIT) ?


Loading…
Cancel
Save