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