// ================================================================ // // runtime.c // // This is a reduced system layer. // It allows you to create a window, draw graphics in it, handle // input events, write samples to audio output, send and receive // UDP packets, etc. All code is single-threaded. // // Primary target platforms: Linux (X11), Windows, Web. // // ---------------------------------------------------------------- // // DESIGN PRINCIPLES // // - Minimalistic feature set. For graphics, you have access to the // pixel buffer, and that's it. // // - No implicit control flow. No callbacks. You write your own // main and call everything explicitly. But the number of things // you have to call to do something is as little as possible. // // - Optimized to use in a single source file. // Installation process? Ctrl+C, Ctrl+V, done. // // STYLE CONVENTIONS // // - Pascal_Snake_Case - Type name. // - snake_case - Non-type name. // - UPPER_SNAKE_CASE - Macro or constant. // - UPPER_Pascal_Snake_Case - Constant. // // - g_ prefix - Global variable name. // - _ prefix - Name of a global variable that is not part of the user API. // - _ suffix - Name of a procedure that is not part of the user API. // // Most procedures have long and descriptive names. // Some procedures have prefixes according to their domain. // // There may be exceptions if convenient. // // ---------------------------------------------------------------- // // To-Do list // // - Work in progress // - Graphics // - Stencil buffer // - Custom fonts - integrate stb_truetype.h // - UI // - Icons // - Folder widget // - Clipboard // - Images - BMP, PPM // - Sound - WAV // - Dynamic libraries - load dependencies conditionally // - X11 // - Wayland // - ALSA // - Sockets // - Windows // - System // - Win32 window // - Cross-platform networking - UDP + TCP + WebSocket // Requires: // - [ ] Sockets - UDP, TCP // - [ ] HTTP client // - [ ] HTTP server // - [ ] Web sockets // - [ ] Key exchange // - [ ] Cipher suite - TLS_AES_128_GCM_SHA256 // - [ ] TLS // - [ ] Web sockets over TLS // - Examples // - Conway's Game of Life // - Julia Set // - Chat // - Long term // - Utility // - Improve microbenchmarks library // - Parsing // - Printing // - Logging // - Terminal colors // - Big integer // - Mersenne Twister 64 // - Arithmetic coding // - A* search // - Graphics // - Vector math // - Bezier curves // - CMYK color // - Textures // - Vulkan boilerplate // - System // - Window // - Windows graphics // - Wayland // - Sound // - Windows audio // - Recording // - Device selection // - Networking // - Windows sockets // - fetch - via libcurl on native platforms // - Lattice-based cipher suite // - Web // - Touch events // - Mobile input // - Switching canvas // - File system // - Secure random // - Process // - Shared memory // - Shared mutex // - Threads - https://nachtimwald.com/2019/04/05/cross-platform-thread-wrapper // - Cryptography - https://github.com/jedisct1/supercop // - macOS support // - Mobile devices support // // Done // // - Examples // - Echo // - UI // - Particles // - Graph // - Sine Wave // - Utility // - UTF-8, UTF-16 // - Testing // - Stackless coroutines // - Allocator // - Profiling // - Graphics // - Font // - Adaptive resolution // - Oklab color // - Relative coordinates // - Alpha blending // - Self-contained impl // - Anti-aliasing // - Gamma correction // - System // - Dynamic libraries // - Window - X11, Web // - Screenshot - X11, Wayland // - Clipboard // - Text - X11, Web // - Sound - ALSA, Web // - Networking // - Unix UDP sockets // - Drop files - X11, Web // // ---------------------------------------------------------------- // // (C) 2025 Mitya Selivanov // // Any use of this code is prohibited. // // ================================================================ // // Options // // ================================================================ #ifndef ENABLE_TESTING #define ENABLE_TESTING 0 #endif #ifndef ENABLE_WAYLAND #define ENABLE_WAYLAND 1 #endif #ifndef ENABLE_X11 #define ENABLE_X11 1 #endif #ifndef ENABLE_ALSA #define ENABLE_ALSA 1 #endif #ifndef STATIC_MEMORY_BUFFER_SIZE #define STATIC_MEMORY_BUFFER_SIZE (400 * 1024 * 1024) #endif #ifndef MEMORY_CHUNK_SIZE #define MEMORY_CHUNK_SIZE 1024 #endif #ifndef DEFAULT_ANTIALIASING_SCALE #define DEFAULT_ANTIALIASING_SCALE 2 #endif #ifndef MIN_PIXEL_SIZE #define MIN_PIXEL_SIZE (1.0) #endif #ifndef MAX_PIXEL_SIZE #define MAX_PIXEL_SIZE (40.0) #endif #ifndef DEFAULT_PIXEL_SIZE #define DEFAULT_PIXEL_SIZE (8.0) #endif #ifndef PIXEL_SIZE_DELTA #define PIXEL_SIZE_DELTA (0.05) #endif // The pixel size will decrease when the frame duration is lower. #ifndef MIN_FRAME_DURATION #define MIN_FRAME_DURATION 17 #endif // The pixel size will increase when the frame duration is higher. #ifndef MAX_FRAME_DURATION #if defined(__wasm__) #define MAX_FRAME_DURATION 33 #else #define MAX_FRAME_DURATION 83 #endif #endif // The pixel size value will reset if the frame duration is higher. #ifndef FRAME_DURATION_HARD_LIMIT #define FRAME_DURATION_HARD_LIMIT 200 #endif #ifndef NUM_FRAMES_AVERAGED #define NUM_FRAMES_AVERAGED 600 #endif #ifndef AVERAGE_FRAME_BIAS #define AVERAGE_FRAME_BIAS (0.03) #endif #ifndef MAX_NUM_KEYS #define MAX_NUM_KEYS 512 #endif #ifndef NUM_PRIMARY_SOUND_CHANNELS #define NUM_PRIMARY_SOUND_CHANNELS 2 #endif #ifndef PRIMARY_SOUND_SAMPLE_RATE #define PRIMARY_SOUND_SAMPLE_RATE 44100 #endif // TODO: Implement dynamic primary sound buffer. #ifndef MAX_NUM_PRIMARY_SOUND_FRAMES #define MAX_NUM_PRIMARY_SOUND_FRAMES (10 * PRIMARY_SOUND_SAMPLE_RATE * NUM_PRIMARY_SOUND_CHANNELS) #endif #ifndef PRIMARY_SOUND_AVAIL_MIN #define PRIMARY_SOUND_AVAIL_MIN 512 #endif #ifndef MOUSE_WHEEL_FACTOR #define MOUSE_WHEEL_FACTOR (1.0) #endif // ================================================================ // // Types // // ================================================================ #ifndef TYPES_HEADER_GUARD_ #define TYPES_HEADER_GUARD_ // NOTE: Simple types can be redefined. typedef signed char i8; typedef signed short i16; typedef signed i32; typedef signed long long i64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned u32; typedef unsigned long long u64; typedef char c8; typedef short c16; typedef int c32; typedef unsigned char b8; typedef float f32; typedef double f64; #if defined(__LP64__) || defined(_LP64) || defined(_WIN64) typedef long long iptr; typedef unsigned long long uptr; #else typedef signed iptr; typedef unsigned uptr; #endif typedef union { struct { f64 v[ 2]; }; struct { f64 x, y; }; } vec2; typedef union { struct { f64 v[ 3]; }; struct { f64 x, y, z; }; } vec3; typedef union { struct { f64 v[ 4]; }; struct { f64 x, y, z, w; }; } vec4; typedef union { struct { f32 v[ 2]; }; struct { f32 x, y; }; } vec2_f32; typedef union { struct { f32 v[ 3]; }; struct { f32 x, y, z; }; } vec3_f32; typedef union { struct { f32 v[ 4]; }; struct { f32 x, y, z, w; }; } vec4_f32; typedef union { struct { i64 v[ 2]; }; struct { i64 x, y; }; } vec2_i64; typedef union { struct { i64 v[ 3]; }; struct { i64 x, y, z; }; } vec3_i64; typedef union { struct { i64 v[ 4]; }; struct { i64 x, y, z, w; }; } vec4_i64; typedef union { struct { f64 v[ 4]; }; struct { f64 m[2][2]; }; } mat2; typedef union { struct { f64 v[ 9]; }; struct { f64 m[3][3]; }; } mat3; typedef union { struct { f64 v[16]; }; struct { f64 m[4][4]; }; } mat4; typedef union { struct { f32 v[ 4]; }; struct { f32 m[2][2]; }; } mat2_f32; typedef union { struct { f32 v[ 9]; }; struct { f32 m[3][3]; }; } mat3_f32; typedef union { struct { f32 v[16]; }; struct { f32 m[4][4]; }; } mat4_f32; #endif // TYPES_HEADER_GUARD_ // ================================================================ #ifndef RUNTIME_HEADER_GUARD_ #define RUNTIME_HEADER_GUARD_ #ifdef __cplusplus extern "C" { #endif // ================================================================ // // User program interface // // ================================================================ // NOTE: This procedure is required for the Web compatibility. void update_and_render_frame(void); #if defined(__wasm__) i32 main(i32 argc, c8 **argv); #endif // ================================================================ // // Basic declarations // // ================================================================ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifndef NULL #define NULL ((void *) 0) #endif // ================================================================ // // Math // // ================================================================ #if !defined(__wasm__) #include #else f64 floor(f64 x); f64 ceil(f64 x); f64 trunc(f64 x); f64 sqrt(f64 x); f64 cbrt(f64 x); f64 pow(f64 x, f64 y); f64 log(f64 x); f64 log2(f64 x); f64 log10(f64 x); f64 exp(f64 x); f64 sin(f64 x); f64 cos(f64 x); f64 tan(f64 x); f64 asin(f64 x); f64 acos(f64 x); f64 atan(f64 x); f64 atan2(f64 y, f64 x); #endif #ifndef M_PI #define M_PI (3.14159265358979323846) #endif #ifndef M_E #define M_E (2.71828182845904523536) #endif #ifndef M_LOG_2 #define M_LOG_2 (0.69314718055994530941) #endif #ifndef M_LOG_10 #define M_LOG_10 (2.30258509299404568402) #endif #ifndef M_LOG2_E #define M_LOG2_E (1.44269504088896340736) #endif #ifndef M_LOG10_E #define M_LOG10_E (0.43429448190325182765) #endif #ifndef M_LOG_PI #define M_LOG_PI (1.14472988584940017414) #endif #ifndef M_SQRT_2 #define M_SQRT_2 (1.41421356237309504880) #endif #ifndef M_SQRT_3 #define M_SQRT_3 (1.73205080756887729352) #endif #ifndef M_SQRT_PI #define M_SQRT_PI (1.77245385090551602730) #endif #ifndef M_SQRT_E #define M_SQRT_E (1.64872127070012814685) #endif #ifndef M_CBRT_2 #define M_CBRT_2 (1.25992104989487316476) #endif #ifndef M_CBRT_3 #define M_CBRT_3 (1.44224957030740838232) #endif #ifndef M_CBRT_PI #define M_CBRT_PI (1.46459188756152326302) #endif #ifndef M_CBRT_E #define M_CBRT_E (1.39561242508608952863) #endif // ================================================================ // // PLATFORM API // // ================================================================ enum { IPv4_UDP = 1, IPv6_UDP = 2, }; enum { MOD_CTRL = 1, MOD_SHIFT = 2, MOD_ALT = 3, MOD_CAPS = 4, MOD_NUM = 5, MOD_SCROLL = 6, MOD_META = 7, KEY_BACKSPACE = 8, KEY_TAB = 9, KEY_ENTER = 10, KEY_LCTRL = 11, KEY_RCTRL = 12, KEY_LSHIFT = 13, KEY_RSHIFT = 14, KEY_LALT = 15, KEY_RALT = 16, KEY_LEFT = 17, KEY_RIGHT = 18, KEY_UP = 19, KEY_DOWN = 20, KEY_PAUSE = 21, KEY_INSERT = 22, KEY_HOME = 23, KEY_END = 24, KEY_PAGEUP = 25, KEY_PAGEDOWN = 26, KEY_ESCAPE = 27, KEY_PRINTSCREEN = 28, BUTTON_LEFT = 29, BUTTON_MIDDLE = 30, BUTTON_RIGHT = 31, KEY_SPACE = 32, KEY_LMETA = 33, KEY_RMETA = 34, KEY_QUOTE = 39, KEY_COMMA = 44, KEY_MINUS = 45, KEY_PERIOD = 46, KEY_SLASH = 47, KEY_0 = 48, KEY_1 = 49, KEY_2 = 50, KEY_3 = 51, KEY_4 = 52, KEY_5 = 53, KEY_6 = 54, KEY_7 = 55, KEY_8 = 56, KEY_9 = 57, KEY_COLON = 59, KEY_EQUAL = 61, KEY_LBRACE = 91, KEY_BACKSLASH = 92, KEY_RBRACE = 93, KEY_TILDE = 96, KEY_A = 97, KEY_B = 98, KEY_C = 99, KEY_D = 100, KEY_E = 101, KEY_F = 102, KEY_G = 103, KEY_H = 104, KEY_I = 105, KEY_J = 106, KEY_K = 107, KEY_L = 108, KEY_M = 109, KEY_N = 110, KEY_O = 111, KEY_P = 112, KEY_Q = 113, KEY_R = 114, KEY_S = 115, KEY_T = 116, KEY_U = 117, KEY_V = 118, KEY_W = 119, KEY_X = 120, KEY_Y = 121, KEY_Z = 122, KEY_DELETE = 127, KEY_KP_0 = 128, KEY_KP_1 = 129, KEY_KP_2 = 130, KEY_KP_3 = 131, KEY_KP_4 = 132, KEY_KP_5 = 133, KEY_KP_6 = 134, KEY_KP_7 = 135, KEY_KP_8 = 136, KEY_KP_9 = 137, KEY_KP_ENTER = 138, KEY_KP_DIVIDE = 139, KEY_KP_MULTIPLY = 140, KEY_KP_ADD = 141, KEY_KP_SUBTRACT = 142, KEY_KP_DECIMAL = 143, KEY_KP_SEPARATOR = 144, KEY_F1 = 145, KEY_F2 = 146, KEY_F3 = 147, KEY_F4 = 148, KEY_F5 = 149, KEY_F6 = 150, KEY_F7 = 151, KEY_F8 = 152, KEY_F9 = 153, KEY_F10 = 154, KEY_F11 = 155, KEY_F12 = 156, KEY_F13 = 157, KEY_F14 = 158, KEY_F15 = 159, KEY_F16 = 160, KEY_F17 = 161, KEY_F18 = 162, KEY_F19 = 163, KEY_F20 = 164, KEY_F21 = 165, KEY_F22 = 166, KEY_F23 = 167, KEY_F24 = 168, }; typedef struct { b8 ctrl : 1; b8 shift : 1; b8 alt : 1; b8 caps : 1; b8 num : 1; b8 scroll : 1; b8 meta : 1; b8 repeat : 1; u16 key; c32 c; } Input_Key; typedef struct { i64 name_len; c8 *name; i64 data_size; u8 *data; } Drop_File; typedef struct { c32 code; i8 len; } UTF8_Char; typedef struct { c32 code; i8 len; } UTF16_Char; typedef struct { u16 protocol; u16 port; union { u32 v4_address_as_u32; u8 v4_address[4]; u8 v6_address[16]; }; } Network_Address; typedef struct { c8 *title; i32 frame_width; i32 frame_height; f64 pixel_size; i32 antialiasing_scale; b8 exact_resolution : 1; b8 enable_gamma : 1; b8 graceful_shutdown : 1; b8 enable_clipboard_text : 1; b8 enable_clipboard_image : 1; b8 enable_clipboard_sound : 1; b8 done : 1; b8 has_focus : 1; b8 has_cursor : 1; b8 files_dragged : 1; b8 files_dropped : 1; i32 real_width; i32 real_height; i32 cursor_x; i32 cursor_y; i32 cursor_dx; i32 cursor_dy; f64 wheel_dx; f64 wheel_dy; i64 num_sound_samples_elapsed; i64 num_pixels; i64 input_len; i64 input_capacity; i64 num_drop_files; i64 clipboard_text_len; i64 clipboard_image_width; i64 clipboard_image_height; i64 clipboard_image_len; i64 num_clipboard_sound_samples; i64 clipboard_sound_len; vec4_f32 *pixels; Input_Key *input; Drop_File *drop_files; c8 *clipboard_text; vec4_f32 *clipboard_image; f32 *clipboard_sound; b8 key_down [MAX_NUM_KEYS]; b8 key_pressed [MAX_NUM_KEYS]; void *window_handle_win32; u64 window_handle_x11; i64 memory_buffer_size; u8 *memory_buffer; } Platform; // UTF-8 i8 utf8_len (c32 c); UTF8_Char utf8_read (i64 len, c8 *s); i8 utf8_write(c32 c, c8 *buffer); // UTF-16 i8 utf16_len (c32 c); UTF16_Char utf16_read (i64 len, c16 *s); i8 utf16_write(c32 c, c16 *buffer); // Time and sleep i64 current_utc_time_in_milliseconds(void); void current_utc_time_in_sec_and_nsec(i64 *seconds, i64 *nanoseconds); void yield_thread_execution(void); void suspend_thread_for_milliseconds(i64 duration); // Window void init_main_window(void); i32 handle_main_window_events(void); i32 wait_main_window_events(void); void render_main_window_frame(void); // Convenient helper procedure for the event loop void run_main_window_event_loop(void); // Clipboard void write_clipboard_text(i64 size, c8 *data); void write_clipboard_image(i64 width, i64 height, vec4_f32 *pixels); void write_clipboard_sound(i8 num_channels, i64 num_samples, f32 *frames); // Take a screenshot void take_screenshot(i64 *width, i64 *height, i64 max_num_pixels, vec4_f32 *pixels); // Sound void handle_primary_sound(void); void queue_primary_sound(i64 delay_in_samples, i64 num_samples, f32 *frames); // Networking b8 network_open(u16 slot, Network_Address address, u16 *local_port); i64 network_recv(u16 slot, Network_Address address, i64 size, u8 *data, u16 *local_port, Network_Address *remote_address); i64 network_send(u16 slot, Network_Address address, i64 size, u8 *data, u16 *local_port); // Dynamic libraries void dynamic_library_open (u16 slot, c8 *name); void *dynamic_library_get_proc_address(u16 slot, c8 *proc); // Profiling void PROFILER_init(u32 slot, c8 *name); void PROFILER_begin(u32 slot); void PROFILER_end(u32 slot); // NOTE: This will shutdown main window, networking, audio, // and close all opened dynamic libraries. // If g_platform.graceful_shutdown is 0, this does nothing. void shutdown_all_systems(void); // Memory void *memory_buffer_allocate(i64 size, i64 alignment, i64 previous_size, void *previous_data); void resize_dynamic_array_exact(i64 *num, void **data, i64 element_size, i64 new_num); void resize_dynamic_array_capacity(i64 *num, i64 *capacity, void **data, i64 element_size, i64 new_num); #ifdef RUNTIME_HEADER // NOTE: Platform is a global object because we can't create multiple windows on Web. extern Platform g_platform; #endif // ================================================================ #ifdef __cplusplus } // extern "C" #endif #endif // RUNTIME_HEADER_GUARD_ // ================================================================ // // PLATFORM INDEPENDENT CODE // // ================================================================ #ifndef RUNTIME_HEADER #ifndef RUNTIME_IMPL_GUARD_ #define RUNTIME_IMPL_GUARD_ Platform g_platform = {0}; enum { PROFILE_MEMORY, PROFILE_DOWNSCALE, PROFILE_RESIZE, PROFILE_WAITING, PROFILE_FRAME, PROFILE_DRAW_PIXELS, PROFILE_FILL_RECTANGLE, PROFILE_FILL_TRIANGLE, PROFILE_FILL_TRIANGLES, PROFILE_FILL_QUAD, PROFILE_FILL_ELLIPSE, PROFILE_FILL_LINE, PROFILE_DRAW_TEXT_AREA, PROFILE_DRAW_TEXT_CURSOR, }; // ================================================================ // // Misc. // // ================================================================ static void mem_set_(void *dst, u8 x, i64 len) { u8 *d = (u8 *) dst; u8 *d_end = d + len; for (; d < d_end; ++d) *d = x; } static void mem_cpy_(void *dst, void *src, i64 len) { u8 *s = (u8 *) src; u8 *d = (u8 *) dst; u8 *d_end = d + len; for (; d < d_end; ++s, ++d) *d = *s; } static i32 min2_i32_(i32 x, i32 y) { return x < y ? x : y; } static i64 max2_i64_(i64 x, i64 y) { return x > y ? x : y; } static i64 min2_i64_(i64 x, i64 y) { return x < y ? x : y; } static f32 max2_f32_(f32 a, f32 b) { return a < b ? b : a; } static f64 min3_(f64 a, f64 b, f64 c) { if (a < b && a < c) return a; if (b < c) return b; return c; } static f64 max3_(f64 a, f64 b, f64 c) { if (a > b && a > c) return a; if (b > c) return b; return c; } static f64 min4_(f64 a, f64 b, f64 c, f64 d) { if (a < b && a < c && a < d) return a; if (b < c && b < d) return b; if (c < d) return c; return d; } static f64 max4_(f64 a, f64 b, f64 c, f64 d) { if (a > b && a > c && a > d) return a; if (b > c && b > d) return b; if (c > d) return c; return d; } // ================================================================ // // Log // // ================================================================ // TODO: // - Use print formatting after we implement it. // - Print time. enum { LOG_Level_None = 0, LOG_Level_Fatal = 1, LOG_Level_Error = 2, LOG_Level_Warning = 3, LOG_Level_Info = 4, LOG_Level_Verbose = 5, LOG_Level_Trace = 6, }; static u8 LOG_level = LOG_Level_Info; #if defined(__wasm__) void log_impl(i32 mode, i32 file_len, c8 const *file, i32 line, i32 func_len, c8 const *func, i32 text_len, c8 const *text); #define LOG_trace() \ do { \ if (LOG_level >= LOG_Level_Trace) \ log_impl( \ 0, \ sizeof(__FILE__) - 1, __FILE__, \ __LINE__, \ sizeof(__func__) - 1, __func__, \ 0, NULL); \ } while (0) #define LOG_print(text_, ...) \ do { \ if (LOG_level >= LOG_Level_Info) { \ i32 len = 0; \ while ((text_)[len] != '\0') ++len; \ log_impl( \ 0, \ sizeof(__FILE__) - 1, __FILE__, \ __LINE__, \ sizeof(__func__) - 1, __func__, \ len, text_); \ } \ } while (0) #define LOG_error(text_, ...) \ do { \ if (LOG_level >= LOG_Level_Error) { \ i32 len = 0; \ while ((text_)[len] != '\0') ++len; \ log_impl( \ 1, \ sizeof(__FILE__) - 1, __FILE__, \ __LINE__, \ sizeof(__func__) - 1, __func__, \ len, text_); \ } \ } while (0) #else #include #define LOG_trace() \ do { \ if (LOG_level >= LOG_Level_Trace) \ fprintf(stdout, "%s:%d, %s\n", __FILE__, __LINE__, __func__); \ } while (0) #define LOG_print(...) \ do { \ if (LOG_level >= LOG_Level_Info) { \ fprintf(stdout, __VA_ARGS__); \ fprintf(stdout, "\n"); \ } \ } while (0) #define LOG_error(...) \ do { \ if (LOG_level >= LOG_Level_Error) { \ fprintf(stderr, "%s:%d, %s: ", __FILE__, __LINE__, __func__); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n"); \ } \ } while (0) #endif // ================================================================ // // BLAKE2B // // TODO: // - Check correctness for arguments. // - Change API to be similar to other procs. // // ================================================================ enum blake2b_constant { BLAKE2B_BLOCKBYTES = 128, BLAKE2B_OUTBYTES = 64, BLAKE2B_KEYBYTES = 64, BLAKE2B_SALTBYTES = 16, BLAKE2B_PERSONALBYTES = 16 }; typedef struct { u8 digest_length; u8 key_length; u8 fanout; u8 depth; u32 leaf_length; u64 node_offset; u8 node_depth; u8 inner_length; u8 reserved[14]; u8 salt[BLAKE2B_SALTBYTES]; u8 personal[BLAKE2B_PERSONALBYTES]; } Blake2b_Param; typedef struct { u64 h[8]; u64 t[2]; u64 f[2]; u8 buf[2 * BLAKE2B_BLOCKBYTES]; u64 buflen; u8 last_node; } Blake2b_State; static u64 blake2b_IV[8] = { 0x6a09e667f3bcc908ull, 0xbb67ae8584caa73bull, 0x3c6ef372fe94f82bull, 0xa54ff53a5f1d36f1ull, 0x510e527fade682d1ull, 0x9b05688c2b3e6c1full, 0x1f83d9abfb41bd6bull, 0x5be0cd19137e2179ull }; static u8 blake2b_sigma[12][16] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } }; static b8 native_little_endian_(void) { return ((u8 *) &(u32) { 1 })[0] == 1; } static u64 load64_(void *src) { if (native_little_endian_()) return *( u64 * )( src ); else { u8 *p = ( u8 * )src; u64 w = *p++; w |= ( u64 )( *p++ ) << 8; w |= ( u64 )( *p++ ) << 16; w |= ( u64 )( *p++ ) << 24; w |= ( u64 )( *p++ ) << 32; w |= ( u64 )( *p++ ) << 40; w |= ( u64 )( *p++ ) << 48; w |= ( u64 )( *p++ ) << 56; return w; } } static void store32_(void *dst, u32 w) { if (native_little_endian_()) *( u32 * )( dst ) = w; else { u8 *p = ( u8 * )dst; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; } } static void store64_(void *dst, u64 w) { if (native_little_endian_()) *( u64 * )( dst ) = w; else { u8 *p = ( u8 * )dst; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; w >>= 8; *p++ = ( u8 )w; } } static u64 rotr64_(u64 w, u32 c) { return ( w >> c ) | ( w << ( 64 - c ) ); } static void secure_zero_memory_(void *v, u64 n) { volatile u8 *p = ( volatile u8 * )v; while( n-- ) *p++ = 0; } static i32 blake2b_set_lastnode_(Blake2b_State *S) { S->f[1] = ~0ull; return 0; } static i32 blake2b_set_lastblock_(Blake2b_State *S) { if( S->last_node ) blake2b_set_lastnode_( S ); S->f[0] = ~0ull; return 0; } static i32 blake2b_increment_counter_(Blake2b_State *S, u64 inc) { S->t[0] += inc; S->t[1] += ( S->t[0] < inc ); return 0; } static i32 blake2b_init0_(Blake2b_State *S) { i32 i; mem_set_( S, 0, sizeof( Blake2b_State ) ); for(i = 0; i < 8; ++i ) S->h[i] = blake2b_IV[i]; return 0; } static i32 blake2b_init_param_(Blake2b_State *S, Blake2b_Param *P) { blake2b_init0_( S ); u8 *p = ( u8 * )( P ); u64 i; for( i = 0; i < 8; ++i ) S->h[i] ^= load64_( p + sizeof( S->h[i] ) * i ); return 0; } i32 blake2b_init(Blake2b_State *S, u8 outlen) { Blake2b_Param P[1]; if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; P->digest_length = outlen; P->key_length = 0; P->fanout = 1; P->depth = 1; store32_( &P->leaf_length, 0 ); store64_( &P->node_offset, 0 ); P->node_depth = 0; P->inner_length = 0; mem_set_( P->reserved, 0, sizeof( P->reserved ) ); mem_set_( P->salt, 0, sizeof( P->salt ) ); mem_set_( P->personal, 0, sizeof( P->personal ) ); return blake2b_init_param_( S, P ); } static i32 blake2b_compress_(Blake2b_State *S, u8 block[BLAKE2B_BLOCKBYTES]) { u64 m[16]; u64 v[16]; i32 i; for( i = 0; i < 16; ++i ) m[i] = load64_( block + i * sizeof( m[i] ) ); for( i = 0; i < 8; ++i ) v[i] = S->h[i]; v[ 8] = blake2b_IV[0]; v[ 9] = blake2b_IV[1]; v[10] = blake2b_IV[2]; v[11] = blake2b_IV[3]; v[12] = S->t[0] ^ blake2b_IV[4]; v[13] = S->t[1] ^ blake2b_IV[5]; v[14] = S->f[0] ^ blake2b_IV[6]; v[15] = S->f[1] ^ blake2b_IV[7]; #define G(r,i,a,b,c,d) \ do { \ a = a + b + m[blake2b_sigma[r][2*i+0]]; \ d = rotr64_(d ^ a, 32); \ c = c + d; \ b = rotr64_(b ^ c, 24); \ a = a + b + m[blake2b_sigma[r][2*i+1]]; \ d = rotr64_(d ^ a, 16); \ c = c + d; \ b = rotr64_(b ^ c, 63); \ } while(0) #define ROUND(r) \ do { \ G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ G(r,2,v[ 2],v[ 6],v[10],v[14]); \ G(r,3,v[ 3],v[ 7],v[11],v[15]); \ G(r,4,v[ 0],v[ 5],v[10],v[15]); \ G(r,5,v[ 1],v[ 6],v[11],v[12]); \ G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ } while(0) ROUND( 0 ); ROUND( 1 ); ROUND( 2 ); ROUND( 3 ); ROUND( 4 ); ROUND( 5 ); ROUND( 6 ); ROUND( 7 ); ROUND( 8 ); ROUND( 9 ); ROUND( 10 ); ROUND( 11 ); for( i = 0; i < 8; ++i ) S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; #undef G #undef ROUND return 0; } i32 blake2b_update(Blake2b_State *S, u8 *in, u64 inlen) { while( inlen > 0 ) { u64 left = S->buflen; u64 fill = 2 * BLAKE2B_BLOCKBYTES - left; if( inlen > fill ) { mem_cpy_( S->buf + left, in, fill ); S->buflen += fill; blake2b_increment_counter_( S, BLAKE2B_BLOCKBYTES ); blake2b_compress_( S, S->buf ); mem_cpy_( S->buf, S->buf + BLAKE2B_BLOCKBYTES, BLAKE2B_BLOCKBYTES ); S->buflen -= BLAKE2B_BLOCKBYTES; in += fill; inlen -= fill; } else { mem_cpy_( S->buf + left, in, inlen ); S->buflen += inlen; in += inlen; inlen -= inlen; } } return 0; } i32 blake2b_init_key(Blake2b_State *S, u8 outlen, void *key, u8 keylen) { Blake2b_Param P[1]; if ( ( !outlen ) || ( outlen > BLAKE2B_OUTBYTES ) ) return -1; if ( !key || !keylen || keylen > BLAKE2B_KEYBYTES ) return -1; P->digest_length = outlen; P->key_length = keylen; P->fanout = 1; P->depth = 1; store32_( &P->leaf_length, 0 ); store64_( &P->node_offset, 0 ); P->node_depth = 0; P->inner_length = 0; mem_set_( P->reserved, 0, sizeof( P->reserved ) ); mem_set_( P->salt, 0, sizeof( P->salt ) ); mem_set_( P->personal, 0, sizeof( P->personal ) ); if( blake2b_init_param_( S, P ) < 0 ) return -1; { u8 block[BLAKE2B_BLOCKBYTES]; mem_set_( block, 0, BLAKE2B_BLOCKBYTES ); mem_cpy_( block, key, keylen ); blake2b_update( S, block, BLAKE2B_BLOCKBYTES ); secure_zero_memory_( block, BLAKE2B_BLOCKBYTES ); } return 0; } i32 blake2b_final(Blake2b_State *S, u8 *out, u8 outlen) { u8 buffer[BLAKE2B_OUTBYTES]; i32 i; if( S->buflen > BLAKE2B_BLOCKBYTES ) { blake2b_increment_counter_( S, BLAKE2B_BLOCKBYTES ); blake2b_compress_( S, S->buf ); S->buflen -= BLAKE2B_BLOCKBYTES; mem_cpy_( S->buf, S->buf + BLAKE2B_BLOCKBYTES, S->buflen ); } blake2b_increment_counter_( S, S->buflen ); blake2b_set_lastblock_( S ); mem_set_( S->buf + S->buflen, 0, 2 * BLAKE2B_BLOCKBYTES - S->buflen ); blake2b_compress_( S, S->buf ); for( i = 0; i < 8; ++i ) store64_( buffer + sizeof( S->h[i] ) * i, S->h[i] ); mem_cpy_( out, buffer, outlen ); return 0; } i32 blake2b(u8 *out, void *in, void *key, u8 outlen, u64 inlen, u8 keylen) { Blake2b_State S[1]; if ( NULL == in ) return -1; if ( NULL == out ) return -1; if ( NULL == key ) keylen = 0; if ( keylen > 0 ) { if ( blake2b_init_key( S, outlen, key, keylen ) < 0 ) return -1; } else { if ( blake2b_init( S, outlen ) < 0 ) return -1; } blake2b_update( S, ( u8 * )in, inlen ); blake2b_final( S, out, outlen ); return 0; } // ================================================================ // // Unicode // // ================================================================ i8 utf8_len(c32 c) { if ((c & 0x00007f) == c) return 1; if ((c & 0x0007ff) == c) return 2; if ((c & 0x00ffff) == c) return 3; if ((c & 0x1fffff) == c) return 4; return 0; } UTF8_Char utf8_read(i64 len, c8 *s) { if ( len >= 1 && (s[0] & 0x80) == 0) return (UTF8_Char) { .code = s[0], .len = 1, }; if ( len >= 2 && (s[0] & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80) return (UTF8_Char) { .code = (s[1] & 0x3f) | ((s[0] & 0x1f) << 6), .len = 2, }; if ( len >= 3 && (s[0] & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80) return (UTF8_Char) { .code = (s[2] & 0x3f) | ((s[1] & 0x3f) << 6) | ((s[0] & 0x0f) << 12), .len = 3, }; if ( len >= 4 && (s[0] & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80) return (UTF8_Char) { .code = (s[3] & 0x3f) | ((s[2] & 0x3f) << 6) | ((s[1] & 0x3f) << 12) | ((s[0] & 0x07) << 18), .len = 4, }; return (UTF8_Char) {0}; } i8 utf8_write(c32 c, c8 *buffer) { if ((c & 0x7f) == c) { buffer[0] = (c8) c; return 1; } if ((c & 0x7ff) == c) { buffer[0] = 0xc0 | ((c >> 6) & 0x1f); buffer[1] = 0x80 | ( c & 0x3f); return 2; } if ((c & 0xffff) == c) { buffer[0] = 0xc0 | ((c >> 12) & 0x0f); buffer[1] = 0x80 | ((c >> 6) & 0x3f); buffer[2] = 0x80 | ( c & 0x3f); return 3; } if ((c & 0x1fffff) == c) { buffer[0] = 0xc0 | ((c >> 18) & 0x07); buffer[1] = 0x80 | ((c >> 12) & 0x3f); buffer[2] = 0x80 | ((c >> 6) & 0x3f); buffer[3] = 0x80 | ( c & 0x3f); return 4; } return 0; } i8 utf16_len(c32 c) { if (c >= 0x000000 && c <= 0x00d7ff) return 1; if (c >= 0x00e000 && c <= 0x00ffff) return 1; if (c >= 0x010000 && c <= 0x10ffff) return 2; return 0; } UTF16_Char utf16_read(i64 len, c16 *s) { if ( len >= 1 && (s[0] & 0x8000) == 0) return (UTF16_Char) { .code = s[0], .len = 1, }; if ( len >= 2 && (s[0] & 0xfc00) == 0xd800 && (s[1] & 0xfc00) == 0xdc00) return (UTF16_Char) { .code = 0x10000 + ( (s[1] & 0x3ff) | ((s[0] & 0x3ff) << 10)), .len = 2, }; return (UTF16_Char) {0}; } i8 utf16_write(c32 c, c16 *buffer) { if ((c >= 0x0000 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { buffer[0] = (c16) c; return 1; } if (c >= 0x010000 && c <= 0x10ffff) { c32 cc = c - 0x10000; buffer[0] = 0xd800 | ((cc >> 10) & 0x3ff); buffer[1] = 0xdc00 | ( cc & 0x3ff); return 2; } return 0; } // ================================================================ // // Colors // // ================================================================ static f32 gamma_(f32 x) { if (x >= 0.0031308f) return 1.055f * (f32) pow(x, 1.0 / 2.4) - 0.055f; return 12.92f * x; } static f32 gamma_re_(f32 x) { if (x >= 0.04045f) return (f32) pow((x + 0.055f) / 1.055f, 2.4); return x / 12.92f; } static u32 rgb_u32_from_f32_(vec3_f32 c) { i32 ir = (i32) floor(c.x * 255. + .5); i32 ig = (i32) floor(c.y * 255. + .5); i32 ib = (i32) floor(c.z * 255. + .5); u32 r = ir < 0 ? 0u : ir > 255 ? 255u : (u32) ir; u32 g = ig < 0 ? 0u : ig > 255 ? 255u : (u32) ig; u32 b = ib < 0 ? 0u : ib > 255 ? 255u : (u32) ib; return (r << 16) | (g << 8) | b; } static vec3_f32 rgb_f32_from_u32_(u32 c) { return (vec3_f32) { .x = ((c >> 16) & 0xff) / 255.f, .y = ((c >> 8) & 0xff) / 255.f, .z = ( c & 0xff) / 255.f, }; } // ================================================================ // // Memory buffer allocator // // ================================================================ static i64 align_(i64 x, i64 alignment) { return ((x + (alignment - 1)) / alignment) * alignment; } static i64 memory_buffer_occupied_len_(i64 buffer_size) { return (buffer_size + 64 * MEMORY_CHUNK_SIZE - 1) / (64 * MEMORY_CHUNK_SIZE); } static void *memory_buffer_allocate_from_(i64 memory_buffer_size, u8 *memory_buffer, i64 size, i64 alignment, i64 previous_size, void *previous_data) { if (memory_buffer_size <= 0) { LOG_error("Invalid memory buffer size."); return NULL; } if (memory_buffer == NULL) { LOG_error("Invalid memory buffer."); return NULL; } u8 *dst = NULL; PROFILER_begin(PROFILE_MEMORY); i64 occupied_len = memory_buffer_occupied_len_(memory_buffer_size); i64 occupied_len_bytes = occupied_len * 8; i64 occupied_len_bits = occupied_len * 64; u64 *occupied = (u64 *) memory_buffer; if (memory_buffer_size <= occupied_len_bytes) { LOG_error("Memory buffer too small."); goto _finish; } if (alignment <= 0) { LOG_error("Invalid alignment: %lld", alignment); goto _finish; } if (size < 0) { LOG_error("Invalid size: %lld", size); goto _finish; } if (previous_size < 0) { LOG_error("Invalid previous size: %lld", previous_size); goto _finish; } if (previous_size > 0 && previous_data == NULL) { LOG_error("Invalid previous data."); goto _finish; } u8 *data = memory_buffer + occupied_len_bytes; i64 prev_num_chunks = 0; i64 prev_chunk = 0; if (previous_size > 0) { prev_num_chunks = (align_(previous_size, alignment) + MEMORY_CHUNK_SIZE - 1) / MEMORY_CHUNK_SIZE; prev_chunk = ((u8 *) previous_data - data) / MEMORY_CHUNK_SIZE; } i64 num_chunks = (align_(size, alignment) + MEMORY_CHUNK_SIZE - 1) / MEMORY_CHUNK_SIZE; i64 chunk = (memory_buffer_size + MEMORY_CHUNK_SIZE - 1) / MEMORY_CHUNK_SIZE; if (num_chunks == prev_num_chunks) { dst = (u8 *) previous_data; goto _finish; } if (num_chunks < prev_num_chunks) { // Reuse previously allocated space. chunk = prev_chunk; dst = data + chunk * MEMORY_CHUNK_SIZE; } else { // Search free space for (i64 i = 0;; ++i) { while ((i % 64) == 0 && i < occupied_len_bits && (i + 64 <= prev_chunk || i >= prev_chunk + prev_num_chunks) && occupied[i / 64] == ~0ull) i += 64; while ((i % 8) == 0 && i < occupied_len_bits && (i + 8 <= prev_chunk || i >= prev_chunk + prev_num_chunks) && ((u8 *) occupied)[i / 8] == 255u) i += 8; if (i >= occupied_len_bits) break; b8 is_occupied = 0; for (i64 j = i; j < i + num_chunks; ++j) { if (j >= prev_chunk && j < prev_chunk + prev_num_chunks) continue; if ((occupied[j / 64] & (1ull << (j % 64))) == 0) continue; is_occupied = 1; break; } if (!is_occupied) { chunk = i; break; } } // Check if out of memory dst = data + chunk * MEMORY_CHUNK_SIZE; if (dst + size > memory_buffer + memory_buffer_size) { LOG_error("Out of memory: %lld bytes", size - previous_size); return NULL; } // Claim required space for (i64 j = chunk; j < chunk + num_chunks; ++j) occupied[j / 64] |= 1ull << (j % 64); } if (chunk != prev_chunk) { // Copy data i64 len = size < previous_size ? size : previous_size; u8 *s0 = data + prev_chunk * MEMORY_CHUNK_SIZE; u8 *s1 = s0 + len - 1; u8 *d0 = dst; u8 *d1 = dst + len - 1; if (d0 > s0) for (u8 *d = d1, *s = s1; d >= d0; --d, --s) *d = *s; else for (u8 *d = d0, *s = s0; d <= d1; ++d, ++s) *d = *s; } // Free previous space for (i64 j = prev_chunk; j < prev_chunk + prev_num_chunks; ++j) { if (j >= chunk && j < chunk + num_chunks) continue; occupied[j / 64] &= ~(1ull << (j % 64)); } if (num_chunks == 0) dst = NULL; _finish: PROFILER_end(PROFILE_MEMORY); return dst; } static u8 _memory_buffer[STATIC_MEMORY_BUFFER_SIZE] = {0}; void *memory_buffer_allocate(i64 size, i64 alignment, i64 previous_size, void *previous_data) { if (g_platform.memory_buffer == NULL || g_platform.memory_buffer_size <= 0) { g_platform.memory_buffer_size = sizeof _memory_buffer; g_platform.memory_buffer = _memory_buffer; } return memory_buffer_allocate_from_(g_platform.memory_buffer_size, g_platform.memory_buffer, size, alignment, previous_size, previous_data); } void resize_dynamic_array_exact(i64 *num, void **data, i64 element_size, i64 new_num) { if (num == NULL || data == NULL) { LOG_error("Sanity"); return; } if (*num == new_num) return; i64 alignment = max2_i64_(8, element_size); u8 *new_data = (u8 *) memory_buffer_allocate( new_num * element_size, alignment, *num * element_size, *data ); if (new_data == NULL && new_num > 0) // Out of memory. return; if (new_num > *num) mem_set_(new_data + element_size * *num, 0, new_num - *num); *num = new_num; *data = new_data; } void resize_dynamic_array_capacity(i64 *num, i64 *capacity, void **data, i64 element_size, i64 new_num) { if (num == NULL || capacity == NULL || data == NULL) { LOG_error("Sanity"); return; } if (*num == new_num) return; if (new_num > *capacity) resize_dynamic_array_exact(capacity, data, element_size, new_num); if (new_num <= *capacity) *num = new_num; } // ================================================================ // // Profiling // // ================================================================ typedef struct { c8 *name; i64 amount; i64 count; i64 time_sec; i64 time_nsec; i64 begin_sec; i64 begin_nsec; } Profiler_Slot_; b8 _profiler_ready = 0; i64 _profiler_num_slots = 0; Profiler_Slot_ *_profiler_slots = NULL; void PROFILER_init(u32 slot, c8 *name) { if ((i64) (u64) slot >= _profiler_num_slots) resize_dynamic_array_exact(&_profiler_num_slots, (void **) &_profiler_slots, sizeof *_profiler_slots, (i64) (u64) slot + 1); if ((i64) (u64) slot >= _profiler_num_slots) { LOG_error("Invalid slot: %d", (i32) slot); return; } mem_set_(_profiler_slots + slot, 0, sizeof *_profiler_slots); _profiler_slots[slot].name = name; _profiler_ready = 1; } void PROFILER_begin(u32 slot) { if (!_profiler_ready) return; if ((i64) (u64) slot >= _profiler_num_slots) { LOG_error("Invalid slot: %d", (i32) slot); return; } if (_profiler_slots[slot].count < 0) { LOG_error("Sanity"); return; } ++_profiler_slots[slot].count; if (_profiler_slots[slot].count != 1) return; ++_profiler_slots[slot].amount; current_utc_time_in_sec_and_nsec( &_profiler_slots[slot].begin_sec, &_profiler_slots[slot].begin_nsec ); } static void normalize_time_(i64 *sec, i64 *nsec) { if (sec == NULL || nsec == NULL) { LOG_error("Sanity"); return; } if (*nsec < 0) { *sec -= 1; *nsec += 1000000000; } if (*nsec >= 1000000000) { *sec += *nsec / 1000000000; *nsec = *nsec % 1000000000; } } void PROFILER_end(u32 slot) { if (!_profiler_ready) return; if ((i64) (u64) slot >= _profiler_num_slots) return; if (_profiler_slots[slot].count <= 0) { LOG_error("Sanity"); return; } --_profiler_slots[slot].count; if (_profiler_slots[slot].count != 0) return; i64 sec, nsec; current_utc_time_in_sec_and_nsec(&sec, &nsec); sec -= _profiler_slots[slot].begin_sec; nsec -= _profiler_slots[slot].begin_nsec; normalize_time_(&sec, &nsec); for (i64 i = 0; i < _profiler_num_slots; ++i) { if (_profiler_slots[i].count == 0) continue; _profiler_slots[i].time_sec -= sec; _profiler_slots[i].time_nsec -= nsec; normalize_time_(&_profiler_slots[i].time_sec, &_profiler_slots[i].time_nsec); } _profiler_slots[slot].begin_sec = 0; _profiler_slots[slot].begin_nsec = 0; _profiler_slots[slot].time_sec += sec; _profiler_slots[slot].time_nsec += nsec; normalize_time_(&_profiler_slots[slot].time_sec, &_profiler_slots[slot].time_nsec); } void PROFILER_report_(void) { if (_profiler_num_slots > 0) { LOG_print("PROFILER REPORT"); for (i64 i = 0; i < _profiler_num_slots; ++i) { if (_profiler_slots[i].name == NULL || _profiler_slots[i].amount == 0) continue; f64 k = _profiler_slots[i].amount == 0 ? 0. : 1. / _profiler_slots[i].amount; f64 f = ((f64) _profiler_slots[i].time_sec) * k + ((f64 )_profiler_slots[i].time_nsec * .000000001) * k; LOG_print("%-31s %3lld.%09lld / %4lld = %.6f", _profiler_slots[i].name, _profiler_slots[i].time_sec, _profiler_slots[i].time_nsec, _profiler_slots[i].amount, f); } } } // ================================================================ // // Platform // // ================================================================ void run_main_window_event_loop(void) { init_main_window(); #if !defined(__wasm__) while (!g_platform.done) { PROFILER_begin(PROFILE_FRAME); update_and_render_frame(); PROFILER_end(PROFILE_FRAME); } shutdown_all_systems(); #endif } // ================================================================ // // Frame // // ================================================================ static i64 _frame_index = 0; static i64 _frame_duration[NUM_FRAMES_AVERAGED] = {0}; static void merge_sort_i64_(i64 len, i64 *values, i64 *buffer) { if (len <= 0) return; if (values == NULL || buffer == NULL) { LOG_error("Sanity"); return; } i64 *a = values; i64 *b = buffer; i64 chunk_size = 1; for (; chunk_size < len; chunk_size <<= 1) { for (i64 offset = 0; offset < len; offset += chunk_size * 2) { i64 *s0 = a + offset; i64 *s0_end = s0 + chunk_size; if (s0_end > a + len) s0_end = a + len; i64 *s1 = s0_end; i64 *s1_end = s1 + chunk_size; i64 *d = b + offset; i64 *d_end = b + chunk_size * 2; if (s1_end > a + len) s1_end = a + len; if (d_end > b + len) d_end = b + len; for (; d < d_end; ++d) if (s0 >= s0_end) { *d = *s1; ++s1; } else if (s1 >= s1_end) { *d = *s0; ++s0; } else if (*s0 > *s1) { *d = *s0; ++s0; } else { *d = *s1; ++s1; } } i64 *c = a; a = b; b = c; } if (a != values) mem_cpy_(values, a, len * sizeof *values); } static i64 average_frame_duration_(i64 duration) { _frame_duration[_frame_index] = duration; _frame_index = (_frame_index + 1) % NUM_FRAMES_AVERAGED; i64 durs[NUM_FRAMES_AVERAGED + 1]; for (i64 i = 0; i < NUM_FRAMES_AVERAGED; ++i) durs[i] = _frame_duration[i]; i64 buf[NUM_FRAMES_AVERAGED + 1]; merge_sort_i64_(NUM_FRAMES_AVERAGED, durs, buf); if (AVERAGE_FRAME_BIAS < 0.0 || AVERAGE_FRAME_BIAS > 1.0) return durs[0]; return durs[(i64) ((NUM_FRAMES_AVERAGED - 1) * AVERAGE_FRAME_BIAS)]; } // ================================================================ // // Graphics // // ================================================================ static i32 _internal_width = 0; static i32 _internal_height = 0; static i64 _internal_pixels_len = 0; static u32 *_internal_pixels = NULL; static i64 _internal_row_len = 0; static vec3_f32 *_internal_row = NULL; static b8 pixel_size_update_(i64 real_width, i64 real_height) { b8 size_changed = 0; if (g_platform.antialiasing_scale <= 0) g_platform.antialiasing_scale = DEFAULT_ANTIALIASING_SCALE; if (g_platform.pixel_size <= 0.0) g_platform.pixel_size = DEFAULT_PIXEL_SIZE; if (g_platform.pixel_size < MIN_PIXEL_SIZE) g_platform.pixel_size = MIN_PIXEL_SIZE; if (g_platform.pixel_size > MAX_PIXEL_SIZE) g_platform.pixel_size = MAX_PIXEL_SIZE; i64 width = (i64) floor(((f64) real_width) / g_platform.pixel_size + .5) * g_platform.antialiasing_scale; i64 height = (i64) floor(((f64) real_height) / g_platform.pixel_size + .5) * g_platform.antialiasing_scale; if (g_platform.real_width != real_width || g_platform.real_height != real_height) { size_changed = 1; g_platform.real_width = real_width; g_platform.real_height = real_height; } resize_dynamic_array_exact(&g_platform.num_pixels, (void **) &g_platform.pixels, sizeof *g_platform.pixels, width * height); if (g_platform.num_pixels < width * height) LOG_error("Failed to allocate %lld x %lld pixel buffer.", width, height); height = width <= 0 ? 0 : g_platform.num_pixels / width; if (g_platform.frame_width != width || g_platform.frame_height != height) { size_changed = 1; g_platform.frame_width = width; g_platform.frame_height = height; } i64 internal_width = max2_i64_(real_width, width / g_platform.antialiasing_scale); i64 internal_height = max2_i64_(real_height, height / g_platform.antialiasing_scale); resize_dynamic_array_exact(&_internal_pixels_len, (void **) &_internal_pixels, sizeof *_internal_pixels, internal_width * internal_height); if (_internal_pixels_len < internal_width * internal_height) LOG_error("Failed to allocate %lld x %lld internal pixel buffer.", internal_width, internal_height); resize_dynamic_array_exact(&_internal_row_len, (void **) &_internal_row, sizeof *_internal_row, internal_width); if (_internal_row_len < internal_width) LOG_error("Failed to allocate %lld internal immediate pixel buffer.", internal_width); _internal_width = real_width; _internal_height = real_width <= 0 ? 0 : min2_i32_(_internal_pixels_len / real_width, real_height); return size_changed; } static void convert_pixels_for_window_(void) { u32 aa_scale = g_platform.antialiasing_scale; if (aa_scale == 0 || aa_scale > 1024) { LOG_error("Sanity"); return; } // Downscale pixels from vec4_f32 pixel buffer to internal u32 pixel buffer. { u32 dst_width = g_platform.frame_width / aa_scale; u32 dst_height = g_platform.frame_height / aa_scale; u32 src_width = g_platform.frame_width; if (g_platform.num_pixels < src_width * g_platform.frame_height) { LOG_error("Sanity"); return; } if (_internal_pixels_len < dst_width * dst_height) { LOG_error("Sanity"); return; } if (_internal_row_len < dst_width) { LOG_error("Sanity"); return; } PROFILER_begin(PROFILE_DOWNSCALE); if (aa_scale == 1) { vec4_f32 *s = g_platform.pixels; u32 *d = _internal_pixels; u32 *d_end = d + dst_width * dst_height; if (g_platform.enable_gamma) for (; d < d_end; ++d, ++s) *d = rgb_u32_from_f32_((vec3_f32) { gamma_(s->x), gamma_(s->y), gamma_(s->z) }) | 0xff000000; else for (; d < d_end; ++d, ++s) *d = rgb_u32_from_f32_((vec3_f32) { s->x, s->y, s->z }) | 0xff000000; } else { f32 k = 1.f / (aa_scale * aa_scale); vec4_f32 *s = g_platform.pixels; u32 *d = _internal_pixels; for (u32 j = 0; j < dst_height; ++j) { mem_set_(_internal_row, 0, dst_width * sizeof *_internal_row); for (u32 jj = 0; jj < aa_scale; ++jj) { vec3_f32 *im = _internal_row; vec3_f32 *im_end = im + dst_width; for (; im < im_end; ++im) { vec4_f32 *s_end = s + aa_scale; for (; s < s_end; ++s) { im->x += s->x; im->y += s->y; im->z += s->z; } } } u32 *d_end = d + dst_width; vec3_f32 *im = _internal_row; vec3_f32 *im_end = _internal_row + dst_width; if (g_platform.enable_gamma) for (; im < im_end; ++im) { im->x = gamma_(im->x * k); im->y = gamma_(im->y * k); im->z = gamma_(im->z * k); } else for (; im < im_end; ++im) { im->x *= k; im->y *= k; im->z *= k; } im = _internal_row; for (; d < d_end; ++d, ++im) *d = rgb_u32_from_f32_(*im) | 0xff000000; } } PROFILER_end(PROFILE_DOWNSCALE); } // Resize internal pixel buffer in-place. { i64 src_width = g_platform.frame_width / aa_scale; i64 src_height = g_platform.frame_height / aa_scale; i64 src_len = src_width * src_height; i64 dst_width = _internal_width; i64 dst_height = _internal_height; i64 dst_len = dst_width * dst_height; if (_internal_pixels_len < src_len) { LOG_error("Sanity"); return; } if (_internal_pixels_len < dst_len) { LOG_error("Sanity"); return; } PROFILER_begin(PROFILE_RESIZE); u32 x_ratio = (src_width << 16) / dst_width; u32 y_ratio = (src_height << 16) / dst_height; u32 x_half = x_ratio / 2; u32 y_half = y_ratio / 2; if (src_len < dst_len) for (u32 j = dst_height - 1;; --j) { u32 jj_w = ((j * y_ratio + y_half) >> 16) * src_width; u32 j_w = j * dst_width; for (u32 i = dst_width - 1;; --i) { u32 ii = (i * x_ratio + x_half) >> 16; _internal_pixels[j_w + i] = _internal_pixels[jj_w + ii]; if (i == 0) break; } if (j == 0) break; } else for (u32 j = 0; j < dst_height; ++j) { u32 jj_w = ((j * y_ratio + y_half) >> 16) * src_width; u32 j_w = j * dst_width; for (u32 i = 0; i < dst_width; ++i) { u32 ii = (i * x_ratio + x_half) >> 16; _internal_pixels[j_w + i] = _internal_pixels[jj_w + ii]; } } PROFILER_end(PROFILE_RESIZE); } } static void pixel_size_calibrate_(i64 current_frame_duration) { if (g_platform.exact_resolution) return; i64 frame_duration = average_frame_duration_(current_frame_duration); if (current_frame_duration > FRAME_DURATION_HARD_LIMIT) g_platform.pixel_size = MAX_PIXEL_SIZE; else if (g_platform.pixel_size < MAX_PIXEL_SIZE && frame_duration > MAX_FRAME_DURATION) g_platform.pixel_size += PIXEL_SIZE_DELTA * (frame_duration - MAX_FRAME_DURATION); else if (g_platform.pixel_size > MIN_PIXEL_SIZE && frame_duration < MIN_FRAME_DURATION) g_platform.pixel_size -= PIXEL_SIZE_DELTA * (MIN_FRAME_DURATION - frame_duration); } static void cleanup_pixel_buffers_(void) { resize_dynamic_array_exact(&_internal_pixels_len, (void **) &_internal_pixels, sizeof *_internal_pixels, 0); resize_dynamic_array_exact(&_internal_row_len, (void **) &_internal_row, sizeof *_internal_row, 0); resize_dynamic_array_exact(&g_platform.num_pixels, (void **) &g_platform.pixels, sizeof *g_platform.pixels, 0); } // ================================================================ // // Sound // // ================================================================ static i64 _sound_clock_time = 0; static i64 _sound_clock_carry = 0; static i64 _sound_position = 0; static f32 _sound_ring[MAX_NUM_PRIMARY_SOUND_FRAMES] = {0}; static i64 sound_samples_elapsed_(void) { if (_sound_clock_time == 0) { _sound_clock_time = current_utc_time_in_milliseconds(); _sound_clock_carry = 0; return 0; } i64 time_elapsed = current_utc_time_in_milliseconds() - _sound_clock_time; i64 delta = time_elapsed * PRIMARY_SOUND_SAMPLE_RATE + _sound_clock_carry; i64 num_samples = delta / 1000; _sound_clock_time += time_elapsed; _sound_clock_carry = delta % 1000; return num_samples; } // ================================================================ // // Drop files // // ================================================================ static void drop_files_clean_(void) { g_platform.files_dragged = 0; g_platform.files_dropped = 0; for (i64 i = 0; i < g_platform.num_drop_files; ++i) { resize_dynamic_array_exact(&g_platform.drop_files[i].name_len, (void **) &g_platform.drop_files[i].name, 1, 0); resize_dynamic_array_exact(&g_platform.drop_files[i].data_size, (void **) &g_platform.drop_files[i].data, 1, 0); } resize_dynamic_array_exact(&g_platform.num_drop_files, (void **) &g_platform.drop_files, sizeof *g_platform.drop_files, 0); } static void drop_files_set_num_(i64 num) { if (num <= g_platform.num_drop_files) { LOG_error("Sanity"); return; } resize_dynamic_array_exact(&g_platform.num_drop_files, (void **) &g_platform.drop_files, sizeof *g_platform.drop_files, num); } static void drop_files_set_name_(i64 index, i64 name_len, c8 *name) { if (g_platform.drop_files == NULL) { LOG_error("Sanity"); return; } if (index < 0 || index >= g_platform.num_drop_files) { LOG_error("Sanity"); return; } Drop_File *f = g_platform.drop_files + index; resize_dynamic_array_exact(&f->name_len, (void **) &f->name, 1, name_len); if (name != NULL) mem_cpy_(f->name, name, f->name_len); } static void drop_files_set_data_(i64 index, i64 data_size) { if (g_platform.drop_files == NULL) { LOG_error("Sanity"); return; } if (index < 0 || index >= g_platform.num_drop_files) { LOG_error("Sanity"); return; } Drop_File *f = g_platform.drop_files + index; resize_dynamic_array_exact(&f->data_size, (void **) &f->data, 1, data_size); } // ================================================================ // // Clipboard // // ================================================================ static void store_clipboard_text_(i64 size, c8 *data) { if (size < 0 || data == NULL) return; resize_dynamic_array_exact(&g_platform.clipboard_text_len, (void **) &g_platform.clipboard_text, 1, size + 1); i64 len = g_platform.clipboard_text_len - 1; mem_cpy_(g_platform.clipboard_text, data, len); if (len >= 0) g_platform.clipboard_text[len] = '\0'; } // ================================================================ // // UTF-16 buffer // // ================================================================ static i64 _utf16_buffer_len = 0; static c16 *_utf16_buffer = NULL; static void buffer_utf16_set_len_(i16 len) { if (_utf16_buffer_len >= len) return; resize_dynamic_array_exact(&_utf16_buffer_len, (void **) &_utf16_buffer, sizeof *_utf16_buffer, _utf16_buffer_len); } static c16 *buffer_utf16_empty_(void) { buffer_utf16_set_len_(1); if (_utf16_buffer != NULL && _utf16_buffer_len > 0) _utf16_buffer[0] = '\0'; return _utf16_buffer; } static c16 *buffer_utf16_from_utf8_(c8 *s) { if (s == NULL) { LOG_error("Sanity"); return buffer_utf16_empty_(); } c8 *s_end = s; while (*s_end != '\0') ++s_end; i64 len = 0; for (c8 *a = s; a < s_end;) { UTF8_Char c = utf8_read(s_end - a, a); if (c.len == 0) { LOG_error("Invalid UTF-8 string."); return buffer_utf16_empty_(); } buffer_utf16_set_len_(len + 2); i8 n = utf16_write(c.code, _utf16_buffer + len); if (n == 0) { LOG_error("Unexpected UTF-16 character."); return buffer_utf16_empty_(); } len += n; a += c.len; } buffer_utf16_set_len_(len + 1); _utf16_buffer[len] = '\0'; return _utf16_buffer; } // ================================================================ // // PLATFORM-SPECIFIC CODE // // ================================================================ // // Dynamic libraries // // ================================================================ #if defined(__unix__) #include #endif #if defined(_WIN32) && !defined(_INC_WINDOWS) #ifndef WINAPI #if defined(_ARM_) #define WINAPI #else #define WINAPI __stdcall #endif #endif typedef c8 const *LPCSTR; typedef c16 const *LPCWSTR; typedef void ** HMODULE; typedef int WINBOOL; typedef iptr (WINAPI *FARPROC)(); // Kernel32.dll HMODULE WINAPI LoadLibraryW (LPCWSTR lpLibFileName); WINBOOL WINAPI FreeLibrary (HMODULE hLibModule); FARPROC WINAPI GetProcAddress (HMODULE hModule, LPCSTR lpProcName); DWORD WINAPI GetLastError (void); #endif typedef struct { void *handle; } Dynamic_Library_Slot_; typedef struct { i64 num_slots; Dynamic_Library_Slot_ *slots; } Dynamic_Libraries_; static Dynamic_Libraries_ _dynamic_libraries = {0}; void dynamic_library_open(u16 slot, c8 *name) { if (name == NULL) { LOG_error("Sanity"); return; } if ((i64) (u64) slot >= _dynamic_libraries.num_slots) resize_dynamic_array_exact(&_dynamic_libraries.num_slots, (void **) &_dynamic_libraries.slots, sizeof *_dynamic_libraries.slots, (i64) slot + 1); if ((i64) (u64) slot >= _dynamic_libraries.num_slots) { LOG_error("Invalid slot: %d", (i32) slot); return; } if (_dynamic_libraries.slots[slot].handle != NULL) { #if defined(__unix__) dlclose(_dynamic_libraries.slots[slot].handle); #elif defined(_WIN32) FreeLibrary((HMODULE) _dynamic_libraries.slots[slot].handle); #endif _dynamic_libraries.slots[slot].handle = NULL; } void *h = NULL; #if defined(__unix__) h = dlopen(name, RTLD_LAZY); #elif defined(_WIN32) h = (void *) LoadLibraryW(buffer_utf16_from_utf8(name)); #endif if (h == NULL) { LOG_error("Failed to open: %s", name); #if defined(__unix__) LOG_error("%s", dlerror()); #elif defined(_WIN32) LOG_error("Error code: %d", GetLastError()); #endif } _dynamic_libraries.slots[slot].handle = h; } void *dynamic_library_get_proc_address(u16 slot, c8 *proc) { if ((i64) (u64) slot >= _dynamic_libraries.num_slots) { LOG_error("Invalid slot: %d", (i32) slot); return NULL; } if (proc == NULL) { LOG_error("Sanity"); return NULL; } if (_dynamic_libraries.slots[slot].handle == NULL) { LOG_error("Slot is closed: %d", (i32) slot); return NULL; } void *proc_address = NULL; #if defined(__unix__) proc_address = dlsym(_dynamic_libraries.slots[slot].handle, proc); #elif defined(_WIN32) proc_address = (void *) GetProcAddress((HMODULE) _dynamic_libraries.slots[slot].handle, proc); #endif if (proc_address == NULL) { LOG_error("Failed to get proc: %s", proc); #if defined(__unix__) LOG_error("%s", dlerror()); #elif defined(_WIN32) LOG_error("Error code: %d", GetLastError()); #endif } return proc_address; } static void close_all_dynamic_libraries_(void) { for (i64 i = 0; i < _dynamic_libraries.num_slots; ++i) if (_dynamic_libraries.slots[i].handle != NULL) { #if defined(__unix__) dlclose(_dynamic_libraries.slots[i].handle); #elif defined(_WIN32) FreeLibrary((HMODULE) _dynamic_libraries.slots[i].handle); #endif } resize_dynamic_array_exact(&_dynamic_libraries.num_slots, (void **) &_dynamic_libraries.slots, sizeof *_dynamic_libraries.slots, 0); } // ================================================================ // // Time // // ================================================================ #if defined(__unix__) #include #include #include void current_utc_time_in_sec_and_nsec(i64 *seconds, i64 *nanoseconds) { struct timespec t; timespec_get(&t, TIME_UTC); if (seconds != NULL) *seconds = t.tv_sec; if (nanoseconds != NULL) *nanoseconds = t.tv_nsec; } i64 current_utc_time_in_milliseconds(void) { struct timespec t; timespec_get(&t, TIME_UTC); return 1000ll * t.tv_sec + t.tv_nsec / 1000000ll; } void yield_thread_execution(void) { sched_yield(); } void suspend_thread_for_milliseconds(i64 duration) { if (duration == 0) usleep(0); if (duration <= 0) return; if (duration >= 1000) // seconds sleep(duration / 1000); if ((duration % 1000) > 0) // microseconds usleep((duration % 1000) * 1000); } #endif // defined(__unix__) // ================================================================ // // Networking // // ================================================================ #if defined(__unix__) #include #include #include #include #include typedef struct { b8 ready; i32 socket; u16 local_port; Network_Address address; } Socket_Slot; static b8 _network_ready = 0; static i64 _num_sockets = 0; static Socket_Slot *_sockets = NULL; static void network_init_(u16 slot) { if (!_network_ready) { signal(SIGPIPE, SIG_IGN); _network_ready = 1; } i64 num = (i64) (u64) slot; i64 prev_num = _num_sockets; if (num > prev_num) resize_dynamic_array_exact(&_num_sockets, (void **) &_sockets, sizeof *_sockets, num); } static void network_cleanup_(void) { for (i64 i = 0; i < _num_sockets; ++i) if (_sockets[i].ready) { close(_sockets[i].socket); _sockets[i].ready = 0; } resize_dynamic_array_exact(&_num_sockets, (void **) &_sockets, sizeof *_sockets, 0); } b8 network_open(u16 slot, Network_Address address, u16 *local_port) { network_init_(slot); if ((i64) (u64) slot >= _num_sockets) { LOG_error("Invalid slot: %d", (i32) slot); return 0; } b8 change_address = !_sockets[slot].ready || _sockets[slot].address.protocol != address.protocol || (address.port != 0 && _sockets[slot].local_port != address.port) || (memcmp(_sockets[slot].address.v6_address, &(u8[sizeof address.v6_address]) {0}, sizeof address.v6_address) != 0 && memcmp(_sockets[slot].address.v6_address, address.v6_address, sizeof address.v6_address) != 0); if (change_address && _sockets[slot].ready) { close(_sockets[slot].socket); _sockets[slot].ready = 0; } struct sockaddr *p; i32 p_len; struct sockaddr_in a4 = {0}; struct sockaddr_in6 a6 = {0}; if (address.protocol == IPv4_UDP) { p = (struct sockaddr *) &a4; p_len = sizeof a4; } else { p = (struct sockaddr *) &a6; p_len = sizeof a6; } if (!_sockets[slot].ready) { _sockets[slot].socket = socket(address.protocol == IPv4_UDP ? AF_INET : AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (_sockets[slot].socket == -1) { LOG_error("socket failed (errno %d).", errno); return 0; } if (address.protocol == IPv4_UDP) { a4.sin_family = AF_INET; a4.sin_port = htons(address.port); a4.sin_addr.s_addr = address.v4_address_as_u32; } else { a6.sin6_family = AF_INET6; a6.sin6_port = htons(address.port); mem_cpy_(a6.sin6_addr.s6_addr, address.v6_address, sizeof a6.sin6_addr.s6_addr); } if (bind(_sockets[slot].socket, p, p_len) == -1) { close(_sockets[slot].socket); LOG_error("bind failed (errno %d).", errno); return 0; } if (getsockname(_sockets[slot].socket, p, &(socklen_t) {p_len}) == -1) { close(_sockets[slot].socket); LOG_error("getsockname failed (errno %d).", errno); return 0; } if (p->sa_family == AF_INET) _sockets[slot].local_port = ntohs(a4.sin_port); else _sockets[slot].local_port = ntohs(a6.sin6_port); _sockets[slot].ready = 1; _sockets[slot].address = address; } if (local_port != NULL) *local_port = _sockets[slot].local_port; return 1; } i64 network_recv(u16 slot, Network_Address address, i64 size, u8 *data, u16 *local_port, Network_Address *remote_address) { if (address.protocol != IPv4_UDP && address.protocol != IPv6_UDP) { LOG_error("Invalid address protocol: %d", (i32) (u32) address.protocol); return -1; } if (!network_open(slot, address, local_port)) return -1; if (size <= 0) return 0; struct sockaddr *p; i32 p_len; struct sockaddr_in a4 = {0}; struct sockaddr_in6 a6 = {0}; if (address.protocol == IPv4_UDP) { p = (struct sockaddr *) &a4; p_len = sizeof a4; } else { p = (struct sockaddr *) &a6; p_len = sizeof a6; } i64 received = recvfrom( _sockets[slot].socket, data, size, MSG_DONTWAIT, p, &(socklen_t) {p_len} ); if (received < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; LOG_error("recvfrom failed (errno %d).", errno); return -1; } if (remote_address != NULL) { mem_set_(remote_address, 0, sizeof *remote_address); remote_address->protocol = address.protocol; if (address.protocol == IPv4_UDP) { remote_address->port = ntohs(a4.sin_port); remote_address->v4_address_as_u32 = a4.sin_addr.s_addr; } else { remote_address->port = ntohs(a6.sin6_port); mem_cpy_(remote_address->v6_address, a6.sin6_addr.s6_addr, sizeof remote_address->v6_address); } } return received; } i64 network_send(u16 slot, Network_Address address, i64 size, u8 *data, u16 *local_port) { if (address.protocol != IPv4_UDP && address.protocol != IPv6_UDP) { LOG_error("Invalid address protocol: %d", (i32) (u32) address.protocol); return -1; } Network_Address local_address = address; local_address.port = 0; if (!network_open(slot, local_address, local_port)) return -1; if (size <= 0) return 0; struct sockaddr *p; i32 p_len; struct sockaddr_in a4 = {0}; struct sockaddr_in6 a6 = {0}; if (address.protocol == IPv4_UDP) { p = (struct sockaddr *) &a4; p_len = sizeof a4; a4.sin_family = AF_INET; a4.sin_port = htons(address.port); a4.sin_addr.s_addr = address.v4_address_as_u32; } else { p = (struct sockaddr *) &a6; p_len = sizeof a6; a6.sin6_family = AF_INET6; a6.sin6_port = htons(address.port); mem_cpy_(a6.sin6_addr.s6_addr, address.v6_address, sizeof a6.sin6_addr.s6_addr); } i64 sent = sendto( _sockets[0].socket, data, size, MSG_DONTWAIT, p, p_len ); if (sent < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; LOG_error("sendto failed (errno %d).", errno); return -1; } return sent; } #endif // defined(__unix__) // ================================================================ // // ALSA // // ================================================================ #if defined(__linux__) && ENABLE_ALSA #include static b8 _sound_ready = 0; static snd_pcm_t *_sound_out = NULL; static void sound_init_(void) { if (_sound_ready) return; i32 s; s = snd_pcm_open(&_sound_out, "default", SND_PCM_STREAM_PLAYBACK, 0); if (s < 0) { LOG_error("snd_pcm_open failed: %s", snd_strerror(s)); return; } snd_pcm_hw_params_t *hw_params; snd_pcm_sw_params_t *sw_params; snd_pcm_hw_params_alloca(&hw_params); s = snd_pcm_hw_params_any(_sound_out, hw_params); if (s < 0) LOG_error("snd_pcm_hw_params_any failed: %s", snd_strerror(s)); s = snd_pcm_hw_params_set_access(_sound_out, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (s < 0) LOG_error("snd_pcm_hw_params_set_access failed: %s", snd_strerror(s)); s = snd_pcm_hw_params_set_format(_sound_out, hw_params, SND_PCM_FORMAT_FLOAT_LE); if (s < 0) LOG_error("snd_pcm_hw_params_set_format failed: %s", snd_strerror(s)); s = snd_pcm_hw_params_set_rate(_sound_out, hw_params, PRIMARY_SOUND_SAMPLE_RATE, 0); if (s < 0) LOG_error("snd_pcm_hw_params_set_rate failed: %s", snd_strerror(s)); s = snd_pcm_hw_params_set_channels(_sound_out, hw_params, NUM_PRIMARY_SOUND_CHANNELS); if (s < 0) LOG_error("snd_pcm_hw_params_set_channels failed: %s", snd_strerror(s)); s = snd_pcm_hw_params(_sound_out, hw_params); if (s < 0) LOG_error("snd_pcm_hw_params failed: %s", snd_strerror(s)); snd_pcm_sw_params_alloca(&sw_params); s = snd_pcm_sw_params_current(_sound_out, sw_params); if (s < 0) LOG_error("snd_pcm_sw_params_current failed: %s", snd_strerror(s)); s = snd_pcm_sw_params_set_avail_min(_sound_out, sw_params, PRIMARY_SOUND_AVAIL_MIN); if (s < 0) LOG_error("snd_pcm_sw_params_set_avail_min failed: %s", snd_strerror(s)); s = snd_pcm_sw_params(_sound_out, sw_params); if (s < 0) LOG_error("snd_pcm_sw_params failed: %s", snd_strerror(s)); s = snd_pcm_prepare(_sound_out); if (s < 0) LOG_error("snd_pcm_prepare failed: %s", snd_strerror(s)); _sound_ready = 1; } static void sound_cleanup_(void) { if (!_sound_ready) return; i32 s; s = snd_pcm_nonblock(_sound_out, 0); if (s < 0) LOG_error("snd_pcm_nonblock failed: %s", snd_strerror(s)); s = snd_pcm_drain(_sound_out); if (s < 0) LOG_error("snd_pcm_drain failed: %s", snd_strerror(s)); // FIXME: Memory leaks, seems to be an ALSA bug. // snd_pcm_close(_sound_out); // snd_config_update_free_global(); _sound_ready = 0; } void handle_primary_sound(void) { sound_init_(); g_platform.num_sound_samples_elapsed = sound_samples_elapsed_(); i64 num_frames = g_platform.num_sound_samples_elapsed * NUM_PRIMARY_SOUND_CHANNELS; if (num_frames > MAX_NUM_PRIMARY_SOUND_FRAMES) { LOG_error("Sound buffer overflow."); num_frames %= MAX_NUM_PRIMARY_SOUND_FRAMES; } i32 s; if (num_frames <= MAX_NUM_PRIMARY_SOUND_FRAMES - _sound_position) { s = snd_pcm_writei(_sound_out, _sound_ring + _sound_position, num_frames / NUM_PRIMARY_SOUND_CHANNELS); if (s < 0) LOG_error("snd_pcm_writei failed: %s", snd_strerror(s)); mem_set_(_sound_ring + _sound_position, 0, num_frames * sizeof *_sound_ring); } else { i64 part_one = MAX_NUM_PRIMARY_SOUND_FRAMES - _sound_position; i64 part_two = num_frames - part_one; s = snd_pcm_writei(_sound_out, _sound_ring + _sound_position, part_one / NUM_PRIMARY_SOUND_CHANNELS); if (s < 0) LOG_error("snd_pcm_writei failed: %s", snd_strerror(s)); s = snd_pcm_writei(_sound_out, _sound_ring, part_two / NUM_PRIMARY_SOUND_CHANNELS); if (s < 0) LOG_error("snd_pcm_writei failed: %s", snd_strerror(s)); mem_set_(_sound_ring + _sound_position, 0, part_one * sizeof *_sound_ring); mem_set_(_sound_ring, 0, part_two * sizeof *_sound_ring); } _sound_position = (_sound_position + num_frames) % MAX_NUM_PRIMARY_SOUND_FRAMES; } void queue_primary_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { if (num_samples < 0) LOG_error("Invalid num samples %lld.", num_samples); if (frames == NULL) return; if (delay_in_samples < 0) { frames += -delay_in_samples * NUM_PRIMARY_SOUND_CHANNELS; num_samples -= delay_in_samples; delay_in_samples = 0; } if (num_samples <= 0) return; i64 num_frames = num_samples * NUM_PRIMARY_SOUND_CHANNELS; if (num_frames > MAX_NUM_PRIMARY_SOUND_FRAMES) { LOG_error("Sound buffer overflow."); return; } sound_init_(); i64 begin = (_sound_position + delay_in_samples) % MAX_NUM_PRIMARY_SOUND_FRAMES; if (num_frames <= MAX_NUM_PRIMARY_SOUND_FRAMES - begin) for (i64 i = 0; i < num_frames; ++i) _sound_ring[begin + i] += frames[i]; else { i64 part_one = MAX_NUM_PRIMARY_SOUND_FRAMES - begin; i64 part_two = num_frames - part_one; for (i64 i = 0; i < part_one; ++i) _sound_ring[begin + i] += frames[i]; for (i64 i = 0; i < part_two; ++i) _sound_ring[i] += frames[part_one + i]; } } #endif // defined(__linux__) && ENABLE_ALSA // ================================================================ // // Wayland // // FIXME: Remove dynamic memory management. // Use arena allocator for a static buffer. // // ================================================================ #if defined(__linux__) && ENABLE_WAYLAND #include #include #include struct zxdg_output_v1_listener { void (*logical_position) (void *data, void *zxdg_output_v1, i32 x, i32 y); void (*logical_size) (void *data, void *zxdg_output_v1, i32 width, i32 height); void (*done) (void *data, void *zxdg_output_v1); void (*name) (void *data, void *zxdg_output_v1, c8 *name); void (*description) (void *data, void *zxdg_output_v1, c8 *description); }; struct zwlr_screencopy_frame_v1_listener { void (*buffer) (void *data, void *zwlr_screencopy_frame_v1, u32 format, u32 width, u32 height, u32 stride); void (*flags) (void *data, void *zwlr_screencopy_frame_v1, u32 flags); void (*ready) (void *data, void *zwlr_screencopy_frame_v1, u32 tv_sec_hi, u32 tv_sec_lo, u32 tv_nsec); void (*failed) (void *data, void *zwlr_screencopy_frame_v1); }; extern struct wl_interface zwlr_screencopy_frame_v1_interface; extern struct wl_interface zxdg_output_v1_interface; static struct wl_interface const *wlr_screencopy_unstable_v1_types[] = { NULL, NULL, NULL, NULL, &zwlr_screencopy_frame_v1_interface, NULL, &wl_output_interface, &zwlr_screencopy_frame_v1_interface, NULL, &wl_output_interface, NULL, NULL, NULL, NULL, &wl_buffer_interface, }; static struct wl_message zwlr_screencopy_frame_v1_requests[] = { { "copy", "o", wlr_screencopy_unstable_v1_types + 14 }, { "destroy", "", wlr_screencopy_unstable_v1_types }, }; static struct wl_message zwlr_screencopy_frame_v1_events[] = { { "buffer", "uuuu", wlr_screencopy_unstable_v1_types }, { "flags", "u", wlr_screencopy_unstable_v1_types }, { "ready", "uuu", wlr_screencopy_unstable_v1_types }, { "failed", "", wlr_screencopy_unstable_v1_types }, }; struct wl_interface zwlr_screencopy_frame_v1_interface = { "zwlr_screencopy_frame_v1", 1, 2, zwlr_screencopy_frame_v1_requests, 4, zwlr_screencopy_frame_v1_events, }; static struct wl_message zwlr_screencopy_manager_v1_requests[] = { { "capture_output", "nio", wlr_screencopy_unstable_v1_types + 4 }, { "capture_output_region", "nioiiii", wlr_screencopy_unstable_v1_types + 7 }, { "destroy", "", wlr_screencopy_unstable_v1_types }, }; static struct wl_interface const *xdg_output_unstable_v1_types[] = { NULL, NULL, &zxdg_output_v1_interface, &wl_output_interface, }; static struct wl_message zxdg_output_manager_v1_requests[] = { { "destroy", "", xdg_output_unstable_v1_types }, { "get_xdg_output", "no", xdg_output_unstable_v1_types + 2 }, }; static struct wl_interface zxdg_output_manager_v1_interface = { "zxdg_output_manager_v1", 3, 2, zxdg_output_manager_v1_requests, 0, NULL, }; static struct wl_message zxdg_output_v1_requests[] = { { "destroy", "", xdg_output_unstable_v1_types }, }; static struct wl_message zxdg_output_v1_events[] = { { "logical_position", "ii", xdg_output_unstable_v1_types }, { "logical_size", "ii", xdg_output_unstable_v1_types }, { "done", "", xdg_output_unstable_v1_types }, { "name", "2s", xdg_output_unstable_v1_types }, { "description", "2s", xdg_output_unstable_v1_types }, }; struct wl_interface zwlr_screencopy_manager_v1_interface = { "zwlr_screencopy_manager_v1", 1, 3, zwlr_screencopy_manager_v1_requests, 0, NULL, }; struct wl_interface zxdg_output_v1_interface = { "zxdg_output_v1", 3, 1, zxdg_output_v1_requests, 5, zxdg_output_v1_events, }; static void *zwlr_screencopy_manager_v1_capture_output(void *zwlr_screencopy_manager_v1, i32 overlay_cursor, struct wl_output *output) { struct wl_proxy *frame; frame = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_screencopy_manager_v1, 0, &zwlr_screencopy_frame_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_manager_v1), 0, NULL, overlay_cursor, output); return (void *) frame; } static void zxdg_output_v1_destroy(void *zxdg_output_v1) { wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_v1, 0, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1), WL_MARSHAL_FLAG_DESTROY); } static void zxdg_output_manager_v1_destroy(void *zxdg_output_manager_v1) { wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1, 0, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), WL_MARSHAL_FLAG_DESTROY); } static void zwlr_screencopy_frame_v1_destroy(void *zwlr_screencopy_frame_v1) { wl_proxy_marshal_flags((struct wl_proxy *) zwlr_screencopy_frame_v1, 1, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_frame_v1), WL_MARSHAL_FLAG_DESTROY); } static void zwlr_screencopy_frame_v1_copy(void *zwlr_screencopy_frame_v1, struct wl_buffer *buffer) { wl_proxy_marshal_flags((struct wl_proxy *) zwlr_screencopy_frame_v1, 0, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_frame_v1), 0, buffer); } static i32 zwlr_screencopy_frame_v1_add_listener(void *zwlr_screencopy_frame_v1, struct zwlr_screencopy_frame_v1_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) zwlr_screencopy_frame_v1, (void (**)(void)) listener, data); } static void zwlr_screencopy_manager_v1_destroy(void *zwlr_screencopy_manager_v1) { wl_proxy_marshal_flags((struct wl_proxy *) zwlr_screencopy_manager_v1, 2, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_screencopy_manager_v1), WL_MARSHAL_FLAG_DESTROY); } // ================================================================ typedef struct { struct wl_display * display; struct wl_registry *registry; struct wl_shm * shm; void * xdg_output_manager; void * screencopy_manager; struct wl_list outputs; u64 n_done; b8 ok; } WL_State_; typedef struct { struct wl_buffer *buffer; void * data; i32 width; i32 height; i32 stride; u64 size; i32 format; } WL_Buffer_; typedef struct { i32 x, y; i32 width, height; } WL_Box_; typedef struct { WL_State_ * state; struct wl_output *output; void * xdg_output; struct wl_list link; c8 * name; WL_Buffer_ * buffer; void * screencopy_frame; u32 screencopy_frame_flags; } WL_Output_; static i32 anonymous_shm_open(void) { c8 name[13] = "scr_XXXXXX"; i32 retries = 1000000; for (i32 i = 0; i < retries; ++i) { snprintf(name + 4, 7, "%06d", i); i32 fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) { shm_unlink(name); return fd; } if (errno != EEXIST) break; } LOG_error("shm_open failed.\n"); return -1; } static i32 create_shm_file(off_t size) { int fd = anonymous_shm_open(); if (fd < 0) { return fd; } if (ftruncate(fd, size) < 0) { close(fd); LOG_error("ftruncate failed.\n"); return -1; } return fd; } static WL_Buffer_ *create_buffer(struct wl_shm *shm, enum wl_shm_format format, i32 width, i32 height, i32 stride) { u64 size = stride * height; i32 fd = create_shm_file(size); if (fd == -1) { return NULL; } void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (data == MAP_FAILED) { close(fd); LOG_error("mmap failed.\n"); return NULL; } struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); struct wl_buffer * wl_buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); wl_shm_pool_destroy(pool); close(fd); WL_Buffer_ *buffer = calloc(1, sizeof(WL_Buffer_)); if (buffer == NULL) { LOG_error("calloc failed.\n"); return NULL; } *buffer = (WL_Buffer_) { .buffer = wl_buffer, .data = data, .width = width, .height = height, .stride = stride, .size = size, .format = format, }; return buffer; } static void destroy_buffer(WL_Buffer_ *buffer) { if (buffer == NULL) return; munmap(buffer->data, buffer->size); wl_buffer_destroy(buffer->buffer); free(buffer); } static void screencopy_frame_handle_buffer( void *data, void *frame, u32 format, u32 width, u32 height, u32 stride ) { WL_Output_ *output = data; output->buffer = create_buffer(output->state->shm, format, width, height, stride); if (output->buffer == NULL) { output->state->ok = 0; return; } zwlr_screencopy_frame_v1_copy(frame, output->buffer->buffer); } static void screencopy_frame_handle_flags( void *data, void *frame, u32 flags ) { (void) frame; WL_Output_ *output = data; output->screencopy_frame_flags = flags; } static void screencopy_frame_handle_ready( void *data, void *frame, u32 tv_sec_hi, u32 tv_sec_lo, u32 tv_nsec ) { (void) frame; (void) tv_sec_hi; (void) tv_sec_lo; (void) tv_nsec; WL_Output_ *output = data; ++output->state->n_done; } static void screencopy_frame_handle_failed( void *data, void *frame ) { (void) frame; WL_Output_ *output = data; output->state->ok = 0; LOG_error("Screenshot copy failed."); } static struct zwlr_screencopy_frame_v1_listener screencopy_frame_listener = { .buffer = screencopy_frame_handle_buffer, .flags = screencopy_frame_handle_flags, .ready = screencopy_frame_handle_ready, .failed = screencopy_frame_handle_failed, }; static void handle_global( void * data, struct wl_registry *registry, u32 name, c8 const * interface, u32 version ) { WL_State_ *state = data; if (strcmp(interface, wl_shm_interface.name) == 0) state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { u32 bind_version = (version > 2) ? 2 : version; state->xdg_output_manager = wl_registry_bind( registry, name, &zxdg_output_manager_v1_interface, bind_version ); } else if (strcmp(interface, wl_output_interface.name) == 0) { WL_Output_ *output = calloc(1, sizeof(WL_Output_)); output->state = state; output->output = wl_registry_bind(registry, name, &wl_output_interface, 3); wl_list_insert(&state->outputs, &output->link); } else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) state->screencopy_manager = wl_registry_bind( registry, name, &zwlr_screencopy_manager_v1_interface, 1 ); } static void handle_global_remove(void *data, struct wl_registry *registry, u32 name) { (void) data; (void) registry; (void) name; } static struct wl_registry_listener _wayland_registry_listener = { .global = handle_global, .global_remove = handle_global_remove, }; b8 check_format_(i32 format) { switch (format) { case WL_SHM_FORMAT_ARGB8888: case WL_SHM_FORMAT_XRGB8888: case WL_SHM_FORMAT_ABGR8888: case WL_SHM_FORMAT_XBGR8888: case WL_SHM_FORMAT_BGRA8888: case WL_SHM_FORMAT_BGRX8888: case WL_SHM_FORMAT_RGBA8888: case WL_SHM_FORMAT_RGBX8888: return 1; default:; } return 0; } b8 wayland_screenshot_(i64 *width, i64 *height, i64 max_num_pixels, vec4_f32 *pixels) { b8 ok = 0; WL_State_ state = { .ok = 1, }; wl_list_init(&state.outputs); state.display = wl_display_connect(NULL); if (state.display == NULL) { LOG_error("wl_display_connect failed."); goto _finalize; } state.registry = wl_display_get_registry(state.display); wl_registry_add_listener( state.registry, &_wayland_registry_listener, &state ); wl_display_roundtrip(state.display); if (state.shm == NULL) { LOG_error("Compositor does not support wl_shm."); goto _finalize; } if (wl_list_empty(&state.outputs)) { LOG_error("No wl_output."); goto _finalize; } if (state.screencopy_manager == NULL) { LOG_error("Compositor does not support wlr-screencopy-unstable-v1."); goto _finalize; } u64 n_pending = 0; WL_Output_ *output; wl_list_for_each(output, &state.outputs, link) { output->screencopy_frame = zwlr_screencopy_manager_v1_capture_output( state.screencopy_manager, 1, output->output ); zwlr_screencopy_frame_v1_add_listener( output->screencopy_frame, &screencopy_frame_listener, output ); ++n_pending; } if (n_pending == 0) { LOG_error("Supplied geometry did not intersect with any outputs."); goto _finalize; } b8 done = 0; while (!done && wl_display_dispatch(state.display) != -1) { if (!state.ok) goto _finalize; usleep(0); done = (state.n_done == n_pending); } if (!done) { LOG_error("Failed to screenshoot all outputs."); goto _finalize; } *width = 0; *height = 0; // NOTE: We grab only one output. // TODO: Allow to choose the desired output. wl_list_for_each(output, &state.outputs, link) { WL_Buffer_ *buffer = output->buffer; if (buffer == NULL) continue; if (!check_format_(buffer->format)) continue; *width = buffer->width; *height = buffer->height; break; } if (pixels == NULL || *width * *height > max_num_pixels) { ok = 1; goto _finalize; } for (i32 i = 0; i < *width * *height; ++i) pixels[i] = (vec4_f32) { 0.f, 0.f, 0.f, 1.f }; wl_list_for_each(output, &state.outputs, link) { WL_Buffer_ *buffer = output->buffer; if (buffer == NULL) continue; if (!check_format_(buffer->format)) continue; if (*width != buffer->width) { LOG_error("Sanity"); break; } for (i32 j = 0; j < buffer->height; ++j) { vec4_f32 *d = pixels + j * *width; vec4_f32 *d_end = d + *width; u32 *s = ((u32 *) buffer->data) + j * (buffer->stride / 4); for (; d < d_end; ++d, ++s) { vec3_f32 c = rgb_f32_from_u32_(*s); *d = (vec4_f32) { .x = c.x, .y = c.y, .z = c.z, .w = 1.f, }; } } break; } ok = 1; WL_Output_ *output_tmp; _finalize: wl_list_for_each_safe(output, output_tmp, &state.outputs, link) { wl_list_remove(&output->link); free(output->name); if (output->screencopy_frame != NULL) zwlr_screencopy_frame_v1_destroy(output->screencopy_frame); destroy_buffer(output->buffer); if (output->xdg_output != NULL) zxdg_output_v1_destroy(output->xdg_output); wl_output_release(output->output); free(output); } zwlr_screencopy_manager_v1_destroy(state.screencopy_manager); if (state.xdg_output_manager != NULL) zxdg_output_manager_v1_destroy(state.xdg_output_manager); if (state.shm != NULL) wl_shm_destroy(state.shm); if (state.registry != NULL) wl_registry_destroy(state.registry); if (state.display != NULL) wl_display_disconnect(state.display); return ok; } #endif // defined(__linux__) && ENABLE_WAYLAND // ================================================================ // // X11 // // ================================================================ #if defined(__linux__) && ENABLE_X11 #include #include #include #include #define FILE_PATH_PREFIX_ "file://" #define FILE_PATH_PREFIX_LEN_ ((sizeof FILE_PATH_PREFIX_) - 1) static i64 _frame_time = 0; static XImage _image = {0}; static Display *_display = NULL; static Window _root_window = 0; static GC _gc = NULL; static XIM _im = NULL; static XIC _ic = NULL; static Window _window = 0; static Window _drop_source = 0; static Atom _wm_delete_window = 0; static Atom _clipboard = 0; static Atom _targets = 0; static Atom _text_plain = 0; static Atom _utf8_string = 0; static Atom _image_bmp = 0; static Atom _image_ppm = 0; static Atom _audio_wav = 0; static Atom _dnd_aware = 0; static Atom _dnd_enter = 0; static Atom _dnd_position = 0; static Atom _dnd_status = 0; static Atom _dnd_leave = 0; static Atom _dnd_drop = 0; static Atom _dnd_finished = 0; static Atom _dnd_action_copy = 0; static Atom _dnd_selection = 0; static Atom _text_uri_list = 0; static Window _dnd_source = 0; static b8 _mapped = 0; static b8 _requested_clipboard = 0; static i16 _key_table [MAX_NUM_KEYS] = {0}; static b8 _key_repeat [MAX_NUM_KEYS] = {0}; static c8 _error_buffer [4096] = {0}; static b8 sub_str_eq_(c8 *a, c8 *b, i64 len) { for (i64 i = 0; i < len; ++i) if (a[i] != b[i]) return 0; return 1; } static i32 x11_error_handler_(Display *display, XErrorEvent *event) { XGetErrorText(display, event->error_code, _error_buffer, sizeof _error_buffer - 1); LOG_error("%s", _error_buffer); return 0; } void put_image_to_main_window_(void) { if (_image.width <= 0 || _image.height <= 0) return; XPutImage(_display, _window, _gc, &_image, 0, 0, 0, 0, _image.width, _image.height); XFlush(_display); } static void init_profiler_slots(void) { PROFILER_init(PROFILE_MEMORY, "Memory allocator"); PROFILER_init(PROFILE_DOWNSCALE, "Downscale anti-aliasing"); PROFILER_init(PROFILE_RESIZE, "Resize in-place"); PROFILER_init(PROFILE_WAITING, "Waiting for events"); PROFILER_init(PROFILE_FRAME, "Update frame"); PROFILER_init(PROFILE_DRAW_PIXELS, "Draw pixels"); PROFILER_init(PROFILE_FILL_RECTANGLE, "Fill rectangle"); PROFILER_init(PROFILE_FILL_TRIANGLE, "Fill triangle"); PROFILER_init(PROFILE_FILL_TRIANGLES, "Fill triangles"); PROFILER_init(PROFILE_FILL_QUAD, "Fill quad"); PROFILER_init(PROFILE_FILL_ELLIPSE, "Fill ellipse"); PROFILER_init(PROFILE_FILL_LINE, "Fill line"); PROFILER_init(PROFILE_DRAW_TEXT_AREA, "Draw text area"); PROFILER_init(PROFILE_DRAW_TEXT_CURSOR, "Draw text cursor"); } void init_main_window(void) { init_profiler_slots(); for (i64 i = 0; i < NUM_FRAMES_AVERAGED; ++i) _frame_duration[i] = (MIN_FRAME_DURATION + MAX_FRAME_DURATION) / 2; XSetErrorHandler(x11_error_handler_); _display = XOpenDisplay(NULL); if (_display == NULL) { LOG_error("XOpenDisplay failed."); return; } _root_window = XDefaultRootWindow(_display); _key_table[XKeysymToKeycode(_display, XK_Left)] = KEY_LEFT; _key_table[XKeysymToKeycode(_display, XK_Right)] = KEY_RIGHT; _key_table[XKeysymToKeycode(_display, XK_Up)] = KEY_UP; _key_table[XKeysymToKeycode(_display, XK_Down)] = KEY_DOWN; _key_table[XKeysymToKeycode(_display, XK_1)] = KEY_1; _key_table[XKeysymToKeycode(_display, XK_2)] = KEY_2; _key_table[XKeysymToKeycode(_display, XK_3)] = KEY_3; _key_table[XKeysymToKeycode(_display, XK_4)] = KEY_4; _key_table[XKeysymToKeycode(_display, XK_5)] = KEY_5; _key_table[XKeysymToKeycode(_display, XK_6)] = KEY_6; _key_table[XKeysymToKeycode(_display, XK_7)] = KEY_7; _key_table[XKeysymToKeycode(_display, XK_8)] = KEY_8; _key_table[XKeysymToKeycode(_display, XK_9)] = KEY_9; _key_table[XKeysymToKeycode(_display, XK_0)] = KEY_0; _key_table[XKeysymToKeycode(_display, XK_A)] = KEY_A; _key_table[XKeysymToKeycode(_display, XK_B)] = KEY_B; _key_table[XKeysymToKeycode(_display, XK_C)] = KEY_C; _key_table[XKeysymToKeycode(_display, XK_D)] = KEY_D; _key_table[XKeysymToKeycode(_display, XK_E)] = KEY_E; _key_table[XKeysymToKeycode(_display, XK_F)] = KEY_F; _key_table[XKeysymToKeycode(_display, XK_G)] = KEY_G; _key_table[XKeysymToKeycode(_display, XK_H)] = KEY_H; _key_table[XKeysymToKeycode(_display, XK_I)] = KEY_I; _key_table[XKeysymToKeycode(_display, XK_J)] = KEY_J; _key_table[XKeysymToKeycode(_display, XK_K)] = KEY_K; _key_table[XKeysymToKeycode(_display, XK_L)] = KEY_L; _key_table[XKeysymToKeycode(_display, XK_M)] = KEY_M; _key_table[XKeysymToKeycode(_display, XK_N)] = KEY_N; _key_table[XKeysymToKeycode(_display, XK_O)] = KEY_O; _key_table[XKeysymToKeycode(_display, XK_P)] = KEY_P; _key_table[XKeysymToKeycode(_display, XK_Q)] = KEY_Q; _key_table[XKeysymToKeycode(_display, XK_R)] = KEY_R; _key_table[XKeysymToKeycode(_display, XK_S)] = KEY_S; _key_table[XKeysymToKeycode(_display, XK_T)] = KEY_T; _key_table[XKeysymToKeycode(_display, XK_U)] = KEY_U; _key_table[XKeysymToKeycode(_display, XK_V)] = KEY_V; _key_table[XKeysymToKeycode(_display, XK_W)] = KEY_W; _key_table[XKeysymToKeycode(_display, XK_X)] = KEY_X; _key_table[XKeysymToKeycode(_display, XK_Y)] = KEY_Y; _key_table[XKeysymToKeycode(_display, XK_Z)] = KEY_Z; _key_table[XKeysymToKeycode(_display, XK_space)] = KEY_SPACE; _key_table[XKeysymToKeycode(_display, XK_braceleft)] = KEY_LBRACE; _key_table[XKeysymToKeycode(_display, XK_braceright)] = KEY_RBRACE; _key_table[XKeysymToKeycode(_display, XK_colon)] = KEY_COLON; _key_table[XKeysymToKeycode(_display, XK_quotedbl)] = KEY_QUOTE; _key_table[XKeysymToKeycode(_display, XK_asciitilde)] = KEY_TILDE; _key_table[XKeysymToKeycode(_display, XK_backslash)] = KEY_BACKSLASH; _key_table[XKeysymToKeycode(_display, XK_comma)] = KEY_COMMA; _key_table[XKeysymToKeycode(_display, XK_greater)] = KEY_PERIOD; _key_table[XKeysymToKeycode(_display, XK_question)] = KEY_SLASH; _key_table[XKeysymToKeycode(_display, XK_minus)] = KEY_MINUS; _key_table[XKeysymToKeycode(_display, XK_equal)] = KEY_EQUAL; _key_table[XKeysymToKeycode(_display, XK_F1)] = KEY_F1; _key_table[XKeysymToKeycode(_display, XK_F2)] = KEY_F2; _key_table[XKeysymToKeycode(_display, XK_F3)] = KEY_F3; _key_table[XKeysymToKeycode(_display, XK_F4)] = KEY_F4; _key_table[XKeysymToKeycode(_display, XK_F5)] = KEY_F5; _key_table[XKeysymToKeycode(_display, XK_F6)] = KEY_F6; _key_table[XKeysymToKeycode(_display, XK_F7)] = KEY_F7; _key_table[XKeysymToKeycode(_display, XK_F8)] = KEY_F8; _key_table[XKeysymToKeycode(_display, XK_F9)] = KEY_F9; _key_table[XKeysymToKeycode(_display, XK_F10)] = KEY_F10; _key_table[XKeysymToKeycode(_display, XK_F11)] = KEY_F11; _key_table[XKeysymToKeycode(_display, XK_F12)] = KEY_F12; _key_table[XKeysymToKeycode(_display, XK_Control_L)] = KEY_LCTRL; _key_table[XKeysymToKeycode(_display, XK_Control_R)] = KEY_RCTRL; _key_table[XKeysymToKeycode(_display, XK_Shift_L)] = KEY_LSHIFT; _key_table[XKeysymToKeycode(_display, XK_Shift_R)] = KEY_RSHIFT; _key_table[XKeysymToKeycode(_display, XK_Alt_L)] = KEY_LALT; _key_table[XKeysymToKeycode(_display, XK_Alt_R)] = KEY_RALT; _key_table[XKeysymToKeycode(_display, XK_Meta_L)] = KEY_LMETA; _key_table[XKeysymToKeycode(_display, XK_Meta_R)] = KEY_RMETA; _key_table[XKeysymToKeycode(_display, XK_Escape)] = KEY_ESCAPE; _key_table[XKeysymToKeycode(_display, XK_BackSpace)] = KEY_BACKSPACE; _key_table[XKeysymToKeycode(_display, XK_Tab)] = KEY_TAB; _key_table[XKeysymToKeycode(_display, XK_Return)] = KEY_ENTER; _key_table[XKeysymToKeycode(_display, XK_Print)] = KEY_PRINTSCREEN; _key_table[XKeysymToKeycode(_display, XK_Delete)] = KEY_DELETE; _key_table[XKeysymToKeycode(_display, XK_Pause)] = KEY_PAUSE; _key_table[XKeysymToKeycode(_display, XK_Insert)] = KEY_INSERT; _key_table[XKeysymToKeycode(_display, XK_Home)] = KEY_HOME; _key_table[XKeysymToKeycode(_display, XK_End)] = KEY_END; _key_table[XKeysymToKeycode(_display, XK_Page_Up)] = KEY_PAGEUP; _key_table[XKeysymToKeycode(_display, XK_Page_Down)] = KEY_PAGEDOWN; _key_table[XKeysymToKeycode(_display, XK_KP_0)] = KEY_KP_0; _key_table[XKeysymToKeycode(_display, XK_KP_1)] = KEY_KP_1; _key_table[XKeysymToKeycode(_display, XK_KP_2)] = KEY_KP_2; _key_table[XKeysymToKeycode(_display, XK_KP_3)] = KEY_KP_3; _key_table[XKeysymToKeycode(_display, XK_KP_4)] = KEY_KP_4; _key_table[XKeysymToKeycode(_display, XK_KP_5)] = KEY_KP_5; _key_table[XKeysymToKeycode(_display, XK_KP_6)] = KEY_KP_6; _key_table[XKeysymToKeycode(_display, XK_KP_7)] = KEY_KP_7; _key_table[XKeysymToKeycode(_display, XK_KP_8)] = KEY_KP_8; _key_table[XKeysymToKeycode(_display, XK_KP_9)] = KEY_KP_9; _key_table[XKeysymToKeycode(_display, XK_KP_Enter)] = KEY_KP_ENTER; _key_table[XKeysymToKeycode(_display, XK_KP_Divide)] = KEY_KP_DIVIDE; _key_table[XKeysymToKeycode(_display, XK_KP_Multiply)] = KEY_KP_MULTIPLY; _key_table[XKeysymToKeycode(_display, XK_KP_Add)] = KEY_KP_ADD; _key_table[XKeysymToKeycode(_display, XK_KP_Subtract)] = KEY_KP_SUBTRACT; _key_table[XKeysymToKeycode(_display, XK_KP_Decimal)] = KEY_KP_DECIMAL; _key_table[XKeysymToKeycode(_display, XK_KP_Separator)] = KEY_KP_SEPARATOR; i32 screen = DefaultScreen(_display); i32 depth = DefaultDepth (_display, screen); Visual *visual = DefaultVisual(_display, screen); _gc = DefaultGC(_display, screen); if (_gc == NULL) { LOG_error("DefaultGC failed."); return; } XSetGraphicsExposures(_display, _gc, False); i32 display_width = DisplayWidth (_display, screen); i32 display_height = DisplayHeight(_display, screen); if (g_platform.frame_width <= 0 || g_platform.frame_height <= 0) { if (display_width >= 1680 && display_height >= 1020) { g_platform.frame_width = 1280; g_platform.frame_height = 720; } else if (display_width >= 800 && display_height >= 600) { g_platform.frame_width = display_width - 400; g_platform.frame_height = display_height - 300; } else if (display_width >= 400 && display_height >= 300) { g_platform.frame_width = 400; g_platform.frame_height = 300; } else { g_platform.frame_width = display_width; g_platform.frame_height = display_height; } } i32 x = (display_width - g_platform.frame_width) / 2; i32 y = (display_height - g_platform.frame_height) / 2; _window = XCreateWindow(_display, _root_window, x, y, g_platform.frame_width, g_platform.frame_height, 0, depth, InputOutput, visual, CWEventMask, &(XSetWindowAttributes) { .event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | VisibilityChangeMask | FocusChangeMask | StructureNotifyMask | SubstructureNotifyMask, }); if (_window == 0) { LOG_error("XCreateWindow failed."); return; } g_platform.window_handle_x11 = _window; _im = XOpenIM(_display, NULL, NULL, NULL); if (_im == NULL) { LOG_error("XOpenIM failed."); return; } _ic = XCreateIC(_im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, _window, NULL); if (_ic == NULL) { LOG_error("XCreateIC failed."); return; } pixel_size_update_(g_platform.frame_width, g_platform.frame_height); _image = (XImage) { .width = _internal_width, .height = _internal_height, .depth = depth, .format = ZPixmap, .data = (c8 *) _internal_pixels, .byte_order = LSBFirst, .bitmap_unit = 32, .bitmap_bit_order = LSBFirst, .bitmap_pad = 32, .bits_per_pixel = 32, .bytes_per_line = _image.width * 4, .red_mask = 0xff0000, .green_mask = 0x00ff00, .blue_mask = 0x0000ff, }; XInitImage(&_image); _wm_delete_window = XInternAtom(_display, "WM_DELETE_WINDOW", False); _clipboard = XInternAtom(_display, "CLIPBOARD", False); _targets = XInternAtom(_display, "TARGETS", False); _text_plain = XInternAtom(_display, "text/plain", False); _utf8_string = XInternAtom(_display, "UTF8_STRING", False); _image_bmp = XInternAtom(_display, "image/bmp", False); _image_ppm = XInternAtom(_display, "image/ppm", False); _audio_wav = XInternAtom(_display, "audio/wav", False); XSetICFocus(_ic); XSetWMProtocols(_display, _window, &_wm_delete_window, 1); if (g_platform.title != NULL) XStoreName(_display, _window, g_platform.title); _dnd_aware = XInternAtom(_display, "XdndAware", False); _dnd_enter = XInternAtom(_display, "XdndEnter", False); _dnd_position = XInternAtom(_display, "XdndPosition", False); _dnd_status = XInternAtom(_display, "XdndStatus", False); _dnd_leave = XInternAtom(_display, "XdndLeave", False); _dnd_drop = XInternAtom(_display, "XdndDrop", False); _dnd_finished = XInternAtom(_display, "XdndFinished", False); _dnd_action_copy = XInternAtom(_display, "XdndActionCopy", False); _dnd_selection = XInternAtom(_display, "XdndSelection", False); _text_uri_list = XInternAtom(_display, "text/uri-list", False); XChangeProperty(_display, _window, _dnd_aware, 4, 32, PropModeReplace, &(u8) {5}, 1); _mapped = 0; } void shutdown_all_systems(void) { PROFILER_report_(); g_platform.done = 1; g_platform.window_handle_x11 = 0; if (!g_platform.graceful_shutdown) return; if (_window != 0) XDestroyWindow(_display, _window); if (_display != NULL) XCloseDisplay (_display); _window = 0; _display = NULL; drop_files_clean_(); cleanup_pixel_buffers_(); network_cleanup_(); sound_cleanup_(); close_all_dynamic_libraries_(); g_platform.input_len = 0; resize_dynamic_array_exact(&g_platform.input_capacity, (void **) &g_platform.input, sizeof *g_platform.input, 0); resize_dynamic_array_exact(&g_platform.clipboard_text_len, (void **) &g_platform.clipboard_text, 1, 0); } void request_clipboard_(void) { if (_requested_clipboard) return; if (!g_platform.enable_clipboard_text && !g_platform.enable_clipboard_image && !g_platform.enable_clipboard_sound) return; XConvertSelection(_display, _clipboard, _targets, _clipboard, _window, CurrentTime); _requested_clipboard = 1; } i32 handle_main_window_events(void) { if (_display == NULL) return 0; _frame_time = current_utc_time_in_milliseconds(); i32 num_events = 0; if (!_mapped) // We have to make sure that the user won't be waiting for events indefinitely. ++num_events; if (g_platform.files_dropped) drop_files_clean_(); mem_set_(g_platform.key_pressed, 0, sizeof g_platform.key_pressed); mem_set_(_key_repeat, 0, sizeof _key_repeat); resize_dynamic_array_capacity(&g_platform.input_len, &g_platform.input_capacity, (void **) &g_platform.input, sizeof *g_platform.input, 0); g_platform.cursor_dx = 0; g_platform.cursor_dy = 0; g_platform.wheel_dx = 0; g_platform.wheel_dy = 0; XEvent ev; while (XEventsQueued(_display, QueuedAlready) > 0) { ++num_events; XNextEvent(_display, &ev); XFilterEvent(&ev, _window); switch (ev.type) { case DestroyNotify: g_platform.done = 1; break; case MotionNotify: g_platform.cursor_dx += ev.xmotion.x - g_platform.cursor_x; g_platform.cursor_dy += ev.xmotion.y - g_platform.cursor_y; g_platform.cursor_x = ev.xmotion.x; g_platform.cursor_y = ev.xmotion.y; break; case ButtonPress: g_platform.cursor_x = ev.xbutton.x; g_platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: g_platform.key_down [BUTTON_LEFT] = 1; g_platform.key_pressed[BUTTON_LEFT] = 1; break; case Button2: g_platform.key_down [BUTTON_MIDDLE] = 1; g_platform.key_pressed[BUTTON_MIDDLE] = 1; break; case Button3: g_platform.key_down [BUTTON_RIGHT] = 1; g_platform.key_pressed[BUTTON_RIGHT] = 1; break; case Button4: g_platform.wheel_dy += MOUSE_WHEEL_FACTOR; break; case Button5: g_platform.wheel_dy -= MOUSE_WHEEL_FACTOR; break; case Button5 + 1: g_platform.wheel_dx -= MOUSE_WHEEL_FACTOR; break; case Button5 + 2: g_platform.wheel_dx += MOUSE_WHEEL_FACTOR; break; default:; } break; case ButtonRelease: g_platform.cursor_x = ev.xbutton.x; g_platform.cursor_y = ev.xbutton.y; switch (ev.xbutton.button) { case Button1: g_platform.key_down[BUTTON_LEFT] = 0; break; case Button2: g_platform.key_down[BUTTON_MIDDLE] = 0; break; case Button3: g_platform.key_down[BUTTON_RIGHT] = 0; break; default:; } break; case KeyPress: { i16 k = _key_table[ev.xkey.keycode]; g_platform.cursor_x = ev.xkey.x; g_platform.cursor_y = ev.xkey.y; g_platform.key_down[k] = 1; if (!_key_repeat[k]) g_platform.key_pressed[k] = 1; g_platform.key_down[MOD_CTRL] = !!(ev.xkey.state & ControlMask); g_platform.key_down[MOD_SHIFT] = !!(ev.xkey.state & ShiftMask); g_platform.key_down[MOD_ALT] = !!(ev.xkey.state & Mod1Mask); g_platform.key_down[MOD_CAPS] = !!(ev.xkey.state & LockMask); g_platform.key_down[MOD_NUM] = !!(ev.xkey.state & Mod2Mask); g_platform.key_down[MOD_SCROLL] = !!(ev.xkey.state & Mod3Mask); g_platform.key_down[MOD_META] = !!(ev.xkey.state & Mod4Mask); i64 n = g_platform.input_len; resize_dynamic_array_capacity(&g_platform.input_len, &g_platform.input_capacity, (void **) &g_platform.input, sizeof *g_platform.input, n + 1); if (n < g_platform.input_len) { if (k < 32 || k >= 128) g_platform.input[n] = (Input_Key) { .ctrl = !!(ev.xkey.state & ControlMask), .shift = !!(ev.xkey.state & ShiftMask), .alt = !!(ev.xkey.state & Mod1Mask), .caps = !!(ev.xkey.state & LockMask), .num = !!(ev.xkey.state & Mod2Mask), .scroll = !!(ev.xkey.state & Mod3Mask), .meta = !!(ev.xkey.state & Mod4Mask), .repeat = _key_repeat[k], .key = k, .c = 0, }; else { c8 buf[16]; i32 len = Xutf8LookupString(_ic, &ev.xkey, buf, sizeof buf - 1, NULL, NULL); if (len > 0) g_platform.input[n] = (Input_Key) { .ctrl = !!(ev.xkey.state & ControlMask), .shift = !!(ev.xkey.state & ShiftMask), .alt = !!(ev.xkey.state & Mod1Mask), .caps = !!(ev.xkey.state & LockMask), .num = !!(ev.xkey.state & Mod2Mask), .scroll = !!(ev.xkey.state & Mod3Mask), .meta = !!(ev.xkey.state & Mod4Mask), .repeat = _key_repeat[k], .key = k, .c = utf8_read(len, buf).code, }; } } } break; case KeyRelease: { i16 k = _key_table[ev.xkey.keycode]; g_platform.cursor_x = ev.xkey.x; g_platform.cursor_y = ev.xkey.y; g_platform.key_down[k] = 0; _key_repeat[k] = 1; g_platform.key_down[MOD_CTRL] = !!(ev.xkey.state & ControlMask); g_platform.key_down[MOD_SHIFT] = !!(ev.xkey.state & ShiftMask); g_platform.key_down[MOD_ALT] = !!(ev.xkey.state & Mod1Mask); g_platform.key_down[MOD_CAPS] = !!(ev.xkey.state & LockMask); g_platform.key_down[MOD_NUM] = !!(ev.xkey.state & Mod2Mask); g_platform.key_down[MOD_SCROLL] = !!(ev.xkey.state & Mod3Mask); g_platform.key_down[MOD_META] = !!(ev.xkey.state & Mod4Mask); } break; case SelectionRequest: if (ev.xselectionrequest.requestor != _window && ev.xselectionrequest.selection == _clipboard && XGetSelectionOwner(_display, _clipboard) == _window) { if (ev.xselectionrequest.property != None) { if (ev.xselectionrequest.target == _targets) { b8 has_text = g_platform.clipboard_text_len > 0; b8 has_image = g_platform.clipboard_image_width > 0 && g_platform.clipboard_image_height > 0; b8 has_sound = g_platform.num_clipboard_sound_samples > 0; if (!has_text && !has_image && !has_sound) XDeleteProperty( ev.xselectionrequest.display, ev.xselectionrequest.requestor, ev.xselectionrequest.property ); else XChangeProperty( ev.xselectionrequest.display, ev.xselectionrequest.requestor, ev.xselectionrequest.property, XA_ATOM, 32, PropModeReplace, has_text && has_image && has_sound ? (u8 *) &(Atom[]) { XA_STRING, _text_plain, _utf8_string, _image_ppm, _image_bmp, _audio_wav, } : has_text && has_image ? (u8 *) &(Atom[]) { XA_STRING, _text_plain, _utf8_string, _image_ppm, _image_bmp, } : has_text && has_sound ? (u8 *) &(Atom[]) { XA_STRING, _text_plain, _utf8_string, _audio_wav, } : has_image && has_sound ? (u8 *) &(Atom[]) { _image_ppm, _image_bmp, _audio_wav, } : has_text ? (u8 *) &(Atom[]) { XA_STRING, _text_plain, _utf8_string, } : has_image ? (u8 *) &(Atom[]) { _image_ppm, _image_bmp, } : (u8 *) &(Atom[]) { _audio_wav, }, has_text * 3 + has_image * 2 + has_sound * 1 ); } else if (ev.xselectionrequest.target == XA_STRING || ev.xselectionrequest.target == _text_plain || ev.xselectionrequest.target == _utf8_string) { XChangeProperty( ev.xselectionrequest.display, ev.xselectionrequest.requestor, ev.xselectionrequest.property, ev.xselectionrequest.target, 8, PropModeReplace, (u8 *) g_platform.clipboard_text, g_platform.clipboard_text_len ); } else if (ev.xselectionrequest.target == _image_bmp) { // TODO LOG_error("Send BMP image - not implemented."); } else if (ev.xselectionrequest.target == _image_ppm) { // TODO LOG_error("Send PPM image - not implemented."); } else if (ev.xselectionrequest.target == _audio_wav) { // TODO LOG_error("Send WAV audio - not implemented."); } } XSendEvent(_display, ev.xselectionrequest.requestor, 0, 0, (XEvent *) &(XSelectionEvent) { .type = SelectionNotify, .serial = ev.xselectionrequest.serial, .send_event = ev.xselectionrequest.send_event, .display = ev.xselectionrequest.display, .requestor = ev.xselectionrequest.requestor, .selection = ev.xselectionrequest.selection, .target = ev.xselectionrequest.target, .property = ev.xselectionrequest.property, .time = ev.xselectionrequest.time, }); } break; case SelectionNotify: if (ev.xselection.property == _dnd_selection) { u8 *data; i32 len; XGetWindowProperty( _display, ev.xselection.requestor, _dnd_selection, 0, STATIC_MEMORY_BUFFER_SIZE, // FIXME False, ev.xselection.target, &(Atom) {0}, &(i32) {0}, (void *) &len, &(unsigned long) {0}, &data ); if (len == 0) { LOG_error("Unable to retrieve drag and drop info."); break; } if (data) { i64 num = 0; for (i64 i = 0; i < len; ++i) if (data[i] == '\n' || i + 1 == len) ++num; drop_files_set_num_(num); i64 index = 0; i64 offset = 0; for (i64 i = 0; i < len; ++i) if (data[i] == '\n' || i + 1 == len) { c8 *name = (c8 *) data + offset; if (sub_str_eq_(name, FILE_PATH_PREFIX_, FILE_PATH_PREFIX_LEN_)) name = name + FILE_PATH_PREFIX_LEN_; i64 name_len = 0; while (name_len < (c8 *) data + len - name && name[name_len] != '\n' && name[name_len] != '\r') ++name_len; drop_files_set_name_(index, name_len, name); ++index; offset = i + 1; } XFree(data); } } else if (ev.xselection.property != None) { i64 len = 0; u8 *data = NULL; XGetWindowProperty( _display, _window, _clipboard, 0, STATIC_MEMORY_BUFFER_SIZE, // FIXME False, AnyPropertyType, &(Atom) {0}, &(i32) {0}, (void *) &len, &(unsigned long) {0}, &data ); if (ev.xselection.target == _targets) { Atom *list = (Atom *) data; Atom target_text = None; Atom target_image = None; Atom target_sound = None; len /= 4; for (i64 i = 0; i < len; i++) if (list[i] == XA_STRING) target_text = XA_STRING; else if (list[i] == _text_plain) target_text = _text_plain; else if (list[i] == _utf8_string) { target_text = _utf8_string; break; } for (i64 i = 0; i < len; i++) if (list[i] == _image_ppm) target_image = _image_ppm; else if (list[i] == _image_bmp) { target_image = _image_bmp; break; } for (i64 i = 0; i < len; i++) if (list[i] == _audio_wav) { target_sound = _audio_wav; break; } if (g_platform.enable_clipboard_text && target_text != None) XConvertSelection(_display, _clipboard, target_text, _clipboard, _window, CurrentTime); if (g_platform.enable_clipboard_image && target_image != None) XConvertSelection(_display, _clipboard, target_image, _clipboard, _window, CurrentTime); if (g_platform.enable_clipboard_sound && target_sound != None) XConvertSelection(_display, _clipboard, target_sound, _clipboard, _window, CurrentTime); if (target_text == None && target_image == None && target_sound == None) _requested_clipboard = 0; } else if (ev.xselection.target == XA_STRING || ev.xselection.target == _text_plain || ev.xselection.target == _utf8_string) { if (g_platform.enable_clipboard_text) { resize_dynamic_array_exact(&g_platform.clipboard_text_len, (void **) &g_platform.clipboard_text, 1, len + 1); if (g_platform.clipboard_text_len == len + 1) { mem_cpy_(g_platform.clipboard_text, data, len); g_platform.clipboard_text[len] = '\0'; } } _requested_clipboard = 0; } else if (ev.xselection.target == _image_bmp) { if (g_platform.enable_clipboard_image) { // TODO LOG_error("Receive BMP image - not implemented."); } _requested_clipboard = 0; } else if (ev.xselection.target == _image_ppm) { if (g_platform.enable_clipboard_image) { // TODO LOG_error("Receive PPM image - not implemented."); } _requested_clipboard = 0; } else if (ev.xselection.target == _audio_wav) { if (g_platform.enable_clipboard_sound) { // TODO LOG_error("Receive WAV audio - not implemented."); } _requested_clipboard = 0; } if (data) XFree(data); } break; case EnterNotify: g_platform.has_cursor = 1; break; case LeaveNotify: g_platform.has_cursor = 0; break; case FocusOut: g_platform.has_focus = 0; break; case FocusIn: g_platform.has_focus = 1; request_clipboard_(); break; case MappingNotify: XRefreshKeyboardMapping(&ev.xmapping); break; case ClientMessage: if ((Atom) ev.xclient.data.l[0] == _wm_delete_window) g_platform.done = 1; else { if (ev.xclient.message_type == _dnd_enter) _dnd_source = ev.xclient.data.l[0]; else if (ev.xclient.message_type == _dnd_position) { i32 xabs = (ev.xclient.data.l[2] >> 16) & 0xffff; i32 yabs = ev.xclient.data.l[2] & 0xffff; Window dummy; i32 xpos; i32 ypos; XTranslateCoordinates( _display, XDefaultRootWindow(_display), _window, xabs, yabs, &xpos, &ypos, &dummy ); g_platform.cursor_dx += xpos - g_platform.cursor_x; g_platform.cursor_dy += ypos - g_platform.cursor_y; g_platform.cursor_x = xpos; g_platform.cursor_y = ypos; if (!g_platform.files_dragged) { XConvertSelection( _display, _dnd_selection, _text_uri_list, _dnd_selection, _window, ev.xclient.data.l[2] ); g_platform.files_dragged = 1; } XSendEvent( _display, _dnd_source, False, NoEventMask, &(XEvent) { .xclient = { .type = ClientMessage, .message_type = _dnd_status, .window = _drop_source, .format = 32, .data = { .l = { _window, 1, 0, 0, _dnd_action_copy, }, }, }, } ); XFlush(_display); } else if (ev.xclient.message_type == _dnd_drop) { if (g_platform.drop_files == NULL) LOG_error("No drop files."); else for (i64 i = 0; i < g_platform.num_drop_files; ++i) { FILE *f = fopen(g_platform.drop_files[i].name, "rb"); if (f == NULL) LOG_error("Unable to open: %s", g_platform.drop_files[i].name); i64 size = 0; if (f != NULL) { struct stat info; if (fstat(fileno(f), &info) == 0 && S_ISREG(info.st_mode)) size = (i64) info.st_size; else LOG_error("Unable to get the file info: %s", g_platform.drop_files[i].name); } if (size > 0) { drop_files_set_data_(i, size); i64 num = (i64) fread(g_platform.drop_files[i].data, 1, g_platform.drop_files[i].data_size + 1, f); fclose(f); if (num != g_platform.drop_files[i].data_size) { LOG_error("Unable to read: %s", g_platform.drop_files[i].name); drop_files_set_data_(i, 0); } } } g_platform.files_dropped = 1; XSendEvent( _display, _dnd_source, False, NoEventMask, &(XEvent) { .xclient = { .type = ClientMessage, .message_type = _dnd_finished, .window = _drop_source, .format = 32, .data = { .l = { _window, 1, _dnd_action_copy, }, }, }, } ); XFlush(_display); } else if (ev.xclient.message_type == _dnd_leave) { if (!g_platform.files_dropped) drop_files_clean_(); } } break; default:; } } XWindowAttributes attrs; XGetWindowAttributes(_display, _window, &attrs); num_events += pixel_size_update_(attrs.width, attrs.height); _image.width = _internal_width; _image.height = _internal_height; _image.data = (c8 *) _internal_pixels; _image.bytes_per_line = _image.width * 4; return num_events; } i32 wait_main_window_events(void) { PROFILER_begin(PROFILE_WAITING); i32 num_events = 0; for (;;) { num_events = handle_main_window_events(); if (num_events != 0) break; usleep(0); } PROFILER_end(PROFILE_WAITING); return num_events; } void render_main_window_frame(void) { if (g_platform.done) return; if (!_mapped) { XMapWindow(_display, _window); request_clipboard_(); _mapped = 1; } convert_pixels_for_window_(); put_image_to_main_window_(); pixel_size_calibrate_(current_utc_time_in_milliseconds() - _frame_time); } void write_clipboard_text(i64 size, c8 *data) { if (size < 0 || data == NULL) return; store_clipboard_text_(size, data); if (g_platform.clipboard_text_len > 0) XSetSelectionOwner(_display, _clipboard, _window, CurrentTime); } b8 x11_get_root_(void) { Window root, parent, *children; u32 num_children; if(!XQueryTree(_display, _window, &root, &parent, &children, &num_children)) return 0; if (children != NULL) XFree((c8 *) children); _root_window = root; return 1; } b8 x11_screenshot_(i64 *width, i64 *height, i64 max_num_pixels, vec4_f32 *pixels) { if (_display == NULL) { LOG_error("No display."); return 0; } i32 screen = DefaultScreen(_display); i32 display_width = DisplayWidth (_display, screen); i32 display_height = DisplayHeight(_display, screen); x11_get_root_(); XImage *image = XGetImage(_display, _root_window, 0, 0, display_width, display_height, AllPlanes, ZPixmap); if (image == NULL) { LOG_error("XGetImage failed."); return 0; } *width = image->width; *height = image->height; if (pixels != NULL && image->width * image->height <= max_num_pixels) for (i64 j = 0; j < image->height; ++j) { vec4_f32 *d = pixels + j * image->width; vec4_f32 *d_first = d; vec4_f32 *d_end = d + image->width; for (; d < d_end; ++d) { vec3_f32 c = rgb_f32_from_u32_(XGetPixel(image, d - d_first, j)); *d = (vec4_f32) { .x = c.x, .y = c.y, .z = c.z, .w = 1.f, }; } } XDestroyImage(image); return 1; } #endif // defined(__linux__) && ENABLE_X11 // ================================================================ #if defined(__linux__) && (ENABLE_X11 || ENABLE_WAYLAND) void take_screenshot(i64 *width, i64 *height, i64 max_num_pixels, vec4_f32 *pixels) { if (width == NULL || height == NULL) { LOG_error("Invalid arguments."); return; } #if ENABLE_X11 if (x11_screenshot_(width, height, max_num_pixels, pixels)) return; #endif #if ENABLE_WAYLAND if (wayland_screenshot_(width, height, max_num_pixels, pixels)) return; #endif *width = 0; *height = 0; } #endif // defined(__linux__) && (ENABLE_X11 || ENABLE_WAYLAND) // ================================================================ // // Web startup options // // ================================================================ #if defined(__wasm__) static u16 _key_map[MAX_NUM_KEYS] = {0}; __attribute__((export_name("js_max_num_keys"))) i32 js_max_num_keys(void) { return MAX_NUM_KEYS; } __attribute__((export_name("js_key_map"))) void *js_key_map(void) { i32 n = 0; _key_map[n++] = KEY_BACKSPACE; _key_map[n++] = KEY_TAB; _key_map[n++] = KEY_ENTER; _key_map[n++] = KEY_LCTRL; _key_map[n++] = KEY_RCTRL; _key_map[n++] = KEY_LSHIFT; _key_map[n++] = KEY_RSHIFT; _key_map[n++] = KEY_LALT; _key_map[n++] = KEY_RALT; _key_map[n++] = KEY_LEFT; _key_map[n++] = KEY_RIGHT; _key_map[n++] = KEY_UP; _key_map[n++] = KEY_DOWN; _key_map[n++] = KEY_PAUSE; _key_map[n++] = KEY_INSERT; _key_map[n++] = KEY_HOME; _key_map[n++] = KEY_END; _key_map[n++] = KEY_PAGEUP; _key_map[n++] = KEY_PAGEDOWN; _key_map[n++] = KEY_ESCAPE; _key_map[n++] = KEY_PRINTSCREEN; _key_map[n++] = KEY_SPACE; _key_map[n++] = KEY_LMETA; _key_map[n++] = KEY_RMETA; _key_map[n++] = KEY_QUOTE; _key_map[n++] = KEY_COMMA; _key_map[n++] = KEY_MINUS; _key_map[n++] = KEY_PERIOD; _key_map[n++] = KEY_SLASH; _key_map[n++] = KEY_0; _key_map[n++] = KEY_1; _key_map[n++] = KEY_2; _key_map[n++] = KEY_3; _key_map[n++] = KEY_4; _key_map[n++] = KEY_5; _key_map[n++] = KEY_6; _key_map[n++] = KEY_7; _key_map[n++] = KEY_8; _key_map[n++] = KEY_9; _key_map[n++] = KEY_COLON; _key_map[n++] = KEY_EQUAL; _key_map[n++] = KEY_LBRACE; _key_map[n++] = KEY_BACKSLASH; _key_map[n++] = KEY_RBRACE; _key_map[n++] = KEY_TILDE; _key_map[n++] = KEY_A; _key_map[n++] = KEY_B; _key_map[n++] = KEY_C; _key_map[n++] = KEY_D; _key_map[n++] = KEY_E; _key_map[n++] = KEY_F; _key_map[n++] = KEY_G; _key_map[n++] = KEY_H; _key_map[n++] = KEY_I; _key_map[n++] = KEY_J; _key_map[n++] = KEY_K; _key_map[n++] = KEY_L; _key_map[n++] = KEY_M; _key_map[n++] = KEY_N; _key_map[n++] = KEY_O; _key_map[n++] = KEY_P; _key_map[n++] = KEY_Q; _key_map[n++] = KEY_R; _key_map[n++] = KEY_S; _key_map[n++] = KEY_T; _key_map[n++] = KEY_U; _key_map[n++] = KEY_V; _key_map[n++] = KEY_W; _key_map[n++] = KEY_X; _key_map[n++] = KEY_Y; _key_map[n++] = KEY_Z; _key_map[n++] = KEY_DELETE; _key_map[n++] = KEY_F1; _key_map[n++] = KEY_F2; _key_map[n++] = KEY_F3; _key_map[n++] = KEY_F4; _key_map[n++] = KEY_F5; _key_map[n++] = KEY_F6; _key_map[n++] = KEY_F7; _key_map[n++] = KEY_F8; _key_map[n++] = KEY_F9; _key_map[n++] = KEY_F10; _key_map[n++] = KEY_F11; _key_map[n++] = KEY_F12; _key_map[n++] = KEY_F13; _key_map[n++] = KEY_F14; _key_map[n++] = KEY_F15; _key_map[n++] = KEY_F16; _key_map[n++] = KEY_F17; _key_map[n++] = KEY_F18; _key_map[n++] = KEY_F19; _key_map[n++] = KEY_F20; _key_map[n++] = KEY_F21; _key_map[n++] = KEY_F22; _key_map[n++] = KEY_F23; _key_map[n++] = KEY_F24; return _key_map; } __attribute__((export_name("js_sound_sample_rate"))) f64 js_sound_sample_rate(void) { return (f64) PRIMARY_SOUND_SAMPLE_RATE; } __attribute__((export_name("js_num_sound_channels"))) i32 js_num_sound_channels(void) { return NUM_PRIMARY_SOUND_CHANNELS; } #endif // defined(__wasm__) // ================================================================ // // Web // // ================================================================ #if defined(__wasm__) static i32 _num_events = 0; static i32 _input_len = 0; static i32 _cursor_dx = 0; static i32 _cursor_dy = 0; static f64 _wheel_dx = 0; static f64 _wheel_dy = 0; static b8 _wait_events = 0; static i64 _timeout = 0; static i64 _sound_read = 0; static b8 _files_dragged = 0; static b8 _files_dropped = 0; static c8 _href [4096] = {0}; static b8 _key_pressed [MAX_NUM_KEYS] = {0}; static f32 _sound_buffer [MAX_NUM_PRIMARY_SOUND_FRAMES] = {0}; void shutdown_all_systems(void) { g_platform.done = 1; } b8 network_open(u16 slot, Network_Address address, u16 *local_port) { LOG_error("Web sockets are not implemented."); return 0; } i64 network_recv(u16 slot, Network_Address address, i64 size, u8 *data, u16 *local_port, Network_Address *remote_address) { LOG_error("Web sockets are not implemented."); return 0; } i64 network_send(u16 slot, Network_Address address, i64 size, u8 *data, u16 *local_port) { LOG_error("Web sockets are not implemented."); return 0; } i64 current_utc_time_in_milliseconds(void); void current_utc_time_in_sec_and_nsec(i64 *seconds, i64 *nanoseconds) { i64 time = current_utc_time_in_milliseconds(); if (seconds != NULL) *seconds = time / 1000ll; if (nanoseconds != NULL) *nanoseconds = 1000000ll * (time % 1000ll); } void yield_thread_execution(void) { // Do nothing } void suspend_thread_for_milliseconds(i64 duration) { if (duration <= 0) return; i64 t = current_utc_time_in_milliseconds(); if (_timeout < t) _timeout = t + duration; else _timeout += duration; } void init_main_window(void) { (void) rgb_f32_from_u32_; ++_num_events; g_platform.done = 1; for (i64 i = 0; i < NUM_FRAMES_AVERAGED; ++i) _frame_duration[i] = (MIN_FRAME_DURATION + MAX_FRAME_DURATION) / 2; _sound_read = (MAX_NUM_PRIMARY_SOUND_FRAMES + ((-PRIMARY_SOUND_AVAIL_MIN) % MAX_NUM_PRIMARY_SOUND_FRAMES)) % MAX_NUM_PRIMARY_SOUND_FRAMES; } i32 handle_main_window_events(void) { if (g_platform.files_dropped) { // NOTE: We won't be able to drop files at each // of 2 sequential frames. drop_files_clean_(); _files_dragged = 0; _files_dropped = 0; } g_platform.input_len = _input_len; g_platform.cursor_dx = _cursor_dx; g_platform.cursor_dy = _cursor_dy; g_platform.wheel_dx = _wheel_dx; g_platform.wheel_dy = _wheel_dy; g_platform.files_dragged = _files_dragged; g_platform.files_dropped = _files_dropped; _input_len = 0; _cursor_dx = 0; _cursor_dy = 0; _wheel_dx = 0; _wheel_dy = 0; if (_files_dropped) { _files_dragged = 0; _files_dropped = 0; } for (i64 i = 0; i < MAX_NUM_KEYS; ++i) g_platform.key_pressed[i] = _key_pressed[i]; for (i64 i = 0; i < MAX_NUM_KEYS; ++i) _key_pressed[i] = 0; i32 n = _num_events; _num_events = 0; return n; } i32 wait_main_window_events(void) { _wait_events = 1; return handle_main_window_events(); } void render_main_window_frame(void) { convert_pixels_for_window_(); } void write_clipboard_text_impl(i32 size, c8 *data); void write_clipboard_text(i64 size, c8 *data) { if (size < 0 || data == NULL) return; store_clipboard_text_(size, data); if (g_platform.clipboard_text_len > 0) write_clipboard_text_impl((i32) g_platform.clipboard_text_len - 1, data); } void take_screenshot(i64 max_num_pixels, i64 *width, i64 *height, vec4_f32 *pixels) { if (width == NULL || height == NULL) { LOG_error("Invalid arguments."); return; } *width = 0; *height = 0; } void handle_primary_sound(void) { g_platform.num_sound_samples_elapsed = sound_samples_elapsed_(); _sound_position = (_sound_position + g_platform.num_sound_samples_elapsed * NUM_PRIMARY_SOUND_CHANNELS) % MAX_NUM_PRIMARY_SOUND_FRAMES; } void queue_primary_sound(i64 delay_in_samples, i64 num_samples, f32 *frames) { if (frames == NULL) return; if (delay_in_samples < 0) { frames += -delay_in_samples * NUM_PRIMARY_SOUND_CHANNELS; num_samples -= delay_in_samples; delay_in_samples = 0; } if (num_samples <= 0) return; i64 num_frames = num_samples * NUM_PRIMARY_SOUND_CHANNELS; if (num_frames > MAX_NUM_PRIMARY_SOUND_FRAMES) { LOG_error("Sound buffer overflow."); return; } i64 begin = (_sound_position + delay_in_samples * NUM_PRIMARY_SOUND_CHANNELS) % MAX_NUM_PRIMARY_SOUND_FRAMES; if (num_frames <= MAX_NUM_PRIMARY_SOUND_FRAMES - begin) for (i64 i = 0; i < num_frames; ++i) _sound_ring[begin + i] += frames[i]; else { i64 part_one = MAX_NUM_PRIMARY_SOUND_FRAMES - begin; i64 part_two = num_frames - part_one; for (i64 i = 0; i < part_one; ++i) _sound_ring[begin + i] += frames[i]; for (i64 i = 0; i < part_two; ++i) _sound_ring[i] += frames[part_one + i]; } } __attribute__((export_name("js_max_num_sound_frames"))) i32 js_max_num_sound_frames(void) { return MAX_NUM_PRIMARY_SOUND_FRAMES; } __attribute__((export_name("js_sound_buffer"))) void *js_sound_buffer(void) { return _sound_buffer; } __attribute__((export_name("js_href"))) void *js_href(void) { return _href; } __attribute__((export_name("js_href_size"))) i32 js_href_size(void) { return (sizeof _href) - 1; } __attribute__((export_name("js_main"))) void js_main(void) { main(1, (c8 *[1]) { _href }); } __attribute__((export_name("js_title"))) void *js_title(void) { return g_platform.title; } __attribute__((export_name("js_frame"))) void *js_frame(i32 frame_width, i32 frame_height, i32 num_samples) { i64 frame_time = current_utc_time_in_milliseconds(); _num_events += pixel_size_update_(frame_width, frame_height); g_platform.done = 0; b8 do_render = (frame_time >= _timeout) && (_num_events > 0 || !_wait_events); if (do_render) { _wait_events = 0; update_and_render_frame(); } // Convert interleaved frames to non-interleaved. for (i64 j = 0; j < NUM_PRIMARY_SOUND_CHANNELS; ++j) for (i64 i = 0; i < num_samples; ++i) { i64 n = (_sound_read + i * NUM_PRIMARY_SOUND_CHANNELS + j) % MAX_NUM_PRIMARY_SOUND_FRAMES; _sound_buffer[j * num_samples + i] = _sound_ring[n]; _sound_ring[n] = 0.f; } _sound_read = (_sound_read + num_samples * NUM_PRIMARY_SOUND_CHANNELS) % MAX_NUM_PRIMARY_SOUND_FRAMES; if (do_render) pixel_size_calibrate_(current_utc_time_in_milliseconds() - frame_time); return _internal_pixels; } __attribute__((export_name("js_mousemove"))) void js_mousemove(i32 x, i32 y) { ++_num_events; _cursor_dx += x - g_platform.cursor_x; _cursor_dy += y - g_platform.cursor_y; g_platform.cursor_x = x; g_platform.cursor_y = y; } __attribute__((export_name("js_mousedown"))) void js_mousedown(u32 buttons) { ++_num_events; if ((buttons & 1) && !g_platform.key_down[BUTTON_LEFT]) _key_pressed[BUTTON_LEFT] = 1; if ((buttons & 2) && !g_platform.key_down[BUTTON_RIGHT]) _key_pressed[BUTTON_RIGHT] = 1; if ((buttons & 4) && !g_platform.key_down[BUTTON_MIDDLE]) _key_pressed[BUTTON_MIDDLE] = 1; if (buttons & 1) g_platform.key_down[BUTTON_LEFT] = 1; if (buttons & 2) g_platform.key_down[BUTTON_RIGHT] = 1; if (buttons & 4) g_platform.key_down[BUTTON_MIDDLE] = 1; } __attribute__((export_name("js_mouseup"))) void js_mouseup(u32 buttons) { ++_num_events; if (!(buttons & 1)) g_platform.key_down[BUTTON_LEFT] = 0; if (!(buttons & 2)) g_platform.key_down[BUTTON_RIGHT] = 0; if (!(buttons & 4)) g_platform.key_down[BUTTON_MIDDLE] = 0; } __attribute__((export_name("js_keydown"))) void js_keydown(u32 key, u32 mod, u32 ch) { ++_num_events; _key_pressed[key] = 1; g_platform.key_down[key] = 1; g_platform.key_down[MOD_CTRL] = (mod & 1) ? 1 : 0; g_platform.key_down[MOD_SHIFT] = (mod & 2) ? 1 : 0; g_platform.key_down[MOD_ALT] = (mod & 4) ? 1 : 0; g_platform.key_down[MOD_META] = (mod & 8) ? 1 : 0; i64 n = g_platform.input_len; resize_dynamic_array_capacity(&g_platform.input_len, &g_platform.input_capacity, (void **) &g_platform.input, sizeof *g_platform.input, n + 1); if (n < g_platform.input_len) g_platform.input[n] = (Input_Key) { .ctrl = (mod & 1) ? 1 : 0, .shift = (mod & 2) ? 1 : 0, .alt = (mod & 4) ? 1 : 0, .meta = (mod & 8) ? 1 : 0, .key = key, .c = ch, }; } __attribute__((export_name("js_keyup"))) void js_keyup(u32 key, u32 mod) { ++_num_events; g_platform.key_down[key] = 0; g_platform.key_down[MOD_CTRL] = (mod & 1) ? 1 : 0; g_platform.key_down[MOD_SHIFT] = (mod & 2) ? 1 : 0; g_platform.key_down[MOD_ALT] = (mod & 4) ? 1 : 0; g_platform.key_down[MOD_META] = (mod & 8) ? 1 : 0; } __attribute__((export_name("js_wheel"))) void js_wheel(f64 delta_x, f64 delta_y) { ++_num_events; _wheel_dx += delta_x * MOUSE_WHEEL_FACTOR; _wheel_dy += delta_y * MOUSE_WHEEL_FACTOR; } __attribute__((export_name("js_clipboard_text_len"))) i32 js_clipboard_text_len(void) { return g_platform.clipboard_text_len; } __attribute__((export_name("js_clipboard_text"))) void *js_clipboard_text(i32 len) { if (!g_platform.enable_clipboard_text && len < 0) len = 0; resize_dynamic_array_exact(&g_platform.clipboard_text_len, (void **) &g_platform.clipboard_text, 1, len); return g_platform.clipboard_text; } __attribute__((export_name("js_dragenter"))) void js_dragenter(void) { _files_dragged = 1; } __attribute__((export_name("js_dragleave"))) void js_dragleave(void) { _files_dragged = 0; } __attribute__((export_name("js_drop"))) i32 js_drop(i32 name_len, i32 data_size) { i64 n = g_platform.num_drop_files; drop_files_set_num_(n + 1); if (n >= g_platform.num_drop_files) return 0; drop_files_set_name_(n, name_len, NULL); drop_files_set_data_(n, data_size); Drop_File *f = g_platform.drop_files + n; if (f->name_len != name_len || f->data_size != data_size) { drop_files_set_name_(n, 0, NULL); drop_files_set_data_(n, 0); drop_files_set_num_(n); LOG_error("File too big."); return 0; } mem_set_(f->name, 0, name_len); _files_dropped = 1; return 1; } __attribute__((export_name("js_drop_name"))) void *js_drop_name(void) { if (g_platform.num_drop_files <= 0) return NULL; return g_platform.drop_files[g_platform.num_drop_files - 1].name; } __attribute__((export_name("js_drop_data"))) void *js_drop_data(void) { if (g_platform.num_drop_files <= 0) return NULL; return g_platform.drop_files[g_platform.num_drop_files - 1].data; } #endif // defined(__wasm__) // ================================================================ // // No system // // ================================================================ #if !defined(__linux__) || (!ENABLE_X11 && !ENABLE_WAYLAND) void init_main_window(void) { LOG_error("Not implemented."); } void shutdown_all_systems(void) { // TODO: Factor out common code. PROFILER_report_(); g_platform.done = 1; } #endif #if !defined(__linux__) || !ENABLE_ALSA #endif // ================================================================ // // Test suite // // ================================================================ #if ENABLE_TESTING #define TEST_FILE runtime #include "test.c" TEST("types sanity basic") { REQUIRE_EQ(sizeof(i8), 1); REQUIRE_EQ(sizeof(i16), 2); REQUIRE_EQ(sizeof(i32), 4); REQUIRE_EQ(sizeof(i64), 8); REQUIRE_EQ(sizeof(u8), 1); REQUIRE_EQ(sizeof(u16), 2); REQUIRE_EQ(sizeof(u32), 4); REQUIRE_EQ(sizeof(u64), 8); REQUIRE_EQ(sizeof(c8), 1); REQUIRE_EQ(sizeof(c32), 4); REQUIRE_EQ(sizeof(b8), 1); REQUIRE_EQ(sizeof(f32), 4); REQUIRE_EQ(sizeof(f64), 8); } TEST("types sanity vector") { vec2 a = { 1.0, 2.0, }; vec3 b = { 1.0, 2.0, 3.0, }; vec4 c = { 1.0, 2.0, 3.0, 4.0, }; vec2_f32 d = { 1.0f, 2.0f, }; vec3_f32 e = { 1.0f, 2.0f, 3.0f, }; vec4_f32 f = { 1.0f, 2.0f, 3.0f, 4.0f, }; vec2_i64 g = { 1, 2, }; vec3_i64 h = { 1, 2, 3, }; vec4_i64 i = { 1, 2, 3, 4, }; REQUIRE(a.x == 1.0); REQUIRE(a.y == 2.0); REQUIRE(b.x == 1.0); REQUIRE(b.y == 2.0); REQUIRE(b.z == 3.0); REQUIRE(c.x == 1.0); REQUIRE(c.y == 2.0); REQUIRE(c.z == 3.0); REQUIRE(c.w == 4.0); REQUIRE(d.x == 1.0f); REQUIRE(d.y == 2.0f); REQUIRE(e.x == 1.0f); REQUIRE(e.y == 2.0f); REQUIRE(e.z == 3.0f); REQUIRE(f.x == 1.0f); REQUIRE(f.y == 2.0f); REQUIRE(f.z == 3.0f); REQUIRE(f.w == 4.0f); REQUIRE_EQ(g.x, 1); REQUIRE_EQ(g.y, 2); REQUIRE_EQ(h.x, 1); REQUIRE_EQ(h.y, 2); REQUIRE_EQ(h.z, 3); REQUIRE_EQ(i.x, 1); REQUIRE_EQ(i.y, 2); REQUIRE_EQ(i.z, 3); REQUIRE_EQ(i.w, 4); } TEST("types sanity matrix") { mat2 j = { 1.0, 2.0, 3.0, 4.0, }; mat3 k = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, }; mat4 l = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, }; mat2_f32 m = { 1.0, 2.0, 3.0, 4.0, }; mat3_f32 n = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, }; mat4_f32 o = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, }; REQUIRE(j.m[0][0] == 1.0); REQUIRE(j.m[0][1] == 2.0); REQUIRE(j.m[1][0] == 3.0); REQUIRE(j.m[1][1] == 4.0); REQUIRE(k.m[0][0] == 1.0); REQUIRE(k.m[0][1] == 2.0); REQUIRE(k.m[0][2] == 3.0); REQUIRE(k.m[1][0] == 4.0); REQUIRE(k.m[1][1] == 5.0); REQUIRE(k.m[1][2] == 6.0); REQUIRE(k.m[2][0] == 7.0); REQUIRE(k.m[2][1] == 8.0); REQUIRE(k.m[2][2] == 9.0); REQUIRE(l.m[0][0] == 1.0); REQUIRE(l.m[0][1] == 2.0); REQUIRE(l.m[0][2] == 3.0); REQUIRE(l.m[0][3] == 4.0); REQUIRE(l.m[1][0] == 5.0); REQUIRE(l.m[1][1] == 6.0); REQUIRE(l.m[1][2] == 7.0); REQUIRE(l.m[1][3] == 8.0); REQUIRE(l.m[2][0] == 9.0); REQUIRE(l.m[2][1] == 10.0); REQUIRE(l.m[2][2] == 11.0); REQUIRE(l.m[2][3] == 12.0); REQUIRE(l.m[3][0] == 13.0); REQUIRE(l.m[3][1] == 14.0); REQUIRE(l.m[3][2] == 15.0); REQUIRE(l.m[3][3] == 16.0); REQUIRE(m.m[0][0] == 1.0f); REQUIRE(m.m[0][1] == 2.0f); REQUIRE(m.m[1][0] == 3.0f); REQUIRE(m.m[1][1] == 4.0f); REQUIRE(n.m[0][0] == 1.0f); REQUIRE(n.m[0][1] == 2.0f); REQUIRE(n.m[0][2] == 3.0f); REQUIRE(n.m[1][0] == 4.0f); REQUIRE(n.m[1][1] == 5.0f); REQUIRE(n.m[1][2] == 6.0f); REQUIRE(n.m[2][0] == 7.0f); REQUIRE(n.m[2][1] == 8.0f); REQUIRE(n.m[2][2] == 9.0f); REQUIRE(o.m[0][0] == 1.0f); REQUIRE(o.m[0][1] == 2.0f); REQUIRE(o.m[0][2] == 3.0f); REQUIRE(o.m[0][3] == 4.0f); REQUIRE(o.m[1][0] == 5.0f); REQUIRE(o.m[1][1] == 6.0f); REQUIRE(o.m[1][2] == 7.0f); REQUIRE(o.m[1][3] == 8.0f); REQUIRE(o.m[2][0] == 9.0f); REQUIRE(o.m[2][1] == 10.0f); REQUIRE(o.m[2][2] == 11.0f); REQUIRE(o.m[2][3] == 12.0f); REQUIRE(o.m[3][0] == 13.0f); REQUIRE(o.m[3][1] == 14.0f); REQUIRE(o.m[3][2] == 15.0f); REQUIRE(o.m[3][3] == 16.0f); } TEST("math sanity") { REQUIRE(floor(1.99) == 1.0); REQUIRE(floor(-1.01) == -2.0); REQUIRE(ceil(1.01) == 2.0); REQUIRE(ceil(-1.99) == -1.0); REQUIRE(trunc(1.99) == 1.0); REQUIRE(trunc(-1.99) == -1.0); REQUIRE(sqrt(4.0) > 2.0 - 1e-13); REQUIRE(sqrt(4.0) < 2.0 + 1e-13); REQUIRE(cbrt(8.0) > 2.0 - 1e-13); REQUIRE(cbrt(8.0) < 2.0 + 1e-13); REQUIRE(pow(2.0, 3.0) > 8.0 - 1e-13); REQUIRE(pow(2.0, 3.0) < 8.0 + 1e-13); REQUIRE(log(M_E * M_E) > 2.0 - 1e-13); REQUIRE(log(M_E * M_E) < 2.0 + 1e-13); REQUIRE(log2(8.0) > 3.0 - 1e-13); REQUIRE(log2(8.0) < 3.0 + 1e-13); REQUIRE(log10(1000.0) > 3.0 - 1e-13); REQUIRE(log10(1000.0) < 3.0 + 1e-13); REQUIRE(exp(2.0) > M_E * M_E - 1e-13); REQUIRE(exp(2.0) < M_E * M_E + 1e-13); REQUIRE(sin(M_PI / 6.0) > 0.5 - 1e-13); REQUIRE(sin(M_PI / 6.0) < 0.5 + 1e-13); REQUIRE(cos(M_PI / 3.0) > 0.5 - 1e-13); REQUIRE(cos(M_PI / 3.0) < 0.5 + 1e-13); REQUIRE(tan(M_PI / 4.0) > 1.0 - 1e-13); REQUIRE(tan(M_PI / 4.0) < 1.0 + 1e-13); REQUIRE(asin(0.5) > M_PI / 6.0 - 1e-13); REQUIRE(asin(0.5) < M_PI / 6.0 + 1e-13); REQUIRE(acos(0.5) > M_PI / 3.0 - 1e-13); REQUIRE(acos(0.5) < M_PI / 3.0 + 1e-13); REQUIRE(atan(1.0) > M_PI / 4.0 - 1e-13); REQUIRE(atan(1.0) < M_PI / 4.0 + 1e-13); REQUIRE(atan2(1.0, 1.0) > M_PI / 4 - 1e-13); REQUIRE(atan2(1.0, 1.0) < M_PI / 4 + 1e-13); REQUIRE(atan2(1.0, -1.0) > M_PI * 3.0 / 4 - 1e-13); REQUIRE(atan2(1.0, -1.0) < M_PI * 3.0 / 4 + 1e-13); REQUIRE(atan2(-1.0, 1.0) > -M_PI / 4 - 1e-13); REQUIRE(atan2(-1.0, 1.0) < -M_PI / 4 + 1e-13); REQUIRE(atan2(-1.0, -1.0) > -M_PI * 3.0 / 4 - 1e-13); REQUIRE(atan2(-1.0, -1.0) < -M_PI * 3.0 / 4 + 1e-13); REQUIRE(log(2.0) > M_LOG_2 - 1e-13); REQUIRE(log(2.0) < M_LOG_2 + 1e-13); REQUIRE(log(10.0) > M_LOG_10 - 1e-13); REQUIRE(log(10.0) < M_LOG_10 + 1e-13); REQUIRE(log2(M_E) > M_LOG2_E - 1e-13); REQUIRE(log2(M_E) < M_LOG2_E + 1e-13); REQUIRE(log10(M_E) > M_LOG10_E - 1e-13); REQUIRE(log10(M_E) < M_LOG10_E + 1e-13); REQUIRE(log(M_PI) > M_LOG_PI - 1e-13); REQUIRE(log(M_PI) < M_LOG_PI + 1e-13); REQUIRE(sqrt(2.0) > M_SQRT_2 - 1e-13); REQUIRE(sqrt(2.0) < M_SQRT_2 + 1e-13); REQUIRE(sqrt(3.0) > M_SQRT_3 - 1e-13); REQUIRE(sqrt(3.0) < M_SQRT_3 + 1e-13); REQUIRE(sqrt(M_PI) > M_SQRT_PI - 1e-13); REQUIRE(sqrt(M_PI) < M_SQRT_PI + 1e-13); REQUIRE(sqrt(M_E) > M_SQRT_E - 1e-13); REQUIRE(sqrt(M_E) < M_SQRT_E + 1e-13); REQUIRE(cbrt(2.0) > M_CBRT_2 - 1e-13); REQUIRE(cbrt(2.0) < M_CBRT_2 + 1e-13); REQUIRE(cbrt(3.0) > M_CBRT_3 - 1e-13); REQUIRE(cbrt(3.0) < M_CBRT_3 + 1e-13); REQUIRE(cbrt(M_PI) > M_CBRT_PI - 1e-13); REQUIRE(cbrt(M_PI) < M_CBRT_PI + 1e-13); REQUIRE(cbrt(M_E) > M_CBRT_E - 1e-13); REQUIRE(cbrt(M_E) < M_CBRT_E + 1e-13); } TEST("utf8") { REQUIRE_EQ(utf8_len(1), 1); REQUIRE_EQ(utf8_len(127), 1); REQUIRE_EQ(utf8_len(128), 2); REQUIRE_EQ(utf8_len((1 << 11) - 1), 2); REQUIRE_EQ(utf8_len((1 << 11)), 3); REQUIRE_EQ(utf8_len((1 << 16) - 1), 3); REQUIRE_EQ(utf8_len((1 << 16)), 4); // TODO } TEST("utf16") { // TODO } TEST("time sanity") { i64 t = current_utc_time_in_milliseconds(); REQUIRE(t > 0); suspend_thread_for_milliseconds(10); t = current_utc_time_in_milliseconds() - t; REQUIRE(t >= 10); REQUIRE(t <= 20); i64 sec0, nsec0; current_utc_time_in_sec_and_nsec(&sec0, &nsec0); REQUIRE(sec0 > 0); REQUIRE(nsec0 >= 0); REQUIRE(nsec0 < 1000000000); suspend_thread_for_milliseconds(10); i64 sec1, nsec1; current_utc_time_in_sec_and_nsec(&sec1, &nsec1); i64 delta_nsec = 1000000000 * (sec1 - sec0) + (nsec1 - nsec0); REQUIRE(delta_nsec >= 10000000); REQUIRE(delta_nsec <= 20000000); } TEST("blake2b") { u8 out[4]; blake2b(out, (u8[4]) { 1, 2, 3, 4, }, NULL, 4, 4, 0); REQUIRE_EQ(out[0], 26); REQUIRE_EQ(out[1], 185); REQUIRE_EQ(out[2], 56); REQUIRE_EQ(out[3], 84); blake2b(out, (u8[4]) { 1, 2, 3, 4, }, (u8[4]) { 5, 6, 7, 8, }, 4, 4, 4); REQUIRE_EQ(out[0], 74); REQUIRE_EQ(out[1], 233); REQUIRE_EQ(out[2], 165); REQUIRE_EQ(out[3], 192); // TODO: Add more tests with longer inputs. } TEST("memory allocator") { void *p = memory_buffer_allocate(16, 4, 0, NULL); REQUIRE(p != NULL); p = memory_buffer_allocate(64, 4, 16, p); REQUIRE(p != NULL); void *q = p; p = memory_buffer_allocate(8, 4, 64, p); REQUIRE(p == q); p = memory_buffer_allocate(0, 4, 8, p); REQUIRE(p == NULL); // TODO: Add more tests. } TEST("average frame duration") { for (i64 i = 0; i < 50; ++i) average_frame_duration_(100); REQUIRE_EQ(average_frame_duration_(100), 100); } #undef TEST_FILE #endif // ENABLE_TESTING // ================================================================ #endif // RUNTIME_IMPL_GUARD_ #endif // RUNTIME_HEADER