vc.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. // C implementation of the Virtual Console (VC) for demos.
  2. //
  3. // # Usage
  4. // ```c
  5. // // demo.c
  6. // // vc.c expectes render() to be defined and also supplies it's own entry point
  7. // // if needed (some platforms like WASM_PLATFORM do not have the main()
  8. // // entry point)
  9. // #include "vc.c"
  10. //
  11. // #define WIDTH 800
  12. // #define HEIGHT 600
  13. // static uint32_t pixels[WIDTH*HEIGHT];
  14. //
  15. // static Olivec_Canvas vc_render(float dt)
  16. // {
  17. // Olivec_Canvas oc = olivec_canvas(pixels, WIDTH, HEIGHT, WIDTH);
  18. // // ...
  19. // // ... render into oc ...
  20. // // ...
  21. // return oc;
  22. // }
  23. // ```
  24. //
  25. // # Build
  26. // ```console
  27. // $ clang -o demo.sdl -DVC_PLATFORM=VC_SDL_PLATFORM demo.c -lSDL2
  28. // $ clang -o demo.term -DVC_PLATFORM=VC_TERM_PLATFORM demo.c
  29. // $ clang -fno-builtin --target=wasm32 --no-standard-libraries -Wl,--no-entry -Wl,--export=render -Wl,--allow-undefined -o demo.wasm -DVC_PLATFORM=VC_WASM_PLATFORM demo.c
  30. // ```
  31. #define OLIVEC_IMPLEMENTATION
  32. #include <olive.c>
  33. Olivec_Canvas vc_render(float dt);
  34. #ifndef VC_PLATFORM
  35. #error "Please define VC_PLATFORM macro"
  36. #endif
  37. // Possible values of VC_PLATFORM
  38. #define VC_WASM_PLATFORM 0
  39. #define VC_SDL_PLATFORM 1
  40. #define VC_TERM_PLATFORM 2
  41. #if VC_PLATFORM == VC_SDL_PLATFORM
  42. #include <stdio.h>
  43. #include <SDL2/SDL.h>
  44. #define return_defer(value) do { result = (value); goto defer; } while (0)
  45. static SDL_Texture *vc_sdl_texture = NULL;
  46. static size_t vc_sdl_actual_width = 0;
  47. static size_t vc_sdl_actual_height = 0;
  48. static bool vc_sdl_resize_texture(SDL_Renderer *renderer, size_t new_width, size_t new_height)
  49. {
  50. if (vc_sdl_texture != NULL) SDL_DestroyTexture(vc_sdl_texture);
  51. vc_sdl_actual_width = new_width;
  52. vc_sdl_actual_height = new_height;
  53. vc_sdl_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, vc_sdl_actual_width, vc_sdl_actual_height);
  54. if (vc_sdl_texture == NULL) return false;
  55. return true;
  56. }
  57. int main(void)
  58. {
  59. int result = 0;
  60. SDL_Window *window = NULL;
  61. SDL_Renderer *renderer = NULL;
  62. {
  63. if (SDL_Init(SDL_INIT_VIDEO) < 0) return_defer(1);
  64. window = SDL_CreateWindow("Olivec", 0, 0, 0, 0, 0);
  65. if (window == NULL) return_defer(1);
  66. renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
  67. if (renderer == NULL) return_defer(1);
  68. Uint32 prev = SDL_GetTicks();
  69. bool pause = false;
  70. for (;;) {
  71. // Compute Delta Time
  72. Uint32 curr = SDL_GetTicks();
  73. float dt = (curr - prev)/1000.f;
  74. prev = curr;
  75. // Flush the events
  76. SDL_Event event;
  77. while (SDL_PollEvent(&event)) {
  78. switch (event.type) {
  79. case SDL_QUIT: {
  80. return_defer(0);
  81. } break;
  82. case SDL_KEYDOWN: {
  83. if (event.key.keysym.sym == SDLK_SPACE) pause = !pause;
  84. } break;
  85. }
  86. }
  87. SDL_Rect window_rect = {0, 0, vc_sdl_actual_width, vc_sdl_actual_height};
  88. if (!pause) {
  89. // Render the texture
  90. Olivec_Canvas oc_src = vc_render(dt);
  91. if (oc_src.width != vc_sdl_actual_width || oc_src.height != vc_sdl_actual_height) {
  92. if (!vc_sdl_resize_texture(renderer, oc_src.width, oc_src.height)) return_defer(1);
  93. SDL_SetWindowSize(window, vc_sdl_actual_width, vc_sdl_actual_height);
  94. }
  95. void *pixels_dst;
  96. int pitch;
  97. if (SDL_LockTexture(vc_sdl_texture, &window_rect, &pixels_dst, &pitch) < 0) return_defer(1);
  98. for (size_t y = 0; y < vc_sdl_actual_height; ++y) {
  99. // TODO: it would be cool if Olivec_Canvas supported pitch in bytes instead of pixels
  100. // It would be more flexible and we could draw on the locked texture memory directly
  101. memcpy((char*)pixels_dst + y*pitch, oc_src.pixels + y*vc_sdl_actual_width, vc_sdl_actual_width*sizeof(uint32_t));
  102. }
  103. SDL_UnlockTexture(vc_sdl_texture);
  104. }
  105. // Display the texture
  106. if (SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0) < 0) return_defer(1);
  107. if (SDL_RenderClear(renderer) < 0) return_defer(1);
  108. if (SDL_RenderCopy(renderer, vc_sdl_texture, &window_rect, &window_rect) < 0) return_defer(1);
  109. SDL_RenderPresent(renderer);
  110. }
  111. }
  112. defer:
  113. switch (result) {
  114. case 0:
  115. printf("OK\n");
  116. break;
  117. default:
  118. fprintf(stderr, "SDL ERROR: %s\n", SDL_GetError());
  119. }
  120. if (vc_sdl_texture) SDL_DestroyTexture(vc_sdl_texture);
  121. if (renderer) SDL_DestroyRenderer(renderer);
  122. if (window) SDL_DestroyWindow(window);
  123. SDL_Quit();
  124. return result;
  125. }
  126. #elif VC_PLATFORM == VC_TERM_PLATFORM
  127. #include <assert.h>
  128. #include <stdio.h>
  129. #include <stdlib.h>
  130. #include <string.h>
  131. #include <errno.h>
  132. #include <time.h>
  133. #include <unistd.h>
  134. #include <math.h>
  135. #define STB_IMAGE_WRITE_IMPLEMENTATION
  136. #include "stb_image_write.h"
  137. static size_t vc_term_actual_width = 0;
  138. static size_t vc_term_actual_height = 0;
  139. static size_t vc_term_scaled_down_width = 0;
  140. static size_t vc_term_scaled_down_height = 0;
  141. static int *vc_term_char_canvas = 0;
  142. int hsl256[][3] = {
  143. {0, 0, 0},
  144. {0, 100, 25},
  145. {120, 100, 25},
  146. {60, 100, 25},
  147. {240, 100, 25},
  148. {300, 100, 25},
  149. {180, 100, 25},
  150. {0, 0, 75},
  151. {0, 0, 50},
  152. {0, 100, 50},
  153. {120, 100, 50},
  154. {60, 100, 50},
  155. {240, 100, 50},
  156. {300, 100, 50},
  157. {180, 100, 50},
  158. {0, 0, 100},
  159. {0, 0, 0},
  160. {240, 99, 18},
  161. {240, 100, 26},
  162. {240, 100, 34},
  163. {240, 100, 42},
  164. {240, 100, 50},
  165. {120, 99, 18},
  166. {180, 99, 18},
  167. {197, 100, 26},
  168. {207, 100, 34},
  169. {213, 100, 42},
  170. {217, 100, 50},
  171. {120, 100, 26},
  172. {162, 100, 26},
  173. {180, 100, 26},
  174. {193, 100, 34},
  175. {202, 100, 42},
  176. {208, 100, 50},
  177. {120, 100, 34},
  178. {152, 100, 34},
  179. {166, 100, 34},
  180. {180, 100, 34},
  181. {191, 100, 42},
  182. {198, 100, 50},
  183. {120, 100, 42},
  184. {146, 100, 42},
  185. {157, 100, 42},
  186. {168, 100, 42},
  187. {180, 100, 42},
  188. {189, 100, 50},
  189. {120, 100, 50},
  190. {142, 100, 50},
  191. {151, 100, 50},
  192. {161, 100, 50},
  193. {170, 100, 50},
  194. {180, 100, 50},
  195. {0, 99, 18},
  196. {300, 99, 18},
  197. {282, 100, 26},
  198. {272, 100, 34},
  199. {266, 100, 42},
  200. {262, 100, 50},
  201. {60, 99, 18},
  202. {0, 0, 37},
  203. {240, 17, 45},
  204. {240, 33, 52},
  205. {240, 60, 60},
  206. {240, 100, 68},
  207. {77, 100, 26},
  208. {120, 17, 45},
  209. {180, 17, 45},
  210. {210, 33, 52},
  211. {220, 60, 60},
  212. {225, 100, 68},
  213. {87, 100, 34},
  214. {120, 33, 52},
  215. {150, 33, 52},
  216. {180, 33, 52},
  217. {200, 60, 60},
  218. {210, 100, 68},
  219. {93, 100, 42},
  220. {120, 60, 60},
  221. {140, 60, 60},
  222. {160, 60, 60},
  223. {180, 60, 60},
  224. {195, 100, 68},
  225. {97, 100, 50},
  226. {120, 100, 68},
  227. {135, 100, 68},
  228. {150, 100, 68},
  229. {165, 100, 68},
  230. {180, 100, 68},
  231. {0, 100, 26},
  232. {317, 100, 26},
  233. {300, 100, 26},
  234. {286, 100, 34},
  235. {277, 100, 42},
  236. {271, 100, 50},
  237. {42, 100, 26},
  238. {0, 17, 45},
  239. {300, 17, 45},
  240. {270, 33, 52},
  241. {260, 60, 60},
  242. {255, 100, 68},
  243. {60, 100, 26},
  244. {60, 17, 45},
  245. {0, 0, 52},
  246. {240, 20, 60},
  247. {240, 50, 68},
  248. {240, 100, 76},
  249. {73, 100, 34},
  250. {90, 33, 52},
  251. {120, 20, 60},
  252. {180, 20, 60},
  253. {210, 50, 68},
  254. {220, 100, 76},
  255. {82, 100, 42},
  256. {100, 60, 60},
  257. {120, 50, 68},
  258. {150, 50, 68},
  259. {180, 50, 68},
  260. {200, 100, 76},
  261. {88, 100, 50},
  262. {105, 100, 68},
  263. {120, 100, 76},
  264. {140, 100, 76},
  265. {160, 100, 76},
  266. {180, 100, 76},
  267. {0, 100, 34},
  268. {327, 100, 34},
  269. {313, 100, 34},
  270. {300, 100, 34},
  271. {288, 100, 42},
  272. {281, 100, 50},
  273. {32, 100, 34},
  274. {0, 33, 52},
  275. {330, 33, 52},
  276. {300, 33, 52},
  277. {280, 60, 60},
  278. {270, 100, 68},
  279. {46, 100, 34},
  280. {30, 33, 52},
  281. {0, 20, 60},
  282. {300, 20, 60},
  283. {270, 50, 68},
  284. {260, 100, 76},
  285. {60, 100, 34},
  286. {60, 33, 52},
  287. {60, 20, 60},
  288. {0, 0, 68},
  289. {240, 33, 76},
  290. {240, 100, 84},
  291. {71, 100, 42},
  292. {80, 60, 60},
  293. {90, 50, 68},
  294. {120, 33, 76},
  295. {180, 33, 76},
  296. {210, 100, 84},
  297. {78, 100, 50},
  298. {90, 100, 68},
  299. {100, 100, 76},
  300. {120, 100, 84},
  301. {150, 100, 84},
  302. {180, 100, 84},
  303. {0, 100, 42},
  304. {333, 100, 42},
  305. {322, 100, 42},
  306. {311, 100, 42},
  307. {300, 100, 42},
  308. {290, 100, 50},
  309. {26, 100, 42},
  310. {0, 60, 60},
  311. {340, 60, 60},
  312. {320, 60, 60},
  313. {300, 60, 60},
  314. {285, 100, 68},
  315. {37, 100, 42},
  316. {20, 60, 60},
  317. {0, 50, 68},
  318. {330, 50, 68},
  319. {300, 50, 68},
  320. {280, 100, 76},
  321. {48, 100, 42},
  322. {40, 60, 60},
  323. {30, 50, 68},
  324. {0, 33, 76},
  325. {300, 33, 76},
  326. {270, 100, 84},
  327. {60, 100, 42},
  328. {60, 60, 60},
  329. {60, 50, 68},
  330. {60, 33, 76},
  331. {0, 0, 84},
  332. {240, 100, 92},
  333. {69, 100, 50},
  334. {75, 100, 68},
  335. {80, 100, 76},
  336. {90, 100, 84},
  337. {120, 100, 92},
  338. {180, 100, 92},
  339. {0, 100, 50},
  340. {337, 100, 50},
  341. {328, 100, 50},
  342. {318, 100, 50},
  343. {309, 100, 50},
  344. {300, 100, 50},
  345. {22, 100, 50},
  346. {0, 100, 68},
  347. {345, 100, 68},
  348. {330, 100, 68},
  349. {315, 100, 68},
  350. {300, 100, 68},
  351. {31, 100, 50},
  352. {15, 100, 68},
  353. {0, 100, 76},
  354. {340, 100, 76},
  355. {320, 100, 76},
  356. {300, 100, 76},
  357. {41, 100, 50},
  358. {30, 100, 68},
  359. {20, 100, 76},
  360. {0, 100, 84},
  361. {330, 100, 84},
  362. {300, 100, 84},
  363. {50, 100, 50},
  364. {45, 100, 68},
  365. {40, 100, 76},
  366. {30, 100, 84},
  367. {0, 100, 92},
  368. {300, 100, 92},
  369. {60, 100, 50},
  370. {60, 100, 68},
  371. {60, 100, 76},
  372. {60, 100, 84},
  373. {60, 100, 92},
  374. {0, 0, 100},
  375. {0, 0, 3},
  376. {0, 0, 7},
  377. {0, 0, 10},
  378. {0, 0, 14},
  379. {0, 0, 18},
  380. {0, 0, 22},
  381. {0, 0, 26},
  382. {0, 0, 30},
  383. {0, 0, 34},
  384. {0, 0, 38},
  385. {0, 0, 42},
  386. {0, 0, 46},
  387. {0, 0, 50},
  388. {0, 0, 54},
  389. {0, 0, 58},
  390. {0, 0, 61},
  391. {0, 0, 65},
  392. {0, 0, 69},
  393. {0, 0, 73},
  394. {0, 0, 77},
  395. {0, 0, 81},
  396. {0, 0, 85},
  397. {0, 0, 89},
  398. {0, 0, 93},
  399. };
  400. int distance_hsl256(int i, int h, int s, int l)
  401. {
  402. int dh = h - hsl256[i][0];
  403. int ds = s - hsl256[i][1];
  404. int dl = l - hsl256[i][2];
  405. return dh*dh + ds*ds + dl*dl;
  406. }
  407. // TODO: bring find_ansi_index_by_rgb from image2term
  408. int find_ansi_index_by_hsl(int h, int s, int l)
  409. {
  410. int index = 0;
  411. for (int i = 0; i < 256; ++i) {
  412. if (distance_hsl256(i, h, s, l) < distance_hsl256(index, h, s, l)) {
  413. index = i;
  414. }
  415. }
  416. return index;
  417. }
  418. static uint32_t vc_term_compress_pixels_chunk(Olivec_Canvas oc)
  419. {
  420. size_t r = 0;
  421. size_t g = 0;
  422. size_t b = 0;
  423. size_t a = 0;
  424. for (size_t y = 0; y < oc.height; ++y) {
  425. for (size_t x = 0; x < oc.width; ++x) {
  426. r += OLIVEC_RED(OLIVEC_PIXEL(oc, x, y));
  427. g += OLIVEC_GREEN(OLIVEC_PIXEL(oc, x, y));
  428. b += OLIVEC_BLUE(OLIVEC_PIXEL(oc, x, y));
  429. a += OLIVEC_ALPHA(OLIVEC_PIXEL(oc, x, y));
  430. }
  431. }
  432. r /= oc.width*oc.height;
  433. g /= oc.width*oc.height;
  434. b /= oc.width*oc.height;
  435. a /= oc.width*oc.height;
  436. return OLIVEC_RGBA(r, g, b, a);
  437. }
  438. #ifndef VC_TERM_SCALE_DOWN_FACTOR
  439. #define VC_TERM_SCALE_DOWN_FACTOR 20
  440. #endif // VC_TERM_SCALE_DOWN_FACTOR
  441. static void vc_term_resize_char_canvas(size_t new_width, size_t new_height)
  442. {
  443. // TODO: warn the user if vc_term_actual_width does not fit into the screen
  444. // TODO: can we just do something so the divisibility is not important?
  445. // Like round the stuff or something?
  446. // Or we can resize the frame on the fly similarly to how we resize sprites in olivec_sprite_*() functions.
  447. assert(new_width%VC_TERM_SCALE_DOWN_FACTOR == 0 && "Width must be divisible by VC_TERM_SCALE_DOWN_FACTOR");
  448. assert(new_height%VC_TERM_SCALE_DOWN_FACTOR == 0 && "Height must be divisible by VC_TERM_SCALE_DOWN_FACTOR");
  449. vc_term_actual_width = new_width;
  450. vc_term_actual_height = new_height;
  451. vc_term_scaled_down_width = vc_term_actual_width/VC_TERM_SCALE_DOWN_FACTOR;
  452. vc_term_scaled_down_height = vc_term_actual_height/VC_TERM_SCALE_DOWN_FACTOR;
  453. free(vc_term_char_canvas);
  454. vc_term_char_canvas = malloc(sizeof(*vc_term_char_canvas)*vc_term_scaled_down_width*vc_term_scaled_down_height);
  455. assert(vc_term_char_canvas != NULL && "Just buy more RAM");
  456. }
  457. void rgb_to_hsl(int r, int g, int b, int *h, int *s, int *l)
  458. {
  459. float r01 = r/255.0f;
  460. float g01 = g/255.0f;
  461. float b01 = b/255.0f;
  462. float cmax = r01;
  463. if (g01 > cmax) cmax = g01;
  464. if (b01 > cmax) cmax = b01;
  465. float cmin = r01;
  466. if (g01 < cmin) cmin = g01;
  467. if (b01 < cmin) cmin = b01;
  468. float delta = cmax - cmin;
  469. float epsilon = 1e-6;
  470. float hf = 0;
  471. if (delta < epsilon) hf = 0;
  472. else if (cmax == r01) hf = 60.0f*fmod((g01 - b01)/delta, 6.0f);
  473. else if (cmax == g01) hf = 60.0f*((b01 - r01)/delta + 2);
  474. else if (cmax == b01) hf = 60.0f*((r01 - g01)/delta + 4);
  475. else assert(0 && "unreachable");
  476. float lf = (cmax + cmin)/2;
  477. float sf = 0;
  478. if (delta < epsilon) sf = 0;
  479. else sf = delta/(1 - fabsf(2*lf - 1));
  480. *h = fmodf(fmodf(hf, 360.0f) + 360.0f, 360.0f);
  481. *s = sf*100.0f;
  482. *l = lf*100.0f;
  483. }
  484. static void vc_term_compress_pixels(Olivec_Canvas oc)
  485. {
  486. if (vc_term_actual_width != oc.width || vc_term_actual_height != oc.height) {
  487. vc_term_resize_char_canvas(oc.width, oc.height);
  488. }
  489. for (size_t y = 0; y < vc_term_scaled_down_height; ++y) {
  490. for (size_t x = 0; x < vc_term_scaled_down_width; ++x) {
  491. Olivec_Canvas soc = olivec_subcanvas(oc, x*VC_TERM_SCALE_DOWN_FACTOR, y*VC_TERM_SCALE_DOWN_FACTOR, VC_TERM_SCALE_DOWN_FACTOR, VC_TERM_SCALE_DOWN_FACTOR);
  492. uint32_t cp = vc_term_compress_pixels_chunk(soc);
  493. int r = OLIVEC_RED(cp);
  494. int g = OLIVEC_GREEN(cp);
  495. int b = OLIVEC_BLUE(cp);
  496. int a = OLIVEC_ALPHA(cp);
  497. r = a*r/255;
  498. g = a*g/255;
  499. b = a*b/255;
  500. int h, s, l;
  501. rgb_to_hsl(r, g, b, &h, &s, &l);
  502. vc_term_char_canvas[y*vc_term_scaled_down_width + x] = find_ansi_index_by_hsl(h, s, l);
  503. }
  504. }
  505. }
  506. int main(void)
  507. {
  508. for (;;) {
  509. vc_term_compress_pixels(vc_render(1.f/60.f));
  510. for (size_t y = 0; y < vc_term_scaled_down_height; ++y) {
  511. for (size_t x = 0; x < vc_term_scaled_down_width; ++x) {
  512. // TODO: explore the idea of figuring out aspect ratio of the character using escape ANSI codes of the terminal and rendering the image accordingly
  513. printf("\033[48;5;%dm ", vc_term_char_canvas[y*vc_term_scaled_down_width + x]);
  514. }
  515. printf("\033[0m\n");
  516. }
  517. usleep(1000*1000/60);
  518. printf("\033[%zuA", vc_term_scaled_down_height);
  519. printf("\033[%zuD", vc_term_scaled_down_width);
  520. }
  521. return 0;
  522. }
  523. #elif VC_PLATFORM == VC_WASM_PLATFORM
  524. // Do nothing because all the work is done in ../js/vc.js
  525. #else
  526. #error "Unknown VC platform"
  527. #endif // VC_SDL_PLATFORM