vc.c 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  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. static size_t vc_term_actual_width = 0;
  135. static size_t vc_term_actual_height = 0;
  136. static size_t vc_term_scaled_down_width = 0;
  137. static size_t vc_term_scaled_down_height = 0;
  138. static char *vc_term_char_canvas = 0;
  139. // TODO: use ANSI terminal colors for color_to_char
  140. static char vc_term_color_to_char(uint32_t pixel)
  141. {
  142. size_t r = OLIVEC_RED(pixel);
  143. size_t g = OLIVEC_GREEN(pixel);
  144. size_t b = OLIVEC_BLUE(pixel);
  145. size_t a = OLIVEC_ALPHA(pixel);
  146. size_t bright = r;
  147. if (bright < g) bright = g;
  148. if (bright < b) bright = b;
  149. bright = bright*a/255;
  150. char table[] = " .:a@#";
  151. size_t n = sizeof(table) - 1;
  152. return table[bright*n/256];
  153. }
  154. static uint32_t vc_term_compress_pixels_chunk(Olivec_Canvas oc)
  155. {
  156. size_t r = 0;
  157. size_t g = 0;
  158. size_t b = 0;
  159. size_t a = 0;
  160. for (size_t y = 0; y < oc.height; ++y) {
  161. for (size_t x = 0; x < oc.width; ++x) {
  162. r += OLIVEC_RED(OLIVEC_PIXEL(oc, x, y));
  163. g += OLIVEC_GREEN(OLIVEC_PIXEL(oc, x, y));
  164. b += OLIVEC_BLUE(OLIVEC_PIXEL(oc, x, y));
  165. a += OLIVEC_ALPHA(OLIVEC_PIXEL(oc, x, y));
  166. }
  167. }
  168. r /= oc.width*oc.height;
  169. g /= oc.width*oc.height;
  170. b /= oc.width*oc.height;
  171. a /= oc.width*oc.height;
  172. return OLIVEC_RGBA(r, g, b, a);
  173. }
  174. #ifndef VC_TERM_SCALE_DOWN_FACTOR
  175. #define VC_TERM_SCALE_DOWN_FACTOR 20
  176. #endif // VC_TERM_SCALE_DOWN_FACTOR
  177. static void vc_term_resize_char_canvas(size_t new_width, size_t new_height)
  178. {
  179. // TODO: can we just do something so the divisibility is not important?
  180. // Like round the stuff or something?
  181. assert(new_width%VC_TERM_SCALE_DOWN_FACTOR == 0 && "Width must be divisible by VC_TERM_SCALE_DOWN_FACTOR");
  182. assert(new_height%VC_TERM_SCALE_DOWN_FACTOR == 0 && "Height must be divisible by VC_TERM_SCALE_DOWN_FACTOR");
  183. vc_term_actual_width = new_width;
  184. vc_term_actual_height = new_height;
  185. vc_term_scaled_down_width = vc_term_actual_width/VC_TERM_SCALE_DOWN_FACTOR;
  186. vc_term_scaled_down_height = vc_term_actual_height/VC_TERM_SCALE_DOWN_FACTOR;
  187. free(vc_term_char_canvas);
  188. vc_term_char_canvas = malloc(sizeof(*vc_term_char_canvas)*vc_term_scaled_down_width*vc_term_scaled_down_height);
  189. assert(vc_term_char_canvas != NULL && "Just buy more RAM");
  190. }
  191. static void vc_term_compress_pixels(Olivec_Canvas oc)
  192. {
  193. if (vc_term_actual_width != oc.width || vc_term_actual_height != oc.height) {
  194. vc_term_resize_char_canvas(oc.width, oc.height);
  195. }
  196. for (size_t y = 0; y < vc_term_scaled_down_height; ++y) {
  197. for (size_t x = 0; x < vc_term_scaled_down_width; ++x) {
  198. Olivec_Canvas soc = olivec_subcanvas(oc, x*VC_TERM_SCALE_DOWN_FACTOR, y*VC_TERM_SCALE_DOWN_FACTOR, VC_TERM_SCALE_DOWN_FACTOR,
  199. VC_TERM_SCALE_DOWN_FACTOR);
  200. vc_term_char_canvas[y*vc_term_scaled_down_width + x] = vc_term_color_to_char(vc_term_compress_pixels_chunk(soc));
  201. }
  202. }
  203. }
  204. int main(void)
  205. {
  206. for (;;) {
  207. vc_term_compress_pixels(vc_render(1.f/60.f));
  208. for (size_t y = 0; y < vc_term_scaled_down_height; ++y) {
  209. for (size_t x = 0; x < vc_term_scaled_down_width; ++x) {
  210. // TODO: different halfs of the double pixels
  211. // We can do stuff like putc('<', stdout); putc('>', stdout);
  212. putc(vc_term_char_canvas[y*vc_term_scaled_down_width + x], stdout);
  213. putc(vc_term_char_canvas[y*vc_term_scaled_down_width + x], stdout);
  214. // TODO: explore the idea of figuring out aspect ratio of the character using escape ANSI codes of the terminal and rendering the image accordingly
  215. }
  216. putc('\n', stdout);
  217. }
  218. usleep(1000*1000/60);
  219. printf("\033[%zuA", vc_term_scaled_down_height);
  220. printf("\033[%zuD", vc_term_scaled_down_width);
  221. }
  222. return 0;
  223. }
  224. #elif VC_PLATFORM == VC_WASM_PLATFORM
  225. // Do nothing because all the work is done in ../js/vc.js
  226. #else
  227. #error "Unknown VC platform"
  228. #endif // VC_SDL_PLATFORM