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.

616 lines
19KB

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