// ----------------------------------------------------------------------------- // 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 static SDL_mutex* emacs_data_mtx = NULL; static char* emacs_data = NULL; static SDL_cond* emacs_data_cnd = NULL; static bool emacs_data_beg = false; // ----------------------------------------------------------------------------- // font // ----------------------------------------------------------------------------- static TTF_Font* fnt = NULL; static SDL_Color fg = { 135, 175, 255, 255 }; static int fnt_sze = 16; static int fnt_w = unset; static int fnt_h = unset; void font_init () { TTF_Init(); fnt = TTF_OpenFont("/usr/share/fonts/truetype/ocr-a/OCRABold.ttf", fnt_sze); assert(fnt); TTF_SizeText(fnt, "M", &fnt_w, &fnt_h); assert(0 < fnt_h); assert(0 < fnt_w); } void font_quit () { if (fnt) { TTF_CloseFont (fnt); fnt = NULL; } TTF_Quit(); } // ----------------------------------------------------------------------------- // cube // ----------------------------------------------------------------------------- #define cube_side 256 #define cube_size 16777216 static int drawn = 0; static int not_drawn = 0; static int cube_call = 0; static int cube_insert = 0; static SDL_mutex* cube_mtx = NULL; static SDL_Texture* cube[cube_size]; void cube_init (bool quit) { if (!quit) { cube_call = 0; cube_insert = 0; cube_mtx = SDL_CreateMutex (); } SDL_LockMutex (cube_mtx); for (int i = 0; i < cube_size; i++) { if (cube[i]) { SDL_DestroyTexture(cube[i]); } cube[i] = NULL; } SDL_UnlockMutex (cube_mtx); if ( quit && cube_mtx) { SDL_DestroyMutex (cube_mtx); } } void cube_quit () { cube_init(true); int total = drawn + not_drawn; printf("%d created surfaces / %d provided: %.1f%%\n", cube_insert, cube_call, (100 * (cube_insert / (1.0 + cube_call)))); printf("%d frames not drawn / %d provided: %.1f%%\n", not_drawn, total, (100 * (not_drawn / (1.0 + drawn)))); } // ----------------------------------------------------------------------------- // thread // ----------------------------------------------------------------------------- static LineMeta lines_pipe[lines]; static bool thr_run = false; static SDL_Thread* thr0 = NULL; static SDL_Thread* thr1 = NULL; static SDL_Thread* thr2 = NULL; static SDL_Thread* thr3 = NULL; static SDL_Thread* thr4 = NULL; int thr_main (void* arg nas) { int id = SDL_GetThreadID(NULL); static __thread int drawn = 0; uint8_t x = unset; uint8_t y = unset; uint8_t z = unset; uint32_t idx = unset; uint8_t i = 0; while (thr_run) { if (lines <= i) { i = 0; SDL_Delay(1); } if (!(SDL_TryLockMutex (lines_pipe[i].mtx))) { if (lines_pipe[i].str) { hash_coords(lines_pipe[i].str, &x, &y, &z); idx = ((((uint32_t)x) << 16) | (((uint32_t)y) << 8) | ((uint32_t)z)) % cube_size; lines_pipe[i].new = idx; SDL_LockMutex (cube_mtx); // BEG cube lock if ((!cube[idx])) { lines_pipe[i].sur = TTF_RenderText_Blended (fnt, lines_pipe[i].str, fg); drawn++; } SDL_UnlockMutex (cube_mtx); //END cube lock lines_pipe[i].str = NULL; } SDL_UnlockMutex (lines_pipe[i].mtx); } i++; } printf("[%d] goodbye I draw %d surfaces\n", id, drawn); return 0; } void thr_init () { if ((!thr0)) { thr_run = true; thr0 = SDL_CreateThread (get_data_draw, "sdl2-texture", NULL); assert(thr0); thr1 = SDL_CreateThread (thr_main, "sdl2-surface-s", NULL); assert(thr1); thr2 = SDL_CreateThread (thr_main, "sdl2-surface-u", NULL); assert(thr2); thr3 = SDL_CreateThread (thr_main, "sdl2-surface-r", NULL); assert(thr3); thr4 = SDL_CreateThread (clear_cache, "sdl2-cache", NULL); assert(thr4); } else { perror("[main thread] surface threads already running"); } } void thr_quit () { thr_run = false; SDL_WaitThread(thr0, NULL); thr0 = NULL; SDL_WaitThread(thr1, NULL); thr1 = NULL; SDL_WaitThread(thr2, NULL); thr2 = NULL; SDL_WaitThread(thr3, NULL); thr3 = NULL; SDL_WaitThread(thr4, NULL); thr4 = NULL; } // ----------------------------------------------------------------------------- // string to texture // ----------------------------------------------------------------------------- void hash_coords (const char* str, uint8_t* x, uint8_t* y, uint8_t* z) { assert(str); size_t len = strlen(str); uint8_t h[SHA256_DIGEST_LENGTH]; SHA256((const uint8_t*)str, len, h); *x = h[0] % cube_side; *y = h[1] % cube_side; *z = h[2] % cube_side; } int clear_cache (void* arg nas) { int id = SDL_GetThreadID(NULL); int calls = 0; const int max_all = 2048; static int max_nxt = max_all; while (thr_run) { if ((max_nxt < cube_insert)) { SDL_LockMutex(cube_mtx); cube_init (false); SDL_UnlockMutex(cube_mtx); calls++; max_nxt += max_all; } SDL_Delay(2000); } printf("[%d] goodbye I cleared the cache %d times\n", id, calls); return 0; } void make_surface (int i) { assert ((0 <= i) && (i < lines)); SDL_LockMutex(cube_mtx); if ((lines_pipe[i].new != unset) && (lines_pipe[i].sur)) { 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); SDL_FreeSurface(lines_pipe[i].sur); lines_pipe[i].sur = NULL; SDL_UnlockMutex(cube_mtx); } } void make_from_index (int i) { SDL_Rect rect; int lne_h = unset; int w = unset; int h = unset; SDL_LockMutex(cube_mtx); SDL_QueryTexture (cube[lines_pipe[i].new], NULL, NULL, &w, &h); SDL_UnlockMutex(cube_mtx); assert (0 < w); assert (0 < h); int log_w; int nas log_h; SDL_GetRendererOutputSize (rnd, &log_w, &log_h); lne_h = fnt_h; rect.x = 0; rect.y = lne_h * i; rect.w = log_w; rect.h = lne_h; // draw assert(0 <= rect.x); assert((rect.x + rect.w) <= 1280); assert(0 <= rect.y); assert((rect.y + rect.h) <= 720); SDL_SetRenderTarget (rnd, tex); SDL_RenderFillRect (rnd, &rect); rect.w = w; rect.h = h; SDL_LockMutex(cube_mtx); SDL_RenderCopy (rnd, cube[lines_pipe[i].new], NULL, &rect); SDL_UnlockMutex(cube_mtx); } void main_thread (const char* str) { assert(str); bool debug = false; int enqd = 0; bool draw = false; // fill with draw data for thread char* buff = my_strdup (str); char* save = NULL; char* lne = strtok_r(buff, "\n", &save); for (int i = 0; (i < lines) && lne; i++) { SDL_LockMutex (lines_pipe[i].mtx); lines_pipe[i].str = lne; SDL_UnlockMutex (lines_pipe[i].mtx); enqd++; cube_call++; lne = strtok_r(NULL, "\n", &save); } // wait for surfaces or refs to cube textures if (debug) { printf ("strings done; wait for surfaces\n"); } for (int i = 0; 0 < enqd; i++) { if (i == lines) { i = 0; SDL_Delay(1); } if (!(SDL_TryLockMutex (lines_pipe[i].mtx))) { if ((lines_pipe[i].new != unset) && lines_pipe[i].sur) { draw = true; make_surface(i); } if ((lines_pipe[i].new != unset) && (lines_pipe[i].old == lines_pipe[i].new)) { lines_pipe[i].new = unset; enqd--; } if ((lines_pipe[i].new != unset) && (lines_pipe[i].old != lines_pipe[i].new)) { draw = true; make_from_index (i); lines_pipe[i].old = lines_pipe[i].new; lines_pipe[i].new = unset; enqd--; } SDL_UnlockMutex (lines_pipe[i].mtx); } } // housekeeping free (buff); if (draw) { drawn++; 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); // get tex 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_screen(); } 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 (false); if (verbose) { printf ("cube loaded\n"); } thr_init (); if (verbose) { printf ("thr loaded\n"); } emacs_data_mtx = SDL_CreateMutex (); for (int i = 0; i < lines; i++) { lines_pipe[i].mtx = SDL_CreateMutex (); lines_pipe[i].str = NULL; lines_pipe[i].sur = NULL; lines_pipe[i].new = unset; lines_pipe[i].old = unset; } return env->intern(env, "nil"); } emacs_value draw_quit (emacs_env* env, ptrdiff_t nargs nas, emacs_value* args nas, void* data nas) { SDL_LockMutex (emacs_data_mtx); thr_run = false; free (emacs_data); emacs_data = NULL; emacs_data_beg = true; SDL_CondSignal (emacs_data_cnd); SDL_UnlockMutex (emacs_data_mtx); thr_quit (); if (emacs_data_mtx) { SDL_DestroyMutex (emacs_data_mtx); emacs_data_mtx = NULL; } if (emacs_data_cnd) { SDL_DestroyCond (emacs_data_cnd); emacs_data_cnd = NULL; } for (int i = 0; i < lines; i++) { if (lines_pipe[i].mtx) { SDL_DestroyMutex (lines_pipe[i].mtx); } if (lines_pipe[i].sur) { SDL_FreeSurface (lines_pipe[i].sur); } } cube_quit (); font_quit (); video_quit (); SDL_Quit (); return env->intern(env, "nil"); } // ----------------------------------------------------------------------------- // DRAW // ----------------------------------------------------------------------------- void clear_screen () { SDL_SetRenderTarget (rnd, tex); SDL_SetRenderDrawColor (rnd, 0, 0, 0, 255); // 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); char* txt_str = NULL; 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); txt_str = malloc (l); env->copy_string_contents (env, es, txt_str, &l); SDL_LockMutex (emacs_data_mtx); emacs_data = my_strdup (txt_str); emacs_data_beg = true; SDL_CondSignal (emacs_data_cnd); SDL_UnlockMutex (emacs_data_mtx); free (txt_str); txt_str = NULL; return env->intern (env, "nil"); } int get_data_draw (void* arg nas) { int id = SDL_GetThreadID(NULL); int calls = 0; char* data = NULL; while (thr_run) { SDL_LockMutex (emacs_data_mtx); while (!emacs_data_beg) { SDL_CondWait (emacs_data_cnd, emacs_data_mtx); } if (emacs_data) { data = my_strdup(emacs_data); } free (emacs_data); emacs_data = NULL; SDL_UnlockMutex (emacs_data_mtx); if (data) { main_thread (data); calls++; } free (data); data = NULL; } printf("[%d] goodbye I draw %d times\n", id, calls); int log_w, log_h; SDL_GetRendererOutputSize (rnd, &log_w, &log_h); printf("[ char (w/h = a) ] %d / %d = %.2f\n", fnt_w, fnt_h, ((float)fnt_w / (float)fnt_h)); printf("[ pane (w/h = a) ] %d / %d = %.2f\n", log_w, log_h, ((float)log_w / (float)log_h)); return 0; } // ----------------------------------------------------------------------------- // 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, 96, 96, 96, 96); SDL_Rect rect = { .x = x, .y = y, .w = w, .h = h }; SDL_SetRenderTarget(rnd, tex); SDL_RenderFillRect(rnd, &rect); } void draw_rect_test () { // 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 = 32.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); }