浏览代码

Add basic coarse X scrolling

master
父节点
当前提交
1a0f6ac06e
共有 6 个文件被更改,包括 265 次插入91 次删除
  1. +12
    -4
      src/nes.c
  2. +1
    -1
      src/nes.h
  3. +25
    -17
      src/nese.c
  4. +10
    -6
      src/ppu.c
  5. +12
    -2
      src/ppu.h
  6. +205
    -61
      src/sdl_render.c

+ 12
- 4
src/nes.c 查看文件

@@ -71,7 +71,7 @@ void nes_reset(nes* sys) {
nes_apu_reset(&sys->apu);
}

int nes_run(nes* sys, int master_cycles, int* run) {
nes_ppu_Result nes_run(nes* sys, int master_cycles, int* run) {
int cpu_run = 0;
int cpu_cycles = (master_cycles + (nes_clock_cpu_div - 1)) /
nes_clock_cpu_div;
@@ -79,11 +79,19 @@ int nes_run(nes* sys, int master_cycles, int* run) {

master_cycles = cpu_run * nes_clock_cpu_div;
int ppu_cycles = master_cycles / nes_clock_ppu_div;
int vblank = nes_ppu_run(&sys->ppu, ppu_cycles);
nes_ppu_Result result = nes_ppu_run(&sys->ppu, ppu_cycles);

e6502_set_nmi(&sys->cpu, vblank);
if (result == ppu_Result_VBlank_On) {
e6502_set_nmi(&sys->cpu, 1);
} else if (result == ppu_Result_VBlank_Off) {
e6502_set_nmi(&sys->cpu, 0);
}

if (run) *run = master_cycles;

return status;
if (status < 0) {
result = ppu_Result_Halt;
}

return result;
}

+ 1
- 1
src/nes.h 查看文件

@@ -59,7 +59,7 @@ void nes_init(nes*);

void nes_reset(nes*);

int nes_run(nes*, int cycles, int* run);
nes_ppu_Result nes_run(nes*, int cycles, int* run);


#endif // NES_H_

+ 25
- 17
src/nese.c 查看文件

@@ -72,15 +72,17 @@ int main(int argc, char* argv[]) {
nes_init(&sys);
nes_reset(&sys);

nes_render(rend, &sys.ppu);

struct timespec t_target = {0};
clock_gettime(CLOCK_MONOTONIC, &t_target);
uint64_t cycle_last_frame = 0;

uint64_t total_cycles = 0;
int last_frame_rendered = -1;
// int last_frame_rendered = -1;
for (int i = 0; i < n_loops && status == 0; ++i) {
int run = 0;
status = nes_run(&sys, nes_clock_cpu_div, &run);
nes_ppu_Result result = nes_run(&sys, 1U, &run);
total_cycles += run;
/*
float us_run = ( run * 1000. * 1000. *
@@ -90,26 +92,32 @@ int main(int argc, char* argv[]) {
us_run, run,
status == 0 ? "OK" : "Halted");
*/
if ( status == 0 &&
sys.ppu.frame != last_frame_rendered) {
// TODO: Check VBlank or scanline?
if ( result == ppu_Result_Ready ||
result == ppu_Result_VBlank_Off) {
status = nes_render(rend, &sys.ppu);
last_frame_rendered = sys.ppu.frame;

// Sleep to catch up to master clock
uint64_t elapsed_cycles = total_cycles -
cycle_last_frame;
int elapsed_ns = ( elapsed_cycles *
nes_clock_master_den *
NS_PER_S ) /
nes_clock_master_num;
if (status > 0) {
// last_frame_rendered = sys.ppu.frame;

// Sleep to catch up to master clock
uint64_t elapsed_cycles = total_cycles -
cycle_last_frame;
int elapsed_ns = ( elapsed_cycles *
nes_clock_master_den *
NS_PER_S ) /
nes_clock_master_num;

t_add_ns(&t_target, &t_target, elapsed_ns);

t_add_ns(&t_target, &t_target, elapsed_ns);
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
&t_target, NULL);

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
&t_target, NULL);
cycle_last_frame = total_cycles;

cycle_last_frame = total_cycles;
status = 0;
}
} else if (result == ppu_Result_Halt) {
status = -1;
}
}



+ 10
- 6
src/ppu.c 查看文件

@@ -147,7 +147,7 @@ void nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem) {
}

int nes_ppu_run(nes_ppu* ppu, int cycles) {
int vblank = 0;
nes_ppu_Result result = ppu_Result_Running;

ppu->cycle += cycles;

@@ -167,15 +167,18 @@ int nes_ppu_run(nes_ppu* ppu, int cycles) {
ppu->status &= ~(ppu_Status_VBlank | ppu_Status_Hit);
ppu->hit_line = 0;
ppu->hit_dot = 0;
ppu->scanline -= nes_ppu_frame;
ppu->scanline = 0;
ppu->frame++;
// TODO: Render callback if vblank was previously set?
result = ppu_Result_VBlank_Off;

} else if (ppu->scanline >= nes_ppu_active) {
ppu->status |= ppu_Status_VBlank;
if (ppu->control & ppu_Control_VBlank) {
vblank = 1;
result = ppu_Result_VBlank_On;
}

} else if (ppu->scanline % 8 == 1) {
result = ppu_Result_Ready;
}
}

@@ -185,13 +188,14 @@ int nes_ppu_run(nes_ppu* ppu, int cycles) {
ppu->status |= ppu_Status_Hit;
}

return vblank;
return result;
}

