From ca73b12180ad0dd38e73f19bab81807a43460451 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Sun, 20 Apr 2025 18:23:01 +0200 Subject: Rename to Runtime --- examples/echo.c | 2 +- graphics.c | 4 +- reduced_system_layer.c | 4998 ------------------------------------------------ run_tests.c | 2 +- runtime.c | 4998 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 5002 insertions(+), 5002 deletions(-) delete mode 100755 reduced_system_layer.c create mode 100755 runtime.c diff --git a/examples/echo.c b/examples/echo.c index 7d25001..b53c09b 100644 --- a/examples/echo.c +++ b/examples/echo.c @@ -2,7 +2,7 @@ #error Not implemented! #endif -#include "../reduced_system_layer.c" +#include "../runtime.c" enum { MODE_RECV = 1, diff --git a/graphics.c b/graphics.c index 09b5b74..97f4518 100755 --- a/graphics.c +++ b/graphics.c @@ -46,10 +46,10 @@ exit $STATUS # */ #endif #ifdef GRAPHICS_HEADER -#define REDUCED_SYSTEM_LAYER_HEADER +#define RUNTIME_HEADER #endif -#include "reduced_system_layer.c" +#include "runtime.c" // ================================================================ // diff --git a/reduced_system_layer.c b/reduced_system_layer.c deleted file mode 100755 index b2bec10..0000000 --- a/reduced_system_layer.c +++ /dev/null @@ -1,4998 +0,0 @@ -#if 0 /* -#/ ================================================================ -#/ -#/ reduced_system_layer.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. -#/ -#/ - 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 perf - request cache -#/ Requires: -#/ + [ ] Graphics tests -#/ + [x] Graphics requests -#/ + [x] Memory buffer allocator -#/ + [x] Blake2 hash -#/ + [x] Requests cache -#/ + [x] Global anti-aliasing -#/ + [x] Fill triangles -#/ - Examples -#/ - Conway's Game of Life -#/ - Julia Set -#/ - Labyrinth -#/ - Chat -#/ - Graphics -#/ - Gamma correction -#/ - Custom fonts - integrate stb_truetype.h -#/ - UI -#/ - Icons -#/ - Folder widget -#/ - Clipboard -#/ - Images - BMP, PPM -#/ - Sound - WAV -#/ - Clipboard -#/ - Images - BMP, PPM -#/ - Sound - WAV -#/ - Dynamic libraries - load dependencies conditionally -#/ - X11 -#/ - Wayland -#/ - ALSA -#/ - Sockets -#/ - Windows -#/ - 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 -#/ - 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 -#/ - Switching canvas - Web -#/ - 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 -#/ - Testing -#/ - Stackless coroutines -#/ - Allocator -#/ - Profiling -#/ - Graphics -#/ - Font -#/ - Adaptive resolution -#/ - Oklab color -#/ - Relative coordinates -#/ - Alpha blending -#/ - Self-contained impl -#/ - Anti-aliasing -#/ - System -#/ - 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. -#/ -#/ ================================================================ -#/ -#/ Self-testing shell script -#/ -#/ ================================================================ - -SRC=${0##*./} -BIN=${SRC%.*} -gcc \ - -Wall -Wextra -Werror -pedantic \ - -Wno-missing-braces \ - -Wno-old-style-declaration \ - -Wno-overlength-strings \ - -Wno-unused-parameter \ - -Wno-unused-variable \ - -Wno-unused-but-set-variable \ - -O3 -D NDEBUG \ - -fsanitize=undefined,address,leak \ - -D REDUCED_SYSTEM_LAYER_TEST_SUITE \ - -lm -lX11 -lasound \ - -lwayland-client \ - -o $BIN $SRC && \ - ./$BIN $@ -STATUS=$? -rm -f $BIN -exit $STATUS # */ -#endif - -// ================================================================ -// -// Types -// -// ================================================================ - -#ifndef TYPES_HEADER_GUARD_ -#define TYPES_HEADER_GUARD_ - -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 int c32; -typedef unsigned char b8; -typedef float f32; -typedef double f64; - -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 REDUCED_SYSTEM_LAYER_HEADER_GUARD_ -#define REDUCED_SYSTEM_LAYER_HEADER_GUARD_ - -#ifdef EVERY_TEST_SUITE -#define REDUCED_SYSTEM_LAYER_TEST_SUITE -#endif - -#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 - -// ================================================================ -// -// Options -// -// ================================================================ - -#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 - -// ================================================================ -// -// 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 { - 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 disable_gamma : 1; // TODO - 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]; - - i64 memory_buffer_size; - u8 *memory_buffer; -} Platform; - -// UTF-8 -// NOTE: We need UTF-8 because we use Xutf8LookupString on Xlib. -i8 utf8_size (c32 c); -UTF8_Char utf8_read (i64 len, c8 *s); -i8 utf8_write(c32 c, c8 *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 REDUCED_SYSTEM_LAYER_HEADER -extern Platform g_platform; -#endif - -// ================================================================ - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // REDUCED_SYSTEM_LAYER_HEADER_GUARD_ - -// ================================================================ -// -// PLATFORM INDEPENDENT CODE -// -// ================================================================ - -#ifndef REDUCED_SYSTEM_LAYER_HEADER -#ifndef REDUCED_SYSTEM_LAYER_IMPL_GUARD_ -#define REDUCED_SYSTEM_LAYER_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 size) { - for (i64 i = 0; i < size; ++i) - ((u8 *) dst)[i] = x; -} - -static void mem_cpy_(void *dst, void *src, i64 size) { - for (i64 i = 0; i < size; ++i) - ((u8 *) dst)[i] = ((u8 *) src)[i]; -} - -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; -} - -// ================================================================ -// -// 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 -// -// ================================================================ - -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; -} - -// ================================================================ -// -// UTF-8 -// -// ================================================================ - -i8 utf8_size(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; -} - -// ================================================================ -// -// Colors -// -// ================================================================ - -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 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]; - - // FIXME, PERF: Use better sorting algorithm, e.g. merge sort. - for (i64 i = 0; i < (i64) NUM_FRAMES_AVERAGED; ++i) - for (i64 j = i + 1; j < NUM_FRAMES_AVERAGED; ++j) - if (durs[i] < durs[j]) { - i64 t = durs[i]; - durs[i] = durs[j]; - durs[j] = t; - } - - 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. - // - // FIXME: Check if the scale is 1:1. - { - 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); - - 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; - - for (; d < d_end; ++d, ++im) - *d = rgb_u32_from_f32_((vec3_f32) { .x = im->x * k, .y = im->y * k, .z = im->z * k, }) | 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); - - // TODO, PERF: Improve performance. - - u32 x_ratio = (src_width << 16) / dst_width; - u32 y_ratio = (src_height << 16) / dst_height; - - if (src_len < dst_len) - for (u32 j = dst_height - 1;; --j) { - u32 jj_w = ((j * y_ratio) >> 16) * src_width; - u32 j_w = j * dst_width; - for (u32 i = dst_width - 1;; --i) { - u32 ii = (i * x_ratio) >> 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) >> 16) * src_width; - u32 j_w = j * dst_width; - for (u32 i = 0; i < dst_width; ++i) { - u32 ii = (i * x_ratio) >> 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'; -} - -// ================================================================ -// -// Dynamic libraries -// -// ================================================================ - -#if defined(__unix__) -#include - -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) { - dlclose(_dynamic_libraries.slots[slot].handle); - _dynamic_libraries.slots[slot].handle = NULL; - } - - void *h = dlopen(name, RTLD_LAZY); - - if (h == NULL) { - LOG_error("Failed to open: %s", name); - LOG_error("%s", dlerror()); - return; - } - - _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 = dlsym(_dynamic_libraries.slots[slot].handle, proc); - - if (proc_address == NULL) { - LOG_error("Failed to get: %s", proc); - LOG_error("%s", dlerror()); - } - - 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) - dlclose(_dynamic_libraries.slots[i].handle); - resize_dynamic_array_exact(&_dynamic_libraries.num_slots, (void **) &_dynamic_libraries.slots, sizeof *_dynamic_libraries.slots, 0); -} -#endif // defined(__unix__) - -#if defined(__wasm__) -void dynamic_library_open(u16 slot, c8 *name) { - (void) slot; - (void) name; - - LOG_error("Dynamic library not found: %s", name); -} - -void *dynamic_library_get_proc_address(u16 slot, c8 *proc) { - (void) slot; - (void) proc; - - LOG_error("Proc address not found: %s", proc); - return NULL; -} -#endif // defined(__wasm__) - -// ================================================================ -// -// PLATFORM-SPECIFIC CODE -// -// ================================================================ -// -// 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; - } - - _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; - - 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 update_and_render_frame(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 -// -// ================================================================ - -#ifdef REDUCED_SYSTEM_LAYER_TEST_SUITE - -#define TEST_FILE reduced_system_layer -#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_size(1), 1); - REQUIRE_EQ(utf8_size(127), 1); - REQUIRE_EQ(utf8_size(128), 2); - REQUIRE_EQ(utf8_size((1 << 11) - 1), 2); - REQUIRE_EQ(utf8_size((1 << 11)), 3); - REQUIRE_EQ(utf8_size((1 << 16) - 1), 3); - REQUIRE_EQ(utf8_size((1 << 16)), 4); -} - -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); -} - -#ifndef EVERY_TEST_SUITE -void update_and_render_frame(void) {} - -i32 main(i32 argc, c8 **argv) { - return run_tests(argc, argv); -} -#endif - -#undef TEST_FILE -#endif // REDUCED_SYSTEM_LAYER_TEST_SUITE - -// ================================================================ - -#endif // REDUCED_SYSTEM_LAYER_IMPL_GUARD_ -#endif // REDUCED_SYSTEM_LAYER_HEADER diff --git a/run_tests.c b/run_tests.c index 38f6461..43898da 100755 --- a/run_tests.c +++ b/run_tests.c @@ -28,7 +28,7 @@ exit $STATUS # */ #define ENABLE_WAYLAND 0 #define ENABLE_ALSA 0 -#include "reduced_system_layer.c" +#include "runtime.c" #include "graphics.c" #include "stackless_coroutine.c" diff --git a/runtime.c b/runtime.c new file mode 100755 index 0000000..03a50eb --- /dev/null +++ b/runtime.c @@ -0,0 +1,4998 @@ +#if 0 /* +#/ ================================================================ +#/ +#/ 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. +#/ +#/ - 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 perf - request cache +#/ Requires: +#/ + [ ] Graphics tests +#/ + [x] Graphics requests +#/ + [x] Memory buffer allocator +#/ + [x] Blake2 hash +#/ + [x] Requests cache +#/ + [x] Global anti-aliasing +#/ + [x] Fill triangles +#/ - Examples +#/ - Conway's Game of Life +#/ - Julia Set +#/ - Labyrinth +#/ - Chat +#/ - Graphics +#/ - Gamma correction +#/ - Custom fonts - integrate stb_truetype.h +#/ - UI +#/ - Icons +#/ - Folder widget +#/ - Clipboard +#/ - Images - BMP, PPM +#/ - Sound - WAV +#/ - Clipboard +#/ - Images - BMP, PPM +#/ - Sound - WAV +#/ - Dynamic libraries - load dependencies conditionally +#/ - X11 +#/ - Wayland +#/ - ALSA +#/ - Sockets +#/ - Windows +#/ - 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 +#/ - 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 +#/ - Switching canvas - Web +#/ - 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 +#/ - Testing +#/ - Stackless coroutines +#/ - Allocator +#/ - Profiling +#/ - Graphics +#/ - Font +#/ - Adaptive resolution +#/ - Oklab color +#/ - Relative coordinates +#/ - Alpha blending +#/ - Self-contained impl +#/ - Anti-aliasing +#/ - System +#/ - 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. +#/ +#/ ================================================================ +#/ +#/ Self-testing shell script +#/ +#/ ================================================================ + +SRC=${0##*./} +BIN=${SRC%.*} +gcc \ + -Wall -Wextra -Werror -pedantic \ + -Wno-missing-braces \ + -Wno-old-style-declaration \ + -Wno-overlength-strings \ + -Wno-unused-parameter \ + -Wno-unused-variable \ + -Wno-unused-but-set-variable \ + -O3 -D NDEBUG \ + -fsanitize=undefined,address,leak \ + -D RUNTIME_TEST_SUITE \ + -lm -lX11 -lasound \ + -lwayland-client \ + -o $BIN $SRC && \ + ./$BIN $@ +STATUS=$? +rm -f $BIN +exit $STATUS # */ +#endif + +// ================================================================ +// +// Types +// +// ================================================================ + +#ifndef TYPES_HEADER_GUARD_ +#define TYPES_HEADER_GUARD_ + +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 int c32; +typedef unsigned char b8; +typedef float f32; +typedef double f64; + +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 EVERY_TEST_SUITE +#define RUNTIME_TEST_SUITE +#endif + +#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 + +// ================================================================ +// +// Options +// +// ================================================================ + +#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 + +// ================================================================ +// +// 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 { + 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 disable_gamma : 1; // TODO + 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]; + + i64 memory_buffer_size; + u8 *memory_buffer; +} Platform; + +// UTF-8 +// NOTE: We need UTF-8 because we use Xutf8LookupString on Xlib. +i8 utf8_size (c32 c); +UTF8_Char utf8_read (i64 len, c8 *s); +i8 utf8_write(c32 c, c8 *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 +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 size) { + for (i64 i = 0; i < size; ++i) + ((u8 *) dst)[i] = x; +} + +static void mem_cpy_(void *dst, void *src, i64 size) { + for (i64 i = 0; i < size; ++i) + ((u8 *) dst)[i] = ((u8 *) src)[i]; +} + +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; +} + +// ================================================================ +// +// 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 +// +// ================================================================ + +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; +} + +// ================================================================ +// +// UTF-8 +// +// ================================================================ + +i8 utf8_size(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; +} + +// ================================================================ +// +// Colors +// +// ================================================================ + +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 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]; + + // FIXME, PERF: Use better sorting algorithm, e.g. merge sort. + for (i64 i = 0; i < (i64) NUM_FRAMES_AVERAGED; ++i) + for (i64 j = i + 1; j < NUM_FRAMES_AVERAGED; ++j) + if (durs[i] < durs[j]) { + i64 t = durs[i]; + durs[i] = durs[j]; + durs[j] = t; + } + + 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. + // + // FIXME: Check if the scale is 1:1. + { + 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); + + 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; + + for (; d < d_end; ++d, ++im) + *d = rgb_u32_from_f32_((vec3_f32) { .x = im->x * k, .y = im->y * k, .z = im->z * k, }) | 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); + + // TODO, PERF: Improve performance. + + u32 x_ratio = (src_width << 16) / dst_width; + u32 y_ratio = (src_height << 16) / dst_height; + + if (src_len < dst_len) + for (u32 j = dst_height - 1;; --j) { + u32 jj_w = ((j * y_ratio) >> 16) * src_width; + u32 j_w = j * dst_width; + for (u32 i = dst_width - 1;; --i) { + u32 ii = (i * x_ratio) >> 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) >> 16) * src_width; + u32 j_w = j * dst_width; + for (u32 i = 0; i < dst_width; ++i) { + u32 ii = (i * x_ratio) >> 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'; +} + +// ================================================================ +// +// Dynamic libraries +// +// ================================================================ + +#if defined(__unix__) +#include + +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) { + dlclose(_dynamic_libraries.slots[slot].handle); + _dynamic_libraries.slots[slot].handle = NULL; + } + + void *h = dlopen(name, RTLD_LAZY); + + if (h == NULL) { + LOG_error("Failed to open: %s", name); + LOG_error("%s", dlerror()); + return; + } + + _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 = dlsym(_dynamic_libraries.slots[slot].handle, proc); + + if (proc_address == NULL) { + LOG_error("Failed to get: %s", proc); + LOG_error("%s", dlerror()); + } + + 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) + dlclose(_dynamic_libraries.slots[i].handle); + resize_dynamic_array_exact(&_dynamic_libraries.num_slots, (void **) &_dynamic_libraries.slots, sizeof *_dynamic_libraries.slots, 0); +} +#endif // defined(__unix__) + +#if defined(__wasm__) +void dynamic_library_open(u16 slot, c8 *name) { + (void) slot; + (void) name; + + LOG_error("Dynamic library not found: %s", name); +} + +void *dynamic_library_get_proc_address(u16 slot, c8 *proc) { + (void) slot; + (void) proc; + + LOG_error("Proc address not found: %s", proc); + return NULL; +} +#endif // defined(__wasm__) + +// ================================================================ +// +// PLATFORM-SPECIFIC CODE +// +// ================================================================ +// +// 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; + } + + _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; + + 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 update_and_render_frame(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 +// +// ================================================================ + +#ifdef RUNTIME_TEST_SUITE + +#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_size(1), 1); + REQUIRE_EQ(utf8_size(127), 1); + REQUIRE_EQ(utf8_size(128), 2); + REQUIRE_EQ(utf8_size((1 << 11) - 1), 2); + REQUIRE_EQ(utf8_size((1 << 11)), 3); + REQUIRE_EQ(utf8_size((1 << 16) - 1), 3); + REQUIRE_EQ(utf8_size((1 << 16)), 4); +} + +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); +} + +#ifndef EVERY_TEST_SUITE +void update_and_render_frame(void) {} + +i32 main(i32 argc, c8 **argv) { + return run_tests(argc, argv); +} +#endif + +#undef TEST_FILE +#endif // RUNTIME_TEST_SUITE + +// ================================================================ + +#endif // RUNTIME_IMPL_GUARD_ +#endif // RUNTIME_HEADER -- cgit v1.2.3