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.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

588 Zeilen
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. };