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.
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

588 líneas
18KB

  1. #include <stdio.h>
  2. #include <SDL2/SDL.h>
  3. #include "render.h"
  4. #include "ppu.h"
  5. #include "mapper.h"
  6. static SDL_Color nes_palette[64] = {
  7. {0x80,0x80,0x80}, {0x00,0x00,0xBB}, {0x37,0x00,0xBF}, {0x84,0x00,0xA6},
  8. {0xBB,0x00,0x6A}, {0xB7,0x00,0x1E}, {0xB3,0x00,0x00}, {0x91,0x26,0x00},
  9. {0x7B,0x2B,0x00}, {0x00,0x3E,0x00}, {0x00,0x48,0x0D}, {0x00,0x3C,0x22},
  10. {0x00,0x2F,0x66}, {0x00,0x00,0x00}, {0x05,0x05,0x05}, {0x05,0x05,0x05},
  11. {0xC8,0xC8,0xC8}, {0x00,0x59,0xFF}, {0x44,0x3C,0xFF}, {0xB7,0x33,0xCC},
  12. {0xFF,0x33,0xAA}, {0xFF,0x37,0x5E}, {0xFF,0x37,0x1A}, {0xD5,0x4B,0x00},
  13. {0xC4,0x62,0x00}, {0x3C,0x7B,0x00}, {0x1E,0x84,0x15}, {0x00,0x95,0x66},
  14. {0x00,0x84,0xC4}, {0x11,0x11,0x11}, {0x09,0x09,0x09}, {0x09,0x09,0x09},
  15. {0xFF,0xFF,0xFF}, {0x00,0x95,0xFF}, {0x6F,0x84,0xFF}, {0xD5,0x6F,0xFF},
  16. {0xFF,0x77,0xCC}, {0xFF,0x6F,0x99}, {0xFF,0x7B,0x59}, {0xFF,0x91,0x5F},
  17. {0xFF,0xA2,0x33}, {0xA6,0xBF,0x00}, {0x51,0xD9,0x6A}, {0x4D,0xD5,0xAE},
  18. {0x00,0xD9,0xFF}, {0x66,0x66,0x66}, {0x0D,0x0D,0x0D}, {0x0D,0x0D,0x0D},
  19. {0xFF,0xFF,0xFF}, {0x84,0xBF,0xFF}, {0xBB,0xBB,0xFF}, {0xD0,0xBB,0xFF},
  20. {0xFF,0xBF,0xEA}, {0xFF,0xBF,0xCC}, {0xFF,0xC4,0xB7}, {0xFF,0xCC,0xAE},
  21. {0xFF,0xD9,0xA2}, {0xCC,0xE1,0x99}, {0xAE,0xEE,0xB7}, {0xAA,0xF7,0xEE},
  22. {0xB3,0xEE,0xFF}, {0xDD,0xDD,0xDD}, {0x11,0x11,0x11}, {0x11,0x11,0x11}
  23. };
  24. static inline uint8_t* chr_mem(const nes_ppu* ppu,
  25. uint16_t addr) {
  26. return ppu->mapper->chr_addr(ppu->mapper, addr);
  27. }
  28. typedef struct {
  29. SDL_Window* window;
  30. SDL_Renderer* renderer;
  31. SDL_Surface* background;
  32. SDL_Surface* background_line;
  33. SDL_Surface* foreground;
  34. SDL_Surface* sprite;
  35. SDL_Surface* target;
  36. } sdl_render_data;
  37. static sdl_render_data the_render_data = {0};
  38. static int sdl_render_init(nes_Renderer* rend) {
  39. sdl_render_data* data = &the_render_data;
  40. int status = SDL_Init(SDL_INIT_VIDEO);
  41. if (0 != status) {
  42. fprintf(stderr, "SDL: Failed to initialize\n");
  43. } else {
  44. SDL_DisplayMode mode = {0};
  45. SDL_GetCurrentDisplayMode(0, &mode);
  46. int yscale = (mode.h - 1) / nes_ppu_scan_h;
  47. int xscale = mode.w / nes_ppu_scan_w;
  48. int scale = (xscale < yscale ? xscale : yscale);
  49. data->window = SDL_CreateWindow(
  50. "NESe",
  51. SDL_WINDOWPOS_UNDEFINED,
  52. SDL_WINDOWPOS_UNDEFINED,
  53. nes_ppu_scan_w * scale,
  54. nes_ppu_scan_h * scale,
  55. 0
  56. );
  57. if (NULL == data->window) {
  58. fprintf(stderr, "SDL: Failed to create window\n");
  59. SDL_Quit();
  60. status = -1;
  61. }
  62. }
  63. if (0 == status) {
  64. data->renderer = SDL_CreateRenderer(data->window, -1, 0);
  65. if (NULL == data->renderer) {
  66. fprintf(stderr, "SDL: Failed to create renderer\n");
  67. SDL_DestroyWindow(data->window);
  68. SDL_Quit();
  69. status = -1;
  70. }
  71. }
  72. if (0 == status) {
  73. data->background = SDL_CreateRGBSurfaceWithFormat(
  74. 0, nes_ppu_render_w, nes_ppu_render_h,
  75. 8, SDL_PIXELFORMAT_INDEX8
  76. );
  77. if (NULL == data->background) {
  78. fprintf(stderr, "SDL: Failed to create background\n");
  79. SDL_DestroyRenderer(data->renderer);
  80. SDL_DestroyWindow(data->window);
  81. SDL_Quit();
  82. status = -1;
  83. }
  84. }
  85. if (0 == status) {
  86. data->background_line = SDL_CreateRGBSurfaceWithFormat(
  87. 0, nes_ppu_render_w + 8, 8,
  88. 8, SDL_PIXELFORMAT_INDEX8
  89. );
  90. if (NULL == data->background_line) {
  91. fprintf(stderr, "SDL: Failed to create block buffer\n");
  92. SDL_FreeSurface(data->background);
  93. SDL_DestroyRenderer(data->renderer);
  94. SDL_DestroyWindow(data->window);
  95. SDL_Quit();
  96. status = -1;
  97. }
  98. }
  99. if (0 == status) {
  100. data->foreground = SDL_CreateRGBSurfaceWithFormat(
  101. 0, nes_ppu_render_w, nes_ppu_render_h,
  102. 8, SDL_PIXELFORMAT_INDEX8
  103. );
  104. if (NULL == data->foreground) {
  105. fprintf(stderr, "SDL: Failed to create foreground\n");
  106. SDL_FreeSurface(data->background_line);
  107. SDL_FreeSurface(data->background);
  108. SDL_DestroyRenderer(data->renderer);
  109. SDL_DestroyWindow(data->window);
  110. SDL_Quit();
  111. status = -1;
  112. }
  113. }
  114. if (0 == status) {
  115. data->sprite = SDL_CreateRGBSurfaceWithFormat(
  116. 0, 8, 16, 8, SDL_PIXELFORMAT_INDEX8
  117. );
  118. if (NULL == data->sprite) {
  119. fprintf(stderr, "SDL: Failed to create sprite\n");
  120. SDL_FreeSurface(data->foreground);
  121. SDL_FreeSurface(data->background_line);
  122. SDL_FreeSurface(data->background);
  123. SDL_DestroyRenderer(data->renderer);
  124. SDL_DestroyWindow(data->window);
  125. SDL_Quit();
  126. status = -1;
  127. }
  128. }
  129. if (0 == status) {
  130. data->target = SDL_CreateRGBSurfaceWithFormat(
  131. 0U, nes_ppu_render_w, nes_ppu_render_h,
  132. 24U, SDL_PIXELFORMAT_RGB888
  133. );
  134. if (NULL == data->target) {
  135. fprintf(stderr, "SDL: Failed to create target\n");
  136. SDL_FreeSurface(data->sprite);
  137. SDL_FreeSurface(data->foreground);
  138. SDL_FreeSurface(data->background_line);
  139. SDL_FreeSurface(data->background);
  140. SDL_DestroyRenderer(data->renderer);
  141. SDL_DestroyWindow(data->window);
  142. SDL_Quit();
  143. status = -1;
  144. }
  145. }
  146. if (0 == status) {
  147. SDL_SetPaletteColors(data->background->format->palette,
  148. nes_palette, 0U, 64U);
  149. SDL_SetColorKey(data->background, SDL_TRUE, 0xFFU);
  150. SDL_SetPaletteColors(data->foreground->format->palette,
  151. nes_palette, 0U, 64U);
  152. SDL_SetColorKey(data->foreground, SDL_TRUE, 0xFFU);
  153. SDL_SetPaletteColors(data->sprite->format->palette,
  154. nes_palette, 0U, 64U);
  155. SDL_SetColorKey(data->sprite, SDL_TRUE, 0xFFU);
  156. rend->data = &the_render_data;
  157. }
  158. return status;
  159. }
  160. static void sdl_render_done(nes_Renderer* rend) {
  161. sdl_render_data* data = (sdl_render_data*)rend->data;
  162. SDL_FreeSurface(data->target);
  163. SDL_FreeSurface(data->sprite);
  164. SDL_FreeSurface(data->foreground);
  165. SDL_FreeSurface(data->background_line);
  166. SDL_FreeSurface(data->background);
  167. SDL_DestroyRenderer(data->renderer);
  168. SDL_DestroyWindow(data->window);
  169. SDL_Quit();
  170. }
  171. static inline void render_sprite_line(
  172. const nes_ppu* ppu, int index, int y, const uint8_t* pal,
  173. uint8_t* dst, int start, int end, const uint8_t* back) {
  174. uint8_t* sprite = chr_mem(ppu, index * 16U);
  175. uint8_t lo = sprite[0U + y] << start;
  176. uint8_t hi = sprite[8U + y] << start;
  177. for (int x = start; x < end; ++x) {
  178. if (back && *back != 0xFFU) {
  179. *dst = *back;
  180. } else {
  181. int pal_idx = (((hi & 0x80) >> 6) | ((lo & 0x80) >> 7));
  182. if (pal_idx) *dst = pal[pal_idx];
  183. }
  184. if (back) ++back;
  185. dst++;
  186. hi <<= 1;
  187. lo <<= 1;
  188. }
  189. }
  190. static inline void render_sprite_line_flip(
  191. const nes_ppu* ppu, int index, int y, const uint8_t* pal,
  192. uint8_t* dst, int start, int end, const uint8_t* back) {
  193. uint8_t* sprite = chr_mem(ppu, index * 16U);
  194. uint8_t lo = sprite[0U + y] >> start;
  195. uint8_t hi = sprite[8U + y] >> start;
  196. for (int x = start; x < end; ++x) {
  197. if (back && *back != 0xFFU) {
  198. *dst = *back;
  199. } else {
  200. int pal_idx = (((hi & 1) << 1) | (lo & 1));
  201. if (pal_idx) *dst = pal[pal_idx];
  202. }
  203. if (back) ++back;
  204. dst++;
  205. hi >>= 1;
  206. lo >>= 1;
  207. }
  208. }
  209. static inline void render_bg_sprite_line(
  210. const nes_ppu* ppu, int index, int y, const uint8_t* pal,
  211. uint8_t* dst, int start, int end) {
  212. uint8_t* sprite = chr_mem(ppu, index * 16U);
  213. uint8_t lo = sprite[0U + y] << (start % 8);
  214. uint8_t hi = sprite[8U + y] << (start % 8);
  215. for (int x = start; x < end; ++x) {
  216. int pal_idx = (((hi & 0x80) >> 6) | ((lo & 0x80) >> 7));
  217. *dst = (pal_idx ? pal[pal_idx] : 0xFFU);
  218. dst++;
  219. hi <<= 1;
  220. lo <<= 1;
  221. }
  222. }
  223. static inline void render_bg_scanline_area(
  224. const nes_ppu* ppu, int page,
  225. uint8_t* dst, int x, int y, int w) {
  226. int block_x = x / 8;
  227. int line_end = x + w;
  228. int block_y = y / 8;
  229. y = y % 8;
  230. int bank = (ppu->control & ppu_Control_Back_Bank) ? 0x100 : 0;
  231. const uint8_t* indexes = nes_map_vram_addr(ppu->mapper,
  232. page << 10);
  233. const uint8_t* attrs = indexes + 960U;
  234. const uint8_t* index = indexes +
  235. (block_y * nes_ppu_blocks_w) +
  236. block_x;
  237. while (x < line_end) {
  238. int attr_idx = ((block_y / 4) * 8) + (block_x / 4);
  239. int shift = 2 * ((block_y & 0b10) |
  240. ((block_x & 0b10) >> 1));
  241. int pal_idx = (attrs[attr_idx] >> shift) & 3;
  242. const uint8_t* pal = &ppu->palette[pal_idx * 4];
  243. int end = (x + 8) & ~7;
  244. if (end > line_end) end = line_end;
  245. render_bg_sprite_line(ppu, bank + *index, y,
  246. pal, dst, x, end);
  247. ++index;
  248. ++block_x;
  249. dst += (end - x);
  250. x = end;
  251. }
  252. }
  253. static void render_bg_scanline(const nes_ppu* ppu,/* int scanline,*/
  254. uint8_t* dst) {
  255. int page = (ppu->control & ppu_Control_Nametable_Mask);
  256. int x = ppu->scroll_x;
  257. int y = ppu->scroll_y;
  258. int w = (nes_ppu_render_w - x);
  259. if (!(ppu->mask & ppu_Mask_Left_Back)) {
  260. // Handle column 0 flag - need to fill with transparency
  261. memset(dst, 0xFFU, 8);
  262. w -= 8;
  263. x += 8;
  264. dst += 8;
  265. }
  266. render_bg_scanline_area(ppu, page, dst, x, y, w);
  267. render_bg_scanline_area(ppu, page ^ 1, dst + w, 0, y,
  268. nes_ppu_render_w - w);
  269. }
  270. static void render_line_sprites(nes_ppu* ppu, uint8_t* dst_line,
  271. int scanline, const uint8_t* back,
  272. const oam_sprite* sprites,
  273. int n_sprites) {
  274. for (int i_sprite = n_sprites - 1; i_sprite >= 0; --i_sprite) {
  275. const oam_sprite* sprite = &sprites[i_sprite];
  276. int index = sprite->index;
  277. int bank = (ppu->control & ppu_Control_Sprite_Bank) ?
  278. 0x100 : 0;
  279. if (ppu->control & ppu_Control_Sprite_Size) {
  280. bank = (index & 1) ? 0x100 : 0;
  281. index &= 0xFEU;
  282. }
  283. index += bank;
  284. int y = scanline - (sprite->y + 1);
  285. if (ppu->control & ppu_Control_Sprite_Size) {
  286. if (y >= 8) {
  287. index ^= 1;
  288. y -= 8;
  289. }
  290. if (sprite->attr & oam_Attr_Flip_Y) {
  291. index ^= 1;
  292. }
  293. }
  294. int pal_idx = (sprite->attr & oam_Attr_Pal_Mask);
  295. const uint8_t* pal = &ppu->palette[16 + (pal_idx * 4)];
  296. if (sprite->attr & oam_Attr_Flip_Y) y = 7 - y;
  297. int end = nes_ppu_render_w - sprite->x;
  298. if (end > 8) end = 8;
  299. int start = 0;
  300. if ( !(ppu->mask & ppu_Mask_Left_Sprite) &&
  301. sprite->x < 8) {
  302. start = 8 - sprite->x;
  303. }
  304. if (sprite->attr & oam_Attr_Flip_X) {
  305. render_sprite_line_flip(
  306. ppu, index, y, pal,
  307. dst_line + sprite->x + start, start, end,
  308. (sprite->attr & oam_Attr_Background) ?
  309. (back + sprite->x + start) : NULL
  310. );
  311. } else {
  312. render_sprite_line(
  313. ppu, index, y, pal,
  314. dst_line + sprite->x + start, start, end,
  315. (sprite->attr & oam_Attr_Background) ?
  316. (back + sprite->x + start) : NULL
  317. );
  318. }
  319. }
  320. }
  321. // Check sprite (0 only) collision on a scanline
  322. // This assumes that we've verified that this sprite
  323. // intersects with this scanline.
  324. // Scanline is 0-239 from inside the rendering window
  325. // (though we should never see this called with 0).
  326. static int eval_sprite_line(const nes_ppu* ppu, int y,
  327. const oam_sprite* sprite,
  328. const uint8_t* chr,
  329. const uint8_t* back) {
  330. int hit_pos = -1;
  331. // TODO: Handle Column 0 mask
  332. if (sprite->attr & oam_Attr_Flip_Y) y = 7 - y;
  333. uint8_t lo = chr[0U + y];
  334. uint8_t hi = chr[8U + y];
  335. back += sprite->x;
  336. int end = nes_ppu_render_w;
  337. if (end > sprite->x + 8) end = sprite->x + 8;
  338. for (int x = sprite->x; x < end; ++x) {
  339. int pal_idx = (sprite->attr & oam_Attr_Flip_X) ?
  340. (((hi & 1) << 1) | (lo & 1)) :
  341. (((hi & 0x80) >> 6) | ((lo & 0x80) >> 7));
  342. if (pal_idx && *back != 0xFFU) {
  343. hit_pos = x;
  344. break;
  345. }
  346. ++back;
  347. if (sprite->attr & oam_Attr_Flip_X) {
  348. hi >>= 1;
  349. lo >>= 1;
  350. } else {
  351. hi <<= 1;
  352. lo <<= 1;
  353. }
  354. }
  355. return hit_pos;
  356. }
  357. static void update_scanline_hit(nes_ppu* ppu, uint8_t* back_line,
  358. int scanline) {
  359. const oam_sprite* sprite = &ppu->oam[0];
  360. int bank = (ppu->control & ppu_Control_Sprite_Bank) ?
  361. 0x100 : 0;
  362. if ( !(ppu->mask & ppu_Mask_Left_Sprite) &&
  363. sprite->x < 8) {
  364. return;
  365. }
  366. int y_pos = (sprite->y + 1);
  367. int y = scanline - y_pos;
  368. if (0 > y) return;
  369. int h = (ppu->control & ppu_Control_Sprite_Size) ? 16 : 8;
  370. if (y >= h) return;
  371. int index = sprite->index;
  372. if (ppu->control & ppu_Control_Sprite_Size) {
  373. bank = (index & 1) ? 0x100 : 0;
  374. index &= 0xFEU;
  375. }
  376. index += bank;
  377. if (ppu->control & ppu_Control_Sprite_Size) {
  378. if (y >= 8) {
  379. index ^= 1;
  380. y -= 8;
  381. }
  382. if (sprite->attr & oam_Attr_Flip_Y) {
  383. index ^= 1;
  384. }
  385. }
  386. const uint8_t* chr = chr_mem(ppu, index * 16U);
  387. int hit = eval_sprite_line(ppu, y, sprite, chr, back_line);
  388. if (hit >= 0) {
  389. REND_LOG("Upcoming hit @ %d, %d\n", scanline + 1, hit);
  390. REND_LOG("(Currently @ %d, %d)\n", ppu->scanline, ppu->cycle);
  391. ppu->hit_line = scanline;
  392. ppu->hit_dot = hit;
  393. }
  394. }
  395. static int select_line_sprites(const nes_ppu* ppu, int scanline,
  396. oam_sprite* sprites, int max) {
  397. int n_sprites = 0;
  398. for ( int i_sprite = 0;
  399. i_sprite < nes_ppu_oam_sprite_count && n_sprites < max;
  400. ++i_sprite) {
  401. const oam_sprite* sprite = &ppu->oam[i_sprite];
  402. int y_pos = (sprite->y + 1);
  403. int y = scanline - y_pos;
  404. if (0 > y) continue;
  405. int h = (ppu->control & ppu_Control_Sprite_Size) ? 16 : 8;
  406. if (y >= h) continue;
  407. *sprites = *sprite;
  408. ++sprites;
  409. ++n_sprites;
  410. }
  411. return n_sprites;
  412. }
  413. static void dump_line_sprites(const nes_ppu* ppu, int line,
  414. const oam_sprite* sprites, int n) {
  415. while (n-- > 0) {
  416. PPU_LOG("PPU: Line %3d: Sprite $%02x @ %3d, %3d (%02x)\n",
  417. line, sprites->index, sprites->x, sprites->y,
  418. sprites->attr);
  419. ++sprites;
  420. }
  421. }
  422. static void render_scanline(nes_ppu* ppu, int line,
  423. sdl_render_data* data) {
  424. SDL_Rect dst_rect = {
  425. .x = 0,
  426. .y = line,
  427. .w = nes_ppu_render_w,
  428. .h = 1,
  429. };
  430. if (line >= 0) {
  431. // Emulate the happy part of the backdrop override quirk
  432. int pal_idx = (ppu->addr >= nes_ppu_mem_pal_start) ?
  433. (ppu->addr & (nes_ppu_mem_pal_size - 1)) : 0;
  434. SDL_Color ext = nes_palette[ppu->palette[pal_idx]];
  435. SDL_FillRect(data->target, &dst_rect, ((int)ext.r << 16) |
  436. ((int)ext.g << 8) |
  437. ext.b);
  438. }
  439. if (!(ppu->mask & (ppu_Mask_Sprite | ppu_Mask_Back))) {
  440. // Do nothing if BOTH are disabled.
  441. return;
  442. }
  443. SDL_Rect src_rect = {
  444. .x = 0,
  445. .y = 0,
  446. .w = nes_ppu_render_w,
  447. .h = 1,
  448. };
  449. uint8_t* background = data->background->pixels;
  450. // We check for hits if EITHER layer is enabled.
  451. render_bg_scanline(ppu, background);
  452. if (ppu->hit_line <= 0) {
  453. update_scanline_hit(ppu, background, line);
  454. }
  455. oam_sprite line_sprites[8] = {0};
  456. int n_sprites = select_line_sprites(ppu, line,
  457. line_sprites, 8);
  458. dump_line_sprites(ppu, line, line_sprites, n_sprites);
  459. if (ppu->mask & ppu_Mask_Back) {
  460. SDL_BlitSurface(data->background, &src_rect,
  461. data->target, &dst_rect);
  462. }
  463. if (ppu->mask & ppu_Mask_Sprite) {
  464. uint8_t* foreground = data->foreground->pixels;
  465. memset(foreground, 0xFFU, nes_ppu_render_w);
  466. render_line_sprites(ppu, foreground, line, background,
  467. line_sprites, n_sprites);
  468. SDL_BlitSurface(data->foreground, &src_rect,
  469. data->target, &dst_rect);
  470. }
  471. }
  472. static int sdl_render(nes_Renderer* rend, nes_ppu* ppu) {
  473. int status = 0;
  474. sdl_render_data* data = (sdl_render_data*)rend->data;
  475. if (ppu->scanline < nes_ppu_prerender) {
  476. REND_LOG("Scanline %3d -> Prerender\n", ppu->scanline);
  477. // TODO: Perform evaluations here?
  478. } else if (ppu->scanline < nes_ppu_prerender +
  479. nes_ppu_height) {
  480. REND_LOG("Scanline %3d : N %d X %d Y %d\n",
  481. ppu->scanline,
  482. ppu->control & ppu_Control_Nametable_Mask,
  483. ppu->scroll_x, ppu->scroll_y);
  484. int line = ppu->scanline - (int)nes_ppu_prerender;
  485. render_scanline(ppu, line, data);
  486. } else {
  487. REND_LOG("Scanline %3d -> Postrender\n", ppu->scanline);
  488. SDL_Texture* texture = SDL_CreateTextureFromSurface(
  489. data->renderer, data->target
  490. );
  491. SDL_RenderCopy(data->renderer, texture, NULL, NULL);
  492. SDL_RenderPresent(data->renderer);
  493. SDL_DestroyTexture(texture);
  494. status = 1;
  495. }
  496. return status;
  497. }
  498. nes_Renderer sdl_renderer = {
  499. .init = sdl_render_init,
  500. .done = sdl_render_done,
  501. .render = sdl_render,
  502. };