/*
int nes_ppu_cycles_til_vblank(nes_ppu* ppu) {
int cycles_til_vblank = nes_ppu_active_cycles - (
ppu->cycle + (ppu->scanline * nes_ppu_dots));
return (cycles_til_vblank > 0 ? cycles_til_vblank :
nes_ppu_vblank_cycles + cycles_til_vblank);
}
*/

+ 12
- 2
src/ppu.h 查看文件

@@ -47,6 +47,8 @@

typedef enum {
ppu_Control_Nametable_Mask = 0b00000011,
ppu_Control_Scroll_Page_X = 0b00000001,
ppu_Control_Scroll_Page_Y = 0b00000010,
ppu_Control_VRAM_Inc = 0b00000100,
ppu_Control_Sprite_Bank = 0b00001000,
ppu_Control_Back_Bank = 0b00010000,
@@ -108,7 +110,15 @@ void nes_ppu_write(nes_ppu* ppu, uint16_t addr, uint8_t val);
void nes_ppu_reset(nes_ppu* ppu);
void nes_ppu_init(nes_ppu* ppu, uint8_t* chr_mem);

int nes_ppu_run(nes_ppu* ppu, int cycles);
int nes_ppu_cycles_til_vblank(nes_ppu* ppu);
typedef enum {
ppu_Result_Halt = -1,
ppu_Result_Running = 0U,
ppu_Result_VBlank_On,
ppu_Result_VBlank_Off,
ppu_Result_Ready,
} nes_ppu_Result;

nes_ppu_Result nes_ppu_run(nes_ppu* ppu, int cycles);
//int nes_ppu_cycles_til_vblank(nes_ppu* ppu);

#endif // ENES_PPU_H_

+ 205
- 61
src/sdl_render.c 查看文件

@@ -84,7 +84,7 @@ static int sdl_render_init(nes_Renderer* rend) {

if (0 == status) {
data->background = SDL_CreateRGBSurfaceWithFormat(
0, nes_ppu_render_w, nes_ppu_render_h,
0, nes_ppu_render_w + 8, nes_ppu_render_h + 8,
8, SDL_PIXELFORMAT_INDEX8
);

@@ -159,13 +159,13 @@ typedef enum {
Render_Mode_Sprite = 0b00000,
Render_Mode_Background = 0b00001,
Render_Mode_Behind = 0b00010,
Render_Mode_Collide = 0b00100,
// Render_Mode_Collide = 0b00100,
Render_Mode_Flip_X = 0b01000,
Render_Mode_Flip_Y = 0b10000,
} Render_Mode;


static void render_bg_sprite(nes_ppu* ppu, int index,
static void render_bg_sprite(const nes_ppu* ppu, int index,
const uint8_t* pal,
void* loc, int pitch) {
uint8_t* sprite = &ppu->chr_mem[index * 16U];
@@ -187,31 +187,80 @@ static void render_bg_sprite(nes_ppu* ppu, int index,
}
}

// TODO: Don't re-render background unless VRAM/scroll changes

static void render_background(nes_ppu* ppu, int nametable,
void* buffer, int pitch) {
static void render_background_area(const nes_ppu* ppu, int page,
void* buffer, int pitch,
int xs, int ys, int w, int h) {
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;
const uint8_t* index_line = &ppu->vram[page * 0x400U];
const uint8_t* attrs = index_line + 960U;
index_line += xs + (ys * nes_ppu_blocks_w);
uint8_t* dst_line = (uint8_t*)buffer;
for (int y = 0; y < nes_ppu_blocks_h; ++y) {
for (int y = ys; y < h + ys; ++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);
}
const uint8_t* index = index_line;
for (int x = xs; x < w + xs; ++x) {
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;
index_line += nes_ppu_blocks_w;
}
}

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;

// Left

int page = 0;
int x = block_x;
if (x >= nes_ppu_blocks_w) {
x -= nes_ppu_blocks_w;
page += 1;
}

render_background_area(
ppu, page, buffer, pitch,
x, line,
nes_ppu_blocks_w - x, 1
);

// Right

buffer += (nes_ppu_blocks_w - x) * 8U;
render_background_area(
ppu, 1U - page, buffer, pitch,
0, line,
1U + x, 1
);
}

static int render_sprite(nes_ppu* ppu, int index,
@@ -244,6 +293,7 @@ static int render_sprite(nes_ppu* ppu, int index,
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) &&
@@ -251,6 +301,7 @@ static int render_sprite(nes_ppu* ppu, int index,
*back != 0xFFU ) {
hit_pos = (8 - x) + (8 * (8 - y));
}
*/
if ((mode & Render_Mode_Behind) && *back != 0xFFU) {
nes_pal_idx = 0xFFU;
}
@@ -286,6 +337,51 @@ typedef enum {
oam_Index_Tile_Mask = 0b11111110,
} oam_Index;

// Check sprite (0 only) collision on a scanline
// This assumes that we've verified that this sprite
// intersects with this scanline.
static int eval_sprite_line(const nes_ppu* ppu, int line,
const oam_sprite* sprite,
const uint8_t* chr,
const uint8_t* back) {
int hit_pos = -1;

int y = line - (sprite->y + 1);

if (sprite->attr & oam_Attr_Flip_Y) y = 7 - y;
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) {
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;
break;
}
++back;
if (sprite->attr & oam_Attr_Flip_X) {
hi >>= 1;
lo >>= 1;
} else {
hi <<= 1;
lo <<= 1;
}
}

return hit_pos;
}


static const SDL_Rect sprite_rect = {
.x = 0,
.y = 0,
@@ -322,7 +418,7 @@ static void render_sprites(nes_ppu* ppu,
Render_Mode mode = (sprite->attr & oam_Attr_Background) ?
Render_Mode_Behind :
Render_Mode_Sprite;
if (i_sprite == 0) mode |= Render_Mode_Collide;
// if (i_sprite == 0) mode |= Render_Mode_Collide;
if (sprite->attr & oam_Attr_Flip_X) {
mode |= Render_Mode_Flip_X;
}
@@ -349,54 +445,102 @@ static void render_sprites(nes_ppu* ppu,
}

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

// 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,
};
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) {
// 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);

} else if (ppu->scanline < nes_ppu_prerender +
nes_ppu_height) {
if (ppu->mask & ppu_Mask_Back) {
// TODO: Only re-render background if VRAM/scroll changes
render_background_line(ppu, line,
data->background->pixels,
data->background->pitch);

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

SDL_Rect render_rect = {
.x = 0,
.y = 0,
.w = nes_ppu_render_w - scroll_x,
.h = nes_ppu_render_h,
};
} else {
if (ppu->mask & ppu_Mask_Back) {
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);

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);
status = 1;
}
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};
return (1 == SDL_PollEvent(&event) && event.type == SDL_QUIT) ?
-1 : 0;
if (1 == SDL_PollEvent(&event) && event.type == SDL_QUIT) {
status = -1;
}

return status;
}




正在加载...
取消
保存