// ----------------------------------------------------------------------------- // https://dataswamp.org/~incal/bad-el/src/bad-sdl2/bad-sdl2.c // ----------------------------------------------------------------------------- int plugin_is_GPL_compatible; #include "bad-sdl2.h" #include #include #include #include #include #include #include #include // ----------------------------------------------------------------------------- // it's all an orchestra of strings // doing unbelievable things // -- D. E. Max // ----------------------------------------------------------------------------- static SDL_Window* win = NULL; static SDL_Renderer* rnd = NULL; static SDL_Texture* tex = NULL; #define leet 1337 #define nas __attribute__((unused)) #define rgba32 SDL_PIXELFORMAT_RGBA32 #define center SDL_WINDOWPOS_CENTERED // ----------------------------------------------------------------------------- // font // ----------------------------------------------------------------------------- static TTF_Font* fnt = NULL; static SDL_Color fg = { 40, 245, 250, 255 }; static int fnt_sze = 16; static int fnt_h = 0; void font_init () { TTF_Init(); fnt = TTF_OpenFont("/usr/share/fonts/truetype/ocr-a/OCRABold.ttf", fnt_sze); assert(fnt); fnt_h = TTF_FontHeight(fnt); assert(fnt_h > 0); } void font_quit () { if (fnt) { TTF_CloseFont (fnt); fnt = NULL; } TTF_Quit(); } // ----------------------------------------------------------------------------- // cube // ----------------------------------------------------------------------------- #define cube_side 256 #define cube_size 16777216 static int cube_call = 0; static int cube_insert = 0; static SDL_Texture* cube[cube_size]; void cube_init () { if (cube_call > 0) { printf("%d calls, %d inserts, %.1f%% computation.\n", cube_call, cube_insert, (100 * (cube_insert / (1.0 + cube_call)))); } cube_call = 0; cube_insert = 0; for (int i = 0; i < cube_size; i++) { if (cube[i]) { SDL_DestroyTexture(cube[i]); } cube[i] = NULL; } } void cube_quit () { cube_init(); } // OK // ----------------------------------------------------------------------------- // thread // ----------------------------------------------------------------------------- static bool gthr_run = false; static SDL_Thread* thr = NULL; int gthr_main (void* arg nas) { bool debug = false; static uint8_t x = unset; static uint8_t y = unset; static uint8_t z = unset; static uint8_t idx = unset; uint8_t i = 0; while (gthr_run) { i = (i + 1) % lines; if (!(SDL_TryLockMutex (lines_pipe[i].mtx))) { if (debug) { printf ("thread has lock\n"); } if (lines_pipe[i].str) { hash_coords(lines_pipe[i].str, &x, &y, &z); idx = (((size_t)x) + ((size_t)y * cube_side) + ((size_t)z * cube_side * cube_side)) % cube_size; lines_pipe[i].new = idx; if ((!cube[idx])) { lines_pipe[i].sur = TTF_RenderText_Blended(fnt, lines_pipe[i].str, fg); assert(lines_pipe[i].sur); } lines_pipe[i].str = NULL; } SDL_UnlockMutex (lines_pipe[i].mtx); if (debug) { printf ("thread has released lock\n"); } } SDL_Delay (1); } printf("[surface thread] goodbye\n"); return 0; } void gthr_init () { if (!thr) { gthr_run = true; thr = SDL_CreateThread(gthr_main, "gthr", NULL); assert(thr); } else { perror("[main thread] surface thread already running"); } } void gthr_quit () { if (gthr_run) { gthr_run = false; SDL_WaitThread(thr, NULL); thr = NULL; } else { perror("[main thread] surface thread already not running"); } } // ----------------------------------------------------------------------------- // string-to-texture hashing & caching // ----------------------------------------------------------------------------- void hash_coords (const char* str, uint8_t* x, uint8_t* y, uint8_t* z) { uint8_t h[SHA256_DIGEST_LENGTH]; SHA256((const uint8_t*)str, strlen(str), h); *x = h[0] % cube_side; *y = h[1] % cube_side; *z = h[2] % cube_side; } void clear_cache () { const int max_all = 999; static int max_nxt = max_all; if ((max_nxt < cube_insert)) { printf("deallocating %d textures at %d inserted.\n", max_all, max_nxt); cube_init(); max_nxt += max_all; } } void main_thread (const char* str) { assert(str); bool debug = false; int enqd = 0; SDL_Rect rect; int w = unset; int h = unset; int lne_h = unset; // fill with draw data for thread char* buff = my_strdup(str); char* save = NULL; strtok_r(buff, "\n", &save); char* lne = NULL; for (int i = 0; (i < lines) && lne; i++) { SDL_LockMutex (lines_pipe[i].mtx); lines_pipe[i].str = lne; enqd++; SDL_UnlockMutex (lines_pipe[i].mtx); lne = strtok(NULL, "\n"); } // wait for surfaces or refs to cube textures if (debug) { printf ("strings done; wait for surfaces\n"); } for (int i = 0; 0 < enqd; i = (i + 1) % lines) { if (!(SDL_TryLockMutex (lines_pipe[i].mtx))) { // got a surface if (lines_pipe[i].sur) { if (debug) { printf ("surface found; draw to texture\n"); } cube[lines_pipe[i].new] = SDL_CreateTextureFromSurface(rnd, lines_pipe[i].sur); assert(cube[lines_pipe[i].new]); cube_insert++; SDL_SetTextureBlendMode(cube[lines_pipe[i].new], SDL_BLENDMODE_BLEND); } // rect if (lines_pipe[i].new) { SDL_QueryTexture (cube[lines_pipe[i].new], NULL, NULL, &w, &h); assert (0 < w); assert (0 < h); lne_h = h/lines; // todo: softcode rect.x = 0; rect.y = lne_h * i; rect.h = h; rect.w = w; // draw SDL_SetRenderTarget (rnd, tex); SDL_RenderCopy (rnd, cube[lines_pipe[i].new], NULL, &rect); // done with line enqd--; lines_pipe[i].old = lines_pipe[i].new; lines_pipe[i].new = unset; if (lines_pipe[i].sur) { SDL_FreeSurface(lines_pipe[i].sur); lines_pipe[i].sur = NULL; } } SDL_UnlockMutex (lines_pipe[i].mtx); } SDL_Delay(1); } // tex done, draw it to GPU if (debug) { printf ("all surface found; draw to GPU\n"); } free (buff); SDL_SetRenderTarget (rnd, NULL); SDL_RenderCopy (rnd, tex, NULL, NULL); SDL_RenderPresent (rnd); } // ----------------------------------------------------------------------------- // Emacs dynamic module C functions, make interface to Emacs // ----------------------------------------------------------------------------- int emacs_module_init (struct emacs_runtime* rnt) { // check for incompatible Emacs binary if (rnt->size < (long int)sizeof(*rnt)) { return 1; } // check for incompatible module API emacs_env* env = rnt->get_environment(rnt); if (env->size < (long int)sizeof(*env)) { return 2; } // check for too old Emacs int nas emacs_ver; if ((long int)sizeof(struct emacs_env_31) <= env->size) { emacs_ver = 31; } else if ((long int)sizeof(struct emacs_env_30) <= env->size) { emacs_ver = 30; } else if ((long int)sizeof(struct emacs_env_29) <= env->size) { emacs_ver = 29; } else { return 3; } // draw_init emacs_value draw_init_func = env->make_function(env, 0, 0, draw_init, "Init draw.", NULL); emacs_value draw_init_symb = env->intern(env, "draw_init"); emacs_value draw_init_args[] = {draw_init_symb, draw_init_func}; env->funcall(env, env->intern(env, "defalias"), 2, draw_init_args); // draw_quit emacs_value draw_quit_func = env->make_function(env, 0, 0, draw_quit, "Quit draw.", NULL); emacs_value draw_quit_symb = env->intern(env, "draw_quit"); emacs_value draw_quit_args[] = {draw_quit_symb, draw_quit_func}; env->funcall(env, env->intern(env, "defalias"), 2, draw_quit_args); /* draw_frame */ emacs_value draw_frame_func = env->make_function(env, 0, 0, draw_frame, "Draw frame.", NULL); emacs_value draw_frame_symb = env->intern(env, "draw_frame"); emacs_value draw_frame_args[] = {draw_frame_symb, draw_frame_func}; env->funcall(env, env->intern(env, "defalias"), 2, draw_frame_args); // done return 0; } // ----------------------------------------------------------------------------- // video init / quit // ----------------------------------------------------------------------------- void video_init () { // SDL SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); SDL_Init(SDL_INIT_VIDEO); // window win = SDL_CreateWindow("SDL2 draw", center, center, 1280, 720, SDL_WINDOW_SHOWN); assert(win); // renderer rnd = SDL_CreateRenderer (win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); assert(rnd); // clear screen SDL_SetRenderTarget (rnd, NULL); SDL_SetRenderDrawColor (rnd, 0, 0, 0, 255); SDL_RenderClear (rnd); // get texture int w, h; SDL_GetRendererOutputSize (rnd, &w, &h); SDL_RenderSetLogicalSize (rnd, w, h); tex = SDL_CreateTexture (rnd, rgba32, SDL_TEXTUREACCESS_TARGET, w, h); assert(tex); // clear SDL_SetRenderTarget (rnd, NULL); SDL_SetRenderDrawColor (rnd, 0, 0, 0, 255); SDL_RenderClear(rnd); } void video_quit () { if (rnd) { SDL_DestroyRenderer (rnd); rnd = NULL; } } // ----------------------------------------------------------------------------- // dynamic module draw init / quit // ----------------------------------------------------------------------------- emacs_value draw_init (emacs_env* env, ptrdiff_t nargs nas, emacs_value* args nas, void* data nas) { bool verbose = false; video_init (); if (verbose) { printf ("draw loaded\n"); } font_init (); if (verbose) { printf ("font loaded\n"); } cube_init (); if (verbose) { printf ("cube loaded\n"); } gthr_init (); if (verbose) { printf ("gthr loaded\n"); } for (int i = 0; i < lines; i++) { lines_pipe[i].mtx = SDL_CreateMutex(); } return env->intern(env, "nil"); } emacs_value draw_quit (emacs_env* env, ptrdiff_t nargs nas, emacs_value* args nas, void* data nas) { gthr_quit (); cube_quit (); font_quit (); video_quit (); for (int i = 0; i < lines; i++) { SDL_DestroyMutex(lines_pipe[i].mtx); } SDL_Quit (); return env->intern(env, "nil"); } // ----------------------------------------------------------------------------- // DRAW // ----------------------------------------------------------------------------- void clear_screen () { SDL_SetRenderTarget (rnd, tex); SDL_SetRenderDrawColor (rnd, 44, 96, 255, 180); // bg color SDL_RenderClear (rnd); } emacs_value draw_frame (emacs_env* env, ptrdiff_t nargs nas, emacs_value* args nas, void* data nas) { assert (env); // get data emacs_value ef = env->intern(env, "bad-grid-string"); // Elisp emacs_value es = env->funcall(env, ef, 0, NULL); ptrdiff_t l = 0; env->copy_string_contents (env, es, NULL, &l); char* txt_str = malloc (l); env->copy_string_contents (env, es, txt_str, &l); assert (txt_str); // draw clear_screen (); main_thread (txt_str); // done free (txt_str); txt_str = NULL; return env->intern (env, "nil"); } // ----------------------------------------------------------------------------- // helpers // ----------------------------------------------------------------------------- char* my_strdup (const char* s) { if (s) { size_t len = strlen(s) + 1; char* cpy = malloc (len); assert(cpy); memcpy(cpy, s, len); return cpy; } return NULL; } // ----------------------------------------------------------------------------- // draw rect // ----------------------------------------------------------------------------- void draw_rect (int x, int y, int w, int h) { SDL_SetRenderDrawBlendMode (rnd, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor (rnd, 205, 200, 210, 240); SDL_Rect rect = { .x = x, .y = y, .w = w, .h = h }; SDL_SetRenderTarget(rnd, tex); SDL_RenderFillRect(rnd, &rect); } void draw_rect_test (SDL_Renderer* rnd) { // validate assert (rnd); // max size static int max_w = 0; static int max_h = 0; if ((max_w == 0) || (max_h == 0)) { SDL_GetRendererOutputSize (rnd, &max_w, &max_h); } // margin const float part = 1.0; const float full = 16.0; int mar_x = (round (max_w * (part / full))); int mar_y = (round (max_h * (part / full))); // rect int x = mar_x; int y = mar_y; int w = max_w - 2 * mar_x; int h = max_h - 2 * mar_y; // done draw_rect(x, y, w, h); } void draw_planet (int cx nas, int cy nas, int rad) { int diam = 2 * rad; int w = diam; int h = diam; SDL_Texture* ptex = SDL_CreateTexture(rnd, rgba32, SDL_TEXTUREACCESS_STREAMING, w, h); int pitch = 0; void* pixels = NULL; SDL_LockTexture(ptex, NULL, &pixels, &pitch); int stride = pitch / sizeof(uint32_t); uint32_t* pixel_array = (uint32_t*)pixels; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { float dx = x - rad; float dy = y - rad; float dist = sqrt(dx*dx + dy*dy); float bright = fmax(0.0f, 1.0f - dist / rad); float curve = powf(bright, 1.5f); uint8_t r = (uint8_t)( 16 * curve); uint8_t g = (uint8_t)( 32 * curve); uint8_t b = (uint8_t)(156 * curve); uint8_t a = (uint8_t)( 96 * curve); pixel_array[y * stride + x] = (a << 24) | (b << 16) | (g << 8) | r; } } SDL_UnlockTexture (ptex); SDL_SetRenderTarget (rnd, tex); SDL_RenderCopy (rnd, ptex, NULL, NULL); SDL_DestroyTexture (ptex); }