summaryrefslogtreecommitdiff
path: root/runtime.c
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2025-04-20 18:23:01 +0200
committerMitya Selivanov <automainint@guattari.tech>2025-04-20 18:23:01 +0200
commitca73b12180ad0dd38e73f19bab81807a43460451 (patch)
tree5e569120b62ce042b262deb04e13677ad3d907d4 /runtime.c
parentbcf1992714fb95d4c4dd16f6b27f8767205bb9f3 (diff)
downloadreduced_system_layer-ca73b12180ad0dd38e73f19bab81807a43460451.zip
Rename to Runtime
Diffstat (limited to 'runtime.c')
-rwxr-xr-xruntime.c4998
1 files changed, 4998 insertions, 0 deletions
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 <guattari.tech>
+#/
+#/ 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 <math.h>
+#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 <stdio.h>
+#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 <dlfcn.h>
+
+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 <time.h>
+#include <sched.h>
+#include <unistd.h>
+
+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 <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+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 <alsa/asoundlib.h>
+
+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 <sys/mman.h>
+#include <wayland-client.h>
+#include <wayland-util.h>
+
+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 <sys/stat.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#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