NESe (pronounced "Nessie") is a NES emulator based on the e6502 emulator, also written in C with a focus on speed and portability for use on embedded platforms, especially ARM.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

509 lines
16KB

  1. #include <SDL2/SDL.h>
  2. #include "render.h"
  3. #include "ppu.h"
  4. static SDL_Color nes_palette[64] = {
  5. {0x80,0x80,0x80}, {0x00,0x00,0xBB}, {0x37,0x00,0xBF}, {0x84,0x00,0xA6},
  6. {0xBB,0x00,0x6A}, {0xB7,0x00,0x1E}, {0xB3,0x00,0x00}, {0x91,0x26,0x00},
  7. {0x7B,0x2B,0x00}, {0x00,0x3E,0x00}, {0x00,0x48,0x0D}, {0x00,0x3C,0x22},
  8. {0x00,0x2F,0x66}, {0x00,0x00,0x00}, {0x05,0x05,0x05}, {0x05,0x05,0x05},
  9. {0xC8,0xC8,0xC8}, {0x00,0x59,0xFF}, {0x44,0x3C,0xFF}, {0xB7,0x33,0xCC},
  10. {0xFF,0x33,0xAA}, {0xFF,0x37,0x5E}, {0xFF,0x37,0x1A}, {0xD5,0x4B,0x00},
  11. {0xC4,0x62,0x00}, {0x3C,0x7B,0x00}, {0x1E,0x84,0x15}, {0x00,0x95,0x66},
  12. {0x00,0x84,0xC4}, {0x11,0x11,0x11}, {0x09,0x09,0x09}, {0x09,0x09,0x09},
  13. {0xFF,0xFF,0xFF}, {0x00,0x95,0xFF}, {0x6F,0x84,0xFF}, {0xD5,0x6F,0xFF},
  14. {0xFF,0x77,0xCC}, {0xFF,0x6F,0x99}, {0xFF,0x7B,0x59}, {0xFF,0x91,0x5F},
  15. {0xFF,0xA2,0x33}, {0xA6,0xBF,0x00}, {0x51,0xD9,0x6A}, {0x4D,0xD5,0xAE},
  16. {0x00,0xD9,0xFF}, {0x66,0x66,0x66}, {0x0D,0x0D,0x0D}, {0x0D,0x0D,0x0D},
  17. {0xFF,0xFF,0xFF}, {0x84,0xBF,0xFF}, {0xBB,0xBB,0xFF}, {0xD0,0xBB,0xFF},
  18. {0xFF,0xBF,0xEA}, {0xFF,0xBF,0xCC}, {0xFF,0xC4,0xB7}, {0xFF,0xCC,0xAE},
  19. {0xFF,0xD9,0xA2}, {0xCC,0xE1,0x99}, {0xAE,0xEE,0xB7}, {0xAA,0xF7,0xEE},
  20. {0xB3,0xEE,0xFF}, {0xDD,0xDD,0xDD}, {0x11,0x11,0x11}, {0x11,0x11,0x11}
  21. };
  22. typedef struct {
  23. SDL_Window* window;
  24. SDL_Renderer* renderer;
  25. SDL_Surface* background;
  26. SDL_Surface* sprite8;
  27. SDL_Surface* target;
  28. } sdl_render_data;
  29. static sdl_render_data the_render_data = {0};
  30. static int sdl_render_init(nes_Renderer* rend) {
  31. sdl_render_data* data = &the_render_data;
  32. int status = SDL_Init(SDL_INIT_VIDEO);
  33. if (0 != status) {
  34. fprintf(stderr, "SDL: Failed to initialize\n");
  35. } else {
  36. data->window = SDL_CreateWindow(
  37. "NESe",
  38. SDL_WINDOWPOS_UNDEFINED,
  39. SDL_WINDOWPOS_UNDEFINED,
  40. nes_ppu_scan_w * 4,
  41. nes_ppu_scan_h * 4,
  42. 0
  43. );
  44. if (NULL == data->window) {
  45. fprintf(stderr, "SDL: Failed to create window\n");
  46. SDL_Quit();
  47. status = -1;
  48. }
  49. }
  50. if (0 == status) {
  51. data->renderer = SDL_CreateRenderer(data->window, -1, 0);
  52. if (NULL == data->renderer) {
  53. fprintf(stderr, "SDL: Failed to create renderer\n");
  54. SDL_DestroyWindow(data->window);
  55. SDL_Quit();
  56. status = -1;
  57. }
  58. }
  59. if (0 == status) {
  60. data->background = SDL_CreateRGBSurfaceWithFormat(
  61. 0, nes_ppu_render_w + 8, nes_ppu_render_h + 8,
  62. 8, SDL_PIXELFORMAT_INDEX8
  63. );
  64. if (NULL == data->background) {
  65. fprintf(stderr, "SDL: Failed to create background 0\n");
  66. SDL_DestroyRenderer(data->renderer);
  67. SDL_DestroyWindow(data->window);
  68. SDL_Quit();
  69. status = -1;
  70. }
  71. }
  72. if (0 == status) {
  73. data->sprite8 = SDL_CreateRGBSurfaceWithFormat(
  74. 0, 8, 8, 8, SDL_PIXELFORMAT_INDEX8
  75. );
  76. if (NULL == data->sprite8) {
  77. fprintf(stderr, "SDL: Failed to create sprite\n");
  78. SDL_FreeSurface(data->background);
  79. SDL_DestroyRenderer(data->renderer);
  80. SDL_DestroyWindow(data->window);
  81. SDL_Quit();
  82. status = -1;
  83. }
  84. }
  85. if (0 == status) {
  86. data->target = SDL_CreateRGBSurfaceWithFormat(
  87. 0U, nes_ppu_render_w, nes_ppu_render_h, 24U,
  88. SDL_PIXELFORMAT_RGB888
  89. );
  90. if (NULL == data->target) {
  91. fprintf(stderr, "SDL: Failed to create target\n");
  92. SDL_FreeSurface(data->sprite8);
  93. SDL_FreeSurface(data->background);
  94. SDL_DestroyRenderer(data->renderer);
  95. SDL_DestroyWindow(data->window);
  96. SDL_Quit();
  97. status = -1;
  98. }
  99. }
  100. if (0 == status) {
  101. SDL_SetPaletteColors(data->background->format->palette,
  102. nes_palette, 0U, 64U);
  103. SDL_SetColorKey(data->background, SDL_TRUE, 0xFFU);
  104. SDL_SetPaletteColors(data->sprite8->format->palette,
  105. nes_palette, 0U, 64U);
  106. SDL_SetColorKey(data->sprite8, SDL_TRUE, 0xFFU);
  107. rend->data = &the_render_data;
  108. }
  109. return status;
  110. }
  111. static void sdl_render_done(nes_Renderer* rend) {
  112. sdl_render_data* data = (sdl_render_data*)rend->data;
  113. SDL_FreeSurface(data->target);
  114. SDL_FreeSurface(data->sprite8);
  115. SDL_FreeSurface(data->background);
  116. SDL_DestroyRenderer(data->renderer);
  117. SDL_DestroyWindow(data->window);
  118. SDL_Quit();
  119. }
  120. typedef enum {
  121. Render_Mode_Sprite = 0b00000,
  122. Render_Mode_Background = 0b00001,
  123. Render_Mode_Behind = 0b00010,
  124. // Render_Mode_Collide = 0b00100,
  125. Render_Mode_Flip_X = 0b01000,
  126. Render_Mode_Flip_Y = 0b10000,
  127. } Render_Mode;
  128. static void render_bg_sprite(const nes_ppu* ppu, int index,
  129. const uint8_t* pal,
  130. void* loc, int pitch) {
  131. uint8_t* sprite = &ppu->chr_mem[index * 16U];
  132. uint8_t* dst_line = (uint8_t*)loc;
  133. for (int y = 8; y > 0; --y) {
  134. uint8_t lo = sprite[0U];
  135. uint8_t hi = sprite[8U];
  136. uint8_t* dst = dst_line;
  137. for (int x = 8; x > 0; --x) {
  138. int pal_idx = ( ((hi & 0x80) >> 6) |
  139. ((lo & 0x80) >> 7));
  140. *dst++ = (pal_idx ? pal[pal_idx] : 0xFFU);
  141. hi <<= 1;
  142. lo <<= 1;
  143. }
  144. dst_line += pitch;
  145. ++sprite;
  146. }
  147. }
  148. static void render_background_area(const nes_ppu* ppu, int page,
  149. void* buffer, int pitch,
  150. int xs, int ys, int w, int h) {
  151. int bank = (ppu->control & ppu_Control_Back_Bank) ? 0x100 : 0;
  152. const uint8_t* index_line = &ppu->vram[page * 0x400U];
  153. const uint8_t* attrs = index_line + 960U;
  154. index_line += xs + (ys * nes_ppu_blocks_w);
  155. uint8_t* dst_line = (uint8_t*)buffer;
  156. for (int y = ys; y < h + ys; ++y) {
  157. uint8_t* dst = dst_line;
  158. const uint8_t* index = index_line;
  159. for (int x = xs; x < w + xs; ++x) {
  160. int attr_idx = ((y / 4) * 8) + (x / 4);
  161. int shift = 2 * ((y & 0b10) | ((x & 0b10) >> 1));
  162. int pal_idx = (attrs[attr_idx] >> shift) & 3;
  163. const uint8_t* pal = &ppu->palette[pal_idx * 4];
  164. render_bg_sprite(ppu, bank + *index, pal,
  165. dst, pitch);
  166. ++index;
  167. dst += 8;
  168. }
  169. dst_line += pitch * 8;
  170. index_line += nes_ppu_blocks_w;
  171. }
  172. }
  173. static void render_background_line(const nes_ppu* ppu, int line,
  174. void* buffer, int pitch) {
  175. // TODO: Handle vertical scrolling
  176. // TODO: Handle column 0 flag
  177. buffer += line * pitch * 8U;
  178. int page = (ppu->control & ppu_Control_Nametable_Mask);
  179. int x = ppu->scroll_x / 8;
  180. // Left
  181. render_background_area(
  182. ppu, page, buffer, pitch,
  183. x, line,
  184. nes_ppu_blocks_w - x, 1
  185. );
  186. // Right
  187. buffer += (nes_ppu_blocks_w - x) * 8U;
  188. render_background_area(
  189. ppu, page ^ 1, buffer, pitch,
  190. 0, line,
  191. 1U + x, 1
  192. );
  193. }
  194. static void render_sprite(nes_ppu* ppu, int index,
  195. const uint8_t* pal, Render_Mode mode,
  196. void* loc, int pitch,
  197. const void* back_loc, int back_pitch) {
  198. uint8_t* sprite = &ppu->chr_mem[index * 16U];
  199. uint8_t* dst_line = (uint8_t*)loc;
  200. const uint8_t* back_line = (uint8_t*)back_loc;
  201. int dx = 1;
  202. if (mode & Render_Mode_Flip_X) {
  203. dst_line += 7;
  204. back_line += 7;
  205. dx = -dx;
  206. }
  207. if (mode & Render_Mode_Flip_Y) {
  208. dst_line += (7 * pitch);
  209. back_line += (7 * back_pitch);
  210. pitch = -pitch;
  211. back_pitch = -back_pitch;
  212. }
  213. for (int y = 8; y > 0; --y) {
  214. uint8_t lo = sprite[0U];
  215. uint8_t hi = sprite[8U];
  216. uint8_t* dst = dst_line;
  217. const uint8_t* back = back_line;
  218. for (int x = 8; x > 0; --x) {
  219. int pal_idx = ( ((hi & 0x80) >> 6) |
  220. ((lo & 0x80) >> 7));
  221. int nes_pal_idx = (pal_idx ? pal[pal_idx] : 0xFFU);
  222. if ((mode & Render_Mode_Behind) && *back != 0xFFU) {
  223. nes_pal_idx = 0xFFU;
  224. }
  225. *dst = nes_pal_idx;
  226. dst += dx;
  227. back += dx;
  228. hi <<= 1;
  229. lo <<= 1;
  230. }
  231. dst_line += pitch;
  232. back_line += back_pitch;
  233. ++sprite;
  234. }
  235. }
  236. typedef struct {
  237. uint8_t y;
  238. uint8_t index;
  239. uint8_t attr;
  240. uint8_t x;
  241. } oam_sprite;
  242. typedef enum {
  243. oam_Attr_Pal_Mask = 0b00000011,
  244. oam_Attr_Background = 0b00100000,
  245. oam_Attr_Flip_X = 0b01000000,
  246. oam_Attr_Flip_Y = 0b10000000,
  247. } oam_Attribute;
  248. typedef enum {
  249. oam_Index_Bank = 0b00000001,
  250. oam_Index_Tile_Mask = 0b11111110,
  251. } oam_Index;
  252. // Check sprite (0 only) collision on a scanline
  253. // This assumes that we've verified that this sprite
  254. // intersects with this scanline.
  255. // Scanline is 0-239 from inside the rendering window
  256. // (though we should never see this called with 0).
  257. static int eval_sprite_line(const nes_ppu* ppu, int line,
  258. const oam_sprite* sprite,
  259. const uint8_t* chr,
  260. const uint8_t* back) {
  261. int hit_pos = -1;
  262. int y = line - (sprite->y + 1);
  263. if (sprite->attr & oam_Attr_Flip_Y) y = 7 - y;
  264. uint8_t lo = chr[0U + y];
  265. uint8_t hi = chr[8U + y];
  266. back += sprite->x;
  267. for (int x = sprite->x; x < nes_ppu_render_w; ++x) {
  268. int pal_idx = (sprite->attr & oam_Attr_Flip_X) ?
  269. (((hi & 1) << 1) | (lo & 1)) :
  270. ( ((hi & 0x80) >> 6) |
  271. ((lo & 0x80) >> 7));
  272. if (pal_idx && *back != 0xFFU) {
  273. hit_pos = x;
  274. break;
  275. }
  276. ++back;
  277. if (sprite->attr & oam_Attr_Flip_X) {
  278. hi >>= 1;
  279. lo >>= 1;
  280. } else {
  281. hi <<= 1;
  282. lo <<= 1;
  283. }
  284. }
  285. return hit_pos;
  286. }
  287. static const SDL_Rect sprite_rect = {
  288. .x = 0,
  289. .y = 0,
  290. .w = 8,
  291. .h = 8,
  292. };
  293. static void render_sprites(nes_ppu* ppu,
  294. SDL_Surface* buffer,
  295. SDL_Surface* target,
  296. const void* back, int back_pitch) {
  297. int bank = (ppu->control & ppu_Control_Sprite_Bank) ?
  298. 0x100 : 0;
  299. const oam_sprite* sprites = (const oam_sprite*)ppu->oam;
  300. uint8_t* dst_origin = (uint8_t*)buffer->pixels;
  301. int pitch = buffer->pitch;
  302. const uint8_t* back_origin = (uint8_t*)back;
  303. for ( int i_sprite = nes_ppu_oam_sprite_count - 1;
  304. i_sprite >= 0; --i_sprite) {
  305. const oam_sprite* sprite = &sprites[i_sprite];
  306. if ( !(ppu->mask & ppu_Mask_Left_Sprite) &&
  307. sprite->x < 8) {
  308. continue;
  309. }
  310. int y = (sprite->y + 1);
  311. if (y >= nes_ppu_render_h) continue;
  312. uint8_t* dst = dst_origin;
  313. int back_offset = sprite->x + (y * back_pitch);
  314. const uint8_t* dst_back = back_offset + back_origin;
  315. // TODO: Support 8x16 sprites
  316. int index = bank + sprite->index;
  317. int pal_idx = (sprite->attr & oam_Attr_Pal_Mask);
  318. const uint8_t* pal = &ppu->palette[16 + (pal_idx * 4)];
  319. Render_Mode mode = (sprite->attr & oam_Attr_Background) ?
  320. Render_Mode_Behind :
  321. Render_Mode_Sprite;
  322. // if (i_sprite == 0) mode |= Render_Mode_Collide;
  323. if (sprite->attr & oam_Attr_Flip_X) {
  324. mode |= Render_Mode_Flip_X;
  325. }
  326. if (sprite->attr & oam_Attr_Flip_Y) {
  327. mode |= Render_Mode_Flip_Y;
  328. }
  329. render_sprite(ppu, index, pal, mode, dst, pitch,
  330. dst_back, back_pitch);
  331. SDL_Rect target_rect = {
  332. .x = sprite->x,
  333. .y = y,
  334. .w = 8,
  335. .h = 8,
  336. };
  337. SDL_BlitSurface(buffer, &sprite_rect,
  338. target, &target_rect);
  339. }
  340. }
  341. static void update_sprite_hit(nes_ppu* ppu,
  342. const void* back_line,
  343. int back_pitch) {
  344. const oam_sprite* sprite = (oam_sprite*)ppu->oam;
  345. int block_line = (ppu->scanline - nes_ppu_prerender) / 8U;
  346. int x_fine = ppu->scroll_x % 8;
  347. int index = sprite->index;
  348. if (ppu->control & ppu_Control_Sprite_Bank) {
  349. index += 0x100U;
  350. }
  351. const uint8_t* chr = &ppu->chr_mem[index * 16U];
  352. int render_line = block_line * 8U;
  353. int start_y = (sprite->y + 1) - render_line;
  354. int end_y = start_y + 8;
  355. if (start_y < 8 && end_y > 0) {
  356. if (start_y < 0) start_y = 0;
  357. if (end_y > 8) end_y = 8;
  358. int hit = -1;
  359. const uint8_t* back = (uint8_t*)back_line + x_fine;
  360. back += (render_line + start_y) * back_pitch;
  361. for (int y = start_y; y < end_y; ++y) {
  362. hit = eval_sprite_line(
  363. ppu, render_line + y,
  364. sprite, chr, back
  365. );
  366. if (hit >= 0) {
  367. ppu->hit_line = y + render_line;
  368. ppu->hit_dot = hit;
  369. break;
  370. }
  371. back += back_pitch;
  372. }
  373. }
  374. }
  375. static int sdl_render(nes_Renderer* rend, nes_ppu* ppu) {
  376. int status = 0;
  377. sdl_render_data* data = (sdl_render_data*)rend->data;
  378. if (ppu->scanline < nes_ppu_prerender) {
  379. // printf("Scanline %3d -> Prerender\n", ppu->scanline);
  380. // Emulate the happy part of the backdrop override quirk
  381. int pal_idx = (ppu->addr >= nes_ppu_mem_pal_start) ?
  382. (ppu->addr & (nes_ppu_mem_pal_size - 1)) : 0;
  383. SDL_Color ext = nes_palette[ppu->palette[pal_idx]];
  384. SDL_FillRect(data->target, NULL, ((int)ext.r << 16) |
  385. ((int)ext.g << 8) |
  386. ext.b);
  387. } else if (ppu->scanline < nes_ppu_prerender +
  388. nes_ppu_height) {
  389. int line = (ppu->scanline - (int)nes_ppu_prerender) / 8;
  390. // printf("Scanline %3d -> Line %2d @ X %d\n", ppu->scanline, line, ppu->scroll_x);
  391. if (ppu->mask & ppu_Mask_Back) {
  392. // TODO: Only re-render if VRAM/scroll changes
  393. render_background_line(ppu, line,
  394. data->background->pixels,
  395. data->background->pitch);
  396. // TODO: Y scroll support
  397. int x_fine = ppu->scroll_x % 8;
  398. // Check for Sprite 0 Hit
  399. if ( 0 >= ppu->hit_line &&
  400. (ppu->mask & ppu_Mask_Sprite)) {
  401. update_sprite_hit(ppu,
  402. data->background->pixels,
  403. data->background->pitch);
  404. }
  405. // Gotta render it now while scroll is set
  406. SDL_Rect back_rect = {
  407. .x = x_fine,
  408. .y = line * 8U,
  409. .w = nes_ppu_render_w,
  410. .h = 8U,
  411. };
  412. SDL_Rect render_rect = {
  413. .x = 0,
  414. .y = line * 8U,
  415. .w = nes_ppu_render_w,
  416. .h = 8U,
  417. };
  418. SDL_BlitSurface(data->background, &back_rect,
  419. data->target, &render_rect);
  420. }
  421. } else {
  422. // printf("Scanline %3d -> Postrender\n", ppu->scanline);
  423. if (ppu->mask & ppu_Mask_Sprite) {
  424. render_sprites(ppu, data->sprite8, data->target,
  425. data->background->pixels,
  426. data->background->pitch);
  427. }
  428. SDL_Texture* texture = SDL_CreateTextureFromSurface(
  429. data->renderer, data->target
  430. );
  431. SDL_RenderCopy(data->renderer, texture, NULL, NULL);
  432. SDL_RenderPresent(data->renderer);
  433. SDL_DestroyTexture(texture);
  434. status = 1;
  435. }
  436. return status;
  437. }
  438. nes_Renderer sdl_renderer = {
  439. .init = sdl_render_init,
  440. .done = sdl_render_done,
  441. .render = sdl_render,
  442. };