|
- #include <SDL2/SDL.h>
-
- #include "render.h"
- #include "ppu.h"
-
-
- 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},
- {0x00,0x2F,0x66}, {0x00,0x00,0x00}, {0x05,0x05,0x05}, {0x05,0x05,0x05},
-
- {0xC8,0xC8,0xC8}, {0x00,0x59,0xFF}, {0x44,0x3C,0xFF}, {0xB7,0x33,0xCC},
- {0xFF,0x33,0xAA}, {0xFF,0x37,0x5E}, {0xFF,0x37,0x1A}, {0xD5,0x4B,0x00},
- {0xC4,0x62,0x00}, {0x3C,0x7B,0x00}, {0x1E,0x84,0x15}, {0x00,0x95,0x66},
- {0x00,0x84,0xC4}, {0x11,0x11,0x11}, {0x09,0x09,0x09}, {0x09,0x09,0x09},
-
- {0xFF,0xFF,0xFF}, {0x00,0x95,0xFF}, {0x6F,0x84,0xFF}, {0xD5,0x6F,0xFF},
- {0xFF,0x77,0xCC}, {0xFF,0x6F,0x99}, {0xFF,0x7B,0x59}, {0xFF,0x91,0x5F},
- {0xFF,0xA2,0x33}, {0xA6,0xBF,0x00}, {0x51,0xD9,0x6A}, {0x4D,0xD5,0xAE},
- {0x00,0xD9,0xFF}, {0x66,0x66,0x66}, {0x0D,0x0D,0x0D}, {0x0D,0x0D,0x0D},
-
- {0xFF,0xFF,0xFF}, {0x84,0xBF,0xFF}, {0xBB,0xBB,0xFF}, {0xD0,0xBB,0xFF},
- {0xFF,0xBF,0xEA}, {0xFF,0xBF,0xCC}, {0xFF,0xC4,0xB7}, {0xFF,0xCC,0xAE},
- {0xFF,0xD9,0xA2}, {0xCC,0xE1,0x99}, {0xAE,0xEE,0xB7}, {0xAA,0xF7,0xEE},
- {0xB3,0xEE,0xFF}, {0xDD,0xDD,0xDD}, {0x11,0x11,0x11}, {0x11,0x11,0x11}
- };
-
-
- typedef struct {
- SDL_Window* window;
- SDL_Renderer* renderer;
- SDL_Surface* background;
- SDL_Surface* sprite8;
- SDL_Surface* target;
- } sdl_render_data;
-
- static sdl_render_data the_render_data = {0};
-
-
- static int sdl_render_init(nes_Renderer* rend) {
- sdl_render_data* data = &the_render_data;
- int status = SDL_Init(SDL_INIT_VIDEO);
-
- if (0 != status) {
- fprintf(stderr, "SDL: Failed to initialize\n");
-
- } else {
- data->window = SDL_CreateWindow(
- "NESe",
- SDL_WINDOWPOS_UNDEFINED,
- SDL_WINDOWPOS_UNDEFINED,
- nes_ppu_scan_w * 4,
- nes_ppu_scan_h * 4,
- 0
- );
- if (NULL == data->window) {
- fprintf(stderr, "SDL: Failed to create window\n");
- SDL_Quit();
- status = -1;
- }
- }
-
- if (0 == status) {
- data->renderer = SDL_CreateRenderer(data->window, -1, 0);
- if (NULL == data->renderer) {
- fprintf(stderr, "SDL: Failed to create renderer\n");
- SDL_DestroyWindow(data->window);
- SDL_Quit();
- status = -1;
- }
- }
-
- if (0 == status) {
- data->background = SDL_CreateRGBSurfaceWithFormat(
- 0, nes_ppu_render_w + 8, nes_ppu_render_h + 8,
- 8, SDL_PIXELFORMAT_INDEX8
- );
-
- if (NULL == data->background) {
- fprintf(stderr, "SDL: Failed to create background 0\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
- );
- 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_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);
- SDL_Quit();
- status = -1;
- }
- }
-
- if (0 == status) {
- 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);
-
- rend->data = &the_render_data;
- }
-
- return status;
- }
-
- 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);
- SDL_Quit();
- }
-
- typedef enum {
- Render_Mode_Sprite = 0b00000,
- Render_Mode_Background = 0b00001,
- Render_Mode_Behind = 0b00010,
- // Render_Mode_Collide = 0b00100,
- Render_Mode_Flip_X = 0b01000,
- Render_Mode_Flip_Y = 0b10000,
- } Render_Mode;
-
-
- 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];
- 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;
- }
- }
-
- 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;
- 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 = ys; y < h + ys; ++y) {
- uint8_t* dst = dst_line;
- 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) {
- // 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
- 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, page ^ 1, buffer, pitch,
- 0, line,
- 1U + x, 1
- );
- }
-
- static void 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) {
- 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) {
- 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) >> 6) |
- ((lo & 0x80) >> 7));
- int nes_pal_idx = (pal_idx ? pal[pal_idx] : 0xFFU);
- if ((mode & Render_Mode_Behind) && *back != 0xFFU) {
- nes_pal_idx = 0xFFU;
- }
- *dst = nes_pal_idx;
- dst += dx;
- back += dx;
- hi <<= 1;
- lo <<= 1;
- }
- dst_line += pitch;
- back_line += back_pitch;
- ++sprite;
- }
- }
-
- typedef struct {
- uint8_t y;
- uint8_t index;
- uint8_t attr;
- uint8_t x;
- } oam_sprite;
-
- 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;
-
- // 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,
- 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];
-
- back += sprite->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 (pal_idx && *back != 0xFFU) {
- hit_pos = 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,
- .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;
- 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;
- if (sprite->attr & oam_Attr_Flip_X) {
- mode |= Render_Mode_Flip_X;
- }
- if (sprite->attr & oam_Attr_Flip_Y) {
- mode |= Render_Mode_Flip_Y;
- }
- render_sprite(ppu, index, pal, mode, dst, pitch,
- dst_back, back_pitch);
-
- SDL_Rect target_rect = {
- .x = sprite->x,
- .y = y,
- .w = 8,
- .h = 8,
- };
- SDL_BlitSurface(buffer, &sprite_rect,
- target, &target_rect);
- }
- }
-
- static void update_sprite_hit(nes_ppu* ppu,
- const void* back_line,
- int back_pitch) {
- const oam_sprite* sprite = (oam_sprite*)ppu->oam;
- int block_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 = block_line * 8U;
- int start_y = (sprite->y + 1) - render_line;
- int end_y = start_y + 8;
- if (start_y < 8 && end_y > 0) {
- if (start_y < 0) start_y = 0;
- if (end_y > 8) end_y = 8;
- int hit = -1;
- const uint8_t* back = (uint8_t*)back_line + x_fine;
- back += (render_line + start_y) * back_pitch;
- for (int y = start_y; y < end_y; ++y) {
- hit = eval_sprite_line(
- ppu, render_line + y,
- sprite, chr, back
- );
- if (hit >= 0) {
- ppu->hit_line = y + render_line;
- 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;
-
- 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);
-
- } 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 if VRAM/scroll changes
- render_background_line(ppu, line,
- data->background->pixels,
- data->background->pitch);
-
- // TODO: Y scroll support
-
- int x_fine = ppu->scroll_x % 8;
-
- // Check for Sprite 0 Hit
- if ( 0 >= ppu->hit_line &&
- (ppu->mask & ppu_Mask_Sprite)) {
- update_sprite_hit(ppu,
- data->background->pixels,
- data->background->pitch);
- }
-
- // Gotta render it now while scroll is set
-
- SDL_Rect back_rect = {
- .x = x_fine,
- .y = line * 8U,
- .w = nes_ppu_render_w,
- .h = 8U,
- };
-
- SDL_Rect render_rect = {
- .x = 0,
- .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,
- 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);
-
- status = 1;
- }
-
- return status;
- }
-
-
- nes_Renderer sdl_renderer = {
- .init = sdl_render_init,
- .done = sdl_render_done,
- .render = sdl_render,
- };
|