diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index 03bde82c2..60ab70a0c --- a/build.sh +++ b/build.sh @@ -10,6 +10,8 @@ if [ -v debug ]; then echo "[debug mode]"; fi if [ -v release ]; then echo "[release mode]"; fi if [ -v clang ]; then compiler="${CC:-clang}"; echo "[clang compile]"; fi if [ -v gcc ]; then compiler="${CC:-gcc}"; echo "[gcc compile]"; fi +if [ -v asan ]; then echo "[AddressSanitizer enabled]"; fi + # --- Unpack Command Line Build Arguments ------------------------------------- auto_compile_flags='' @@ -19,24 +21,35 @@ git_hash=$(git rev-parse HEAD) git_hash_full=$(git rev-parse HEAD) # --- Compile/Link Line Definitions ------------------------------------------- -clang_common="-I../src/ -I../local/ -g -DBUILD_GIT_HASH=\"$git_hash\" -DBUILD_GIT_HASH_FULL=\"$git_hash_full\" -Wno-unknown-warning-option -fdiagnostics-absolute-paths -Wall -Wno-missing-braces -Wno-unused-function -Wno-writable-strings -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-register -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion -Wno-compare-distinct-pointer-types -Wno-initializer-overrides -Wno-incompatible-pointer-types-discards-qualifiers -Wno-for-loop-analysis -Xclang -flto-visibility-public-std -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf" +# AddressSanitizer flags +asan_flags="" +if [ -v asan ]; then + asan_flags="-fsanitize=address -fno-omit-frame-pointer" +fi + +clang_common="-I../src/ -I../local/ -I/usr/include/freetype2 -g -DBUILD_GIT_HASH=\"$git_hash\" -DBUILD_GIT_HASH_FULL=\"$git_hash_full\" -Wno-unknown-warning-option -fdiagnostics-absolute-paths -Wall -Wno-missing-braces -Wno-unused-function -Wno-writable-strings -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-register -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion -Wno-compare-distinct-pointer-types -Wno-initializer-overrides -Wno-incompatible-pointer-types-discards-qualifiers -Wno-for-loop-analysis -Xclang -flto-visibility-public-std -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf -ferror-limit=1000000 $asan_flags" clang_debug="$compiler -g -O0 -DBUILD_DEBUG=1 ${clang_common} ${auto_compile_flags}" clang_release="$compiler -g -O2 -DBUILD_DEBUG=0 ${clang_common} ${auto_compile_flags}" -clang_link="-lpthread -lm -lrt -ldl" +clang_link="-lpthread -lm -lrt -ldl -lfreetype" clang_out="-o" -gcc_common="-I../src/ -I../local/ -g -DBUILD_GIT_HASH=\"$git_hash\" -DBUILD_GIT_HASH_FULL=\"$git_hash_full\" -Wno-unknown-warning-option -Wall -Wno-missing-braces -Wno-unused-function -Wno-attributes -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-compare-distinct-pointer-types -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf" +gcc_common="-I../src/ -I../local/ -I/usr/include/freetype2 -g -DBUILD_GIT_HASH=\"$git_hash\" -DBUILD_GIT_HASH_FULL=\"$git_hash_full\" -Wno-unknown-warning-option -Wall -Wno-missing-braces -Wno-unused-function -Wno-attributes -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-compare-distinct-pointer-types -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf $asan_flags" gcc_debug="$compiler -g -O0 -DBUILD_DEBUG=1 ${gcc_common} ${auto_compile_flags}" gcc_release="$compiler -g -O2 -DBUILD_DEBUG=0 ${gcc_common} ${auto_compile_flags}" -gcc_link="-lpthread -lm -lrt -ldl" +gcc_link="-lpthread -lm -lrt -ldl -lfreetype" gcc_out="-o" +# Version without ASan for metagen +clang_common_no_asan="-I../src/ -I../local/ -I/usr/include/freetype2 -g -DBUILD_GIT_HASH=\"$git_hash\" -DBUILD_GIT_HASH_FULL=\"$git_hash_full\" -Wno-unknown-warning-option -fdiagnostics-absolute-paths -Wall -Wno-missing-braces -Wno-unused-function -Wno-writable-strings -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-register -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion -Wno-compare-distinct-pointer-types -Wno-initializer-overrides -Wno-incompatible-pointer-types-discards-qualifiers -Wno-for-loop-analysis -Xclang -flto-visibility-public-std -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf -ferror-limit=1000000" +gcc_common_no_asan="-I../src/ -I../local/ -I/usr/include/freetype2 -g -DBUILD_GIT_HASH=\"$git_hash\" -DBUILD_GIT_HASH_FULL=\"$git_hash_full\" -Wno-unknown-warning-option -Wall -Wno-missing-braces -Wno-unused-function -Wno-attributes -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-compare-distinct-pointer-types -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf" + # --- Per-Build Settings ------------------------------------------------------ link_dll="-fPIC" -link_os_gfx="-lX11 -lXext" +link_os_gfx="-lX11 -lXext -lXrandr -lGL -lGLEW" +link_agent="-lcurl" # --- Choose Compile/Link Lines ----------------------------------------------- -if [ -v gcc ]; then compile_debug="$gcc_debug"; fi -if [ -v gcc ]; then compile_release="$gcc_release"; fi +if [ -v gcc ]; then compile_debug="$gcc_debug -I/usr/local/include"; fi +if [ -v gcc ]; then compile_release="$gcc_release -I/usr/local/include"; fi if [ -v gcc ]; then compile_link="$gcc_link"; fi if [ -v gcc ]; then out="$gcc_out"; fi if [ -v clang ]; then compile_debug="$clang_debug"; fi @@ -46,6 +59,10 @@ if [ -v clang ]; then out="$clang_out"; fi if [ -v debug ]; then compile="$compile_debug"; fi if [ -v release ]; then compile="$compile_release"; fi +# Define special compiler flags for metagen (without ASan) +if [ -v gcc ]; then compile_metagen="$compiler -g -O0 -DBUILD_DEBUG=1 ${gcc_common_no_asan} ${auto_compile_flags} -I/usr/local/include"; fi +if [ -v clang ]; then compile_metagen="$compiler -g -O0 -DBUILD_DEBUG=1 ${clang_common_no_asan} ${auto_compile_flags}"; fi + # --- Prep Directories -------------------------------------------------------- mkdir -p build mkdir -p local @@ -69,6 +86,16 @@ if [ -v rdi_from_dwarf ]; then didbuild=1 && $compile ../src/rdi_from_dwa if [ -v rdi_dump ]; then didbuild=1 && $compile ../src/rdi_dump/rdi_dump_main.c $compile_link $out rdi_dump; fi if [ -v rdi_breakpad_from_pdb ]; then didbuild=1 && $compile ../src/rdi_breakpad_from_pdb/rdi_breakpad_from_pdb_main.c $compile_link $out rdi_breakpad_from_pdb; fi if [ -v ryan_scratch ]; then didbuild=1 && $compile ../src/scratch/ryan_scratch.c $compile_link $link_os_gfx $out ryan_scratch; fi +if [ -v whatsapp ]; then didbuild=1 && $compile ../src/whatsapp/whatsapp.c $compile_link $out whatsapp; fi +if [ -v agent ]; then didbuild=1 && $compile ../src/agent/agent.c $compile_link $link_agent $out agent; fi +if [ -v browser ]; then didbuild=1 && $compile ../src/browser/browser.c $compile_link $out browser; fi +if [ -v cad ]; then didbuild=1 && $compile ../src/cad/cad.c $compile_link $out cad; fi +if [ -v daw ]; then didbuild=1 && $compile ../src/daw/daw.c $compile_link $out daw; fi +if [ -v editor ]; then didbuild=1 && $compile ../src/editor/editor.c $compile_link $out editor; fi +if [ -v mail ]; then didbuild=1 && $compile ../src/mail/mail.c $compile_link $out mail; fi +if [ -v paint ]; then didbuild=1 && $compile ../src/paint/paint.c $compile_link $out paint; fi +if [ -v stockitup ]; then didbuild=1 && $compile ../src/stockitup/stockitup.c $compile_link $out stockitup; fi +if [ -v terminal ]; then didbuild=1 && $compile ../src/terminal/terminal.c $compile_link $out terminal; fi cd .. # --- Warn On No Builds ------------------------------------------------------- diff --git a/src/base/base_log.h b/src/base/base_log.h index 3687390eb..8abd671fc 100644 --- a/src/base/base_log.h +++ b/src/base/base_log.h @@ -1,65 +1,65 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef BASE_LOG_H -#define BASE_LOG_H - -//////////////////////////////// -//~ rjf: Log Types - -typedef enum LogMsgKind -{ - LogMsgKind_Info, - LogMsgKind_UserError, - LogMsgKind_COUNT -} -LogMsgKind; - -typedef struct LogScope LogScope; -struct LogScope -{ - LogScope *next; - U64 pos; - String8List strings[LogMsgKind_COUNT]; -}; - -typedef struct LogScopeResult LogScopeResult; -struct LogScopeResult -{ - String8 strings[LogMsgKind_COUNT]; -}; - -typedef struct Log Log; -struct Log -{ - Arena *arena; - LogScope *top_scope; -}; - -//////////////////////////////// -//~ rjf: Log Creation/Selection - -internal Log *log_alloc(void); -internal void log_release(Log *log); -internal void log_select(Log *log); - -//////////////////////////////// -//~ rjf: Log Building - -internal void log_msg(LogMsgKind kind, String8 string); -internal void log_msgf(LogMsgKind kind, char *fmt, ...); -#define log_info(s) log_msg(LogMsgKind_Info, (s)) -#define log_infof(fmt, ...) log_msgf(LogMsgKind_Info, (fmt), __VA_ARGS__) -#define log_user_error(s) log_msg(LogMsgKind_UserError, (s)) -#define log_user_errorf(fmt, ...) log_msgf(LogMsgKind_UserError, (fmt), __VA_ARGS__) - -#define LogInfoNamedBlock(s) DeferLoop(log_infof("%S:\n{\n", (s)), log_infof("}\n")) -#define LogInfoNamedBlockF(fmt, ...) DeferLoop((log_infof(fmt, __VA_ARGS__), log_infof(":\n{\n")), log_infof("}\n")) - -//////////////////////////////// -//~ rjf: Log Scopes - -internal void log_scope_begin(void); -internal LogScopeResult log_scope_end(Arena *arena); - -#endif // BASE_LOG_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_LOG_H +#define BASE_LOG_H + +//////////////////////////////// +//~ rjf: Log Types + +typedef enum LogMsgKind +{ + LogMsgKind_Info, + LogMsgKind_UserError, + LogMsgKind_COUNT +} +LogMsgKind; + +typedef struct LogScope LogScope; +struct LogScope +{ + LogScope *next; + U64 pos; + String8List strings[LogMsgKind_COUNT]; +}; + +typedef struct LogScopeResult LogScopeResult; +struct LogScopeResult +{ + String8 strings[LogMsgKind_COUNT]; +}; + +typedef struct Log Log; +struct Log +{ + Arena *arena; + LogScope *top_scope; +}; + +//////////////////////////////// +//~ rjf: Log Creation/Selection + +internal Log *log_alloc(void); +internal void log_release(Log *log); +internal void log_select(Log *log); + +//////////////////////////////// +//~ rjf: Log Building + +internal void log_msg(LogMsgKind kind, String8 string); +internal void log_msgf(LogMsgKind kind, char *fmt, ...); +#define log_info(s) log_msg(LogMsgKind_Info, (s)) +#define log_infof(fmt, ...) log_msgf(LogMsgKind_Info, (fmt), ##__VA_ARGS__) +#define log_user_error(s) log_msg(LogMsgKind_UserError, (s)) +#define log_user_errorf(fmt, ...) log_msgf(LogMsgKind_UserError, (fmt), ##__VA_ARGS__) + +#define LogInfoNamedBlock(s) DeferLoop(log_infof("%S:\n{\n", (s)), log_infof("}\n")) +#define LogInfoNamedBlockF(fmt, ...) DeferLoop((log_infof(fmt, ##__VA_ARGS__), log_infof(":\n{\n")), log_infof("}\n")) + +//////////////////////////////// +//~ rjf: Log Scopes + +internal void log_scope_begin(void); +internal LogScopeResult log_scope_end(Arena *arena); + +#endif // BASE_LOG_H diff --git a/src/base/base_math.c b/src/base/base_math.c index 2c6201f3e..fe9545146 100644 --- a/src/base/base_math.c +++ b/src/base/base_math.c @@ -291,6 +291,20 @@ mul_4x4f32(Mat4x4F32 a, Mat4x4F32 b) return c; } +internal Mat4x4F32 +transpose_4x4f32(Mat4x4F32 m) +{ + Mat4x4F32 result; + for(int j = 0; j < 4; j += 1) + { + for(int i = 0; i < 4; i += 1) + { + result.v[i][j] = m.v[j][i]; + } + } + return result; +} + internal Mat4x4F32 scale_4x4f32(Mat4x4F32 m, F32 scale) { diff --git a/src/base/base_math.h b/src/base/base_math.h index b6063ad58..520ce3571 100644 --- a/src/base/base_math.h +++ b/src/base/base_math.h @@ -540,6 +540,7 @@ internal Mat4x4F32 make_orthographic_4x4f32(F32 left, F32 right, F32 bottom, F32 internal Mat4x4F32 make_look_at_4x4f32(Vec3F32 eye, Vec3F32 center, Vec3F32 up); internal Mat4x4F32 make_rotate_4x4f32(Vec3F32 axis, F32 turns); internal Mat4x4F32 mul_4x4f32(Mat4x4F32 a, Mat4x4F32 b); +internal Mat4x4F32 transpose_4x4f32(Mat4x4F32 m); internal Mat4x4F32 scale_4x4f32(Mat4x4F32 m, F32 scale); internal Mat4x4F32 inverse_4x4f32(Mat4x4F32 m); internal Mat4x4F32 derotate_4x4f32(Mat4x4F32 mat); diff --git a/src/base/base_strings.c b/src/base/base_strings.c index 9e42e0cd6..717c4fa85 100644 --- a/src/base/base_strings.c +++ b/src/base/base_strings.c @@ -1,2446 +1,2455 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Third Party Includes - -#if !BUILD_SUPPLEMENTARY_UNIT -# define STB_SPRINTF_IMPLEMENTATION -# define STB_SPRINTF_STATIC -# include "third_party/stb/stb_sprintf.h" -#endif - -//////////////////////////////// -//~ NOTE(allen): String <-> Integer Tables - -read_only global U8 integer_symbols[16] = { - '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', -}; - -// NOTE(allen): Includes reverses for uppercase and lowercase hex. -read_only global U8 integer_symbol_reverse[128] = { - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, -}; - -read_only global U8 base64[64] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - '_', '$', -}; - -read_only global U8 base64_reverse[128] = { - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0x00, - 0xFF,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32, - 0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0x3E, - 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, - 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0xFF,0xFF,0xFF,0xFF,0xFF, -}; - -//////////////////////////////// -//~ rjf: Character Classification & Conversion Functions - -internal B32 -char_is_space(U8 c){ - return(c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f' || c == '\v'); -} - -internal B32 -char_is_upper(U8 c){ - return('A' <= c && c <= 'Z'); -} - -internal B32 -char_is_lower(U8 c){ - return('a' <= c && c <= 'z'); -} - -internal B32 -char_is_alpha(U8 c){ - return(char_is_upper(c) || char_is_lower(c)); -} - -internal B32 -char_is_slash(U8 c){ - return(c == '/' || c == '\\'); -} - -internal B32 -char_is_digit(U8 c, U32 base){ - B32 result = 0; - if (0 < base && base <= 16){ - U8 val = integer_symbol_reverse[c]; - if (val < base){ - result = 1; - } - } - return(result); -} - -internal U8 -char_to_lower(U8 c){ - if (char_is_upper(c)){ - c += ('a' - 'A'); - } - return(c); -} - -internal U8 -char_to_upper(U8 c){ - if (char_is_lower(c)){ - c += ('A' - 'a'); - } - return(c); -} - -internal U8 -char_to_correct_slash(U8 c){ - if(char_is_slash(c)){ - c = '/'; - } - return(c); -} - -//////////////////////////////// -//~ rjf: C-String Measurement - -internal U64 -cstring8_length(U8 *c){ - U8 *p = c; - for (;*p != 0; p += 1); - return(p - c); -} - -internal U64 -cstring16_length(U16 *c){ - U16 *p = c; - for (;*p != 0; p += 1); - return(p - c); -} - -internal U64 -cstring32_length(U32 *c){ - U32 *p = c; - for (;*p != 0; p += 1); - return(p - c); -} - -//////////////////////////////// -//~ rjf: String Constructors - -internal String8 -str8(U8 *str, U64 size){ - String8 result = {str, size}; - return(result); -} - -internal String8 -str8_range(U8 *first, U8 *one_past_last){ - String8 result = {first, (U64)(one_past_last - first)}; - return(result); -} - -internal String8 -str8_zero(void){ - String8 result = {0}; - return(result); -} - -internal String16 -str16(U16 *str, U64 size){ - String16 result = {str, size}; - return(result); -} - -internal String16 -str16_range(U16 *first, U16 *one_past_last){ - String16 result = {first, (U64)(one_past_last - first)}; - return(result); -} - -internal String16 -str16_zero(void){ - String16 result = {0}; - return(result); -} - -internal String32 -str32(U32 *str, U64 size){ - String32 result = {str, size}; - return(result); -} - -internal String32 -str32_range(U32 *first, U32 *one_past_last){ - String32 result = {first, (U64)(one_past_last - first)}; - return(result); -} - -internal String32 -str32_zero(void){ - String32 result = {0}; - return(result); -} - -internal String8 -str8_cstring(char *c){ - String8 result = {(U8*)c, cstring8_length((U8*)c)}; - return(result); -} - -internal String16 -str16_cstring(U16 *c){ - String16 result = {(U16*)c, cstring16_length((U16*)c)}; - return(result); -} - -internal String32 -str32_cstring(U32 *c){ - String32 result = {(U32*)c, cstring32_length((U32*)c)}; - return(result); -} - -internal String8 -str8_cstring_capped(void *cstr, void *cap) -{ - char *ptr = (char *)cstr; - char *opl = (char *)cap; - for (;ptr < opl && *ptr != 0; ptr += 1); - U64 size = (U64)(ptr - (char *)cstr); - String8 result = str8((U8*)cstr, size); - return result; -} - -internal String16 -str16_cstring_capped(void *cstr, void *cap) -{ - U16 *ptr = (U16 *)cstr; - U16 *opl = (U16 *)cap; - for (;ptr < opl && *ptr != 0; ptr += 1); - U64 size = (U64)(ptr - (U16 *)cstr); - String16 result = str16(cstr, size); - return result; -} - -internal String8 -str8_cstring_capped_reverse(void *raw_start, void *raw_cap) -{ - U8 *start = raw_start; - U8 *ptr = raw_cap; - for(; ptr > start; ) - { - ptr -= 1; - - if (*ptr == '\0') - { - break; - } - } - U64 size = (U64)(ptr - start); - String8 result = str8(start, size); - return result; -} - -//////////////////////////////// -//~ rjf: String Stylization - -internal String8 -upper_from_str8(Arena *arena, String8 string) -{ - string = push_str8_copy(arena, string); - for(U64 idx = 0; idx < string.size; idx += 1) - { - string.str[idx] = char_to_upper(string.str[idx]); - } - return string; -} - -internal String8 -lower_from_str8(Arena *arena, String8 string) -{ - string = push_str8_copy(arena, string); - for(U64 idx = 0; idx < string.size; idx += 1) - { - string.str[idx] = char_to_lower(string.str[idx]); - } - return string; -} - -internal String8 -backslashed_from_str8(Arena *arena, String8 string) -{ - string = push_str8_copy(arena, string); - for(U64 idx = 0; idx < string.size; idx += 1) - { - string.str[idx] = char_is_slash(string.str[idx]) ? '\\' : string.str[idx]; - } - return string; -} - -//////////////////////////////// -//~ rjf: String Matching - -internal B32 -str8_match(String8 a, String8 b, StringMatchFlags flags) -{ - B32 result = 0; - if(a.size == b.size && flags == 0) - { - result = MemoryMatch(a.str, b.str, b.size); - } - else if(a.size == b.size || (flags & StringMatchFlag_RightSideSloppy)) - { - B32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive); - B32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive); - U64 size = Min(a.size, b.size); - result = 1; - for(U64 i = 0; i < size; i += 1) - { - U8 at = a.str[i]; - U8 bt = b.str[i]; - if(case_insensitive) - { - at = char_to_upper(at); - bt = char_to_upper(bt); - } - if(slash_insensitive) - { - at = char_to_correct_slash(at); - bt = char_to_correct_slash(bt); - } - if(at != bt) - { - result = 0; - break; - } - } - } - return result; -} - -internal U64 -str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags){ - U8 *p = string.str + start_pos; - U64 stop_offset = Max(string.size + 1, needle.size) - needle.size; - U8 *stop_p = string.str + stop_offset; - if (needle.size > 0){ - U8 *string_opl = string.str + string.size; - String8 needle_tail = str8_skip(needle, 1); - StringMatchFlags adjusted_flags = flags | StringMatchFlag_RightSideSloppy; - U8 needle_first_char_adjusted = needle.str[0]; - if(adjusted_flags & StringMatchFlag_CaseInsensitive){ - needle_first_char_adjusted = char_to_upper(needle_first_char_adjusted); - } - for (;p < stop_p; p += 1){ - U8 haystack_char_adjusted = *p; - if(adjusted_flags & StringMatchFlag_CaseInsensitive){ - haystack_char_adjusted = char_to_upper(haystack_char_adjusted); - } - if (haystack_char_adjusted == needle_first_char_adjusted){ - if (str8_match(str8_range(p + 1, string_opl), needle_tail, adjusted_flags)){ - break; - } - } - } - } - U64 result = string.size; - if (p < stop_p){ - result = (U64)(p - string.str); - } - return(result); -} - -internal U64 -str8_find_needle_reverse(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags) -{ - U64 result = 0; - for(S64 i = string.size - start_pos - needle.size; i >= 0; --i) - { - String8 haystack = str8_substr(string, rng_1u64(i, i + needle.size)); - if(str8_match(haystack, needle, flags)) - { - result = (U64)i + needle.size; - break; - } - } - return result; -} - -internal B32 -str8_ends_with(String8 string, String8 end, StringMatchFlags flags){ - String8 postfix = str8_postfix(string, end.size); - B32 is_match = str8_match(end, postfix, flags); - return is_match; -} - -//////////////////////////////// -//~ rjf: String Slicing - -internal String8 -str8_substr(String8 str, Rng1U64 range){ - range.min = ClampTop(range.min, str.size); - range.max = ClampTop(range.max, str.size); - str.str += range.min; - str.size = dim_1u64(range); - return(str); -} - -internal String8 -str8_prefix(String8 str, U64 size){ - str.size = ClampTop(size, str.size); - return(str); -} - -internal String8 -str8_skip(String8 str, U64 amt){ - amt = ClampTop(amt, str.size); - str.str += amt; - str.size -= amt; - return(str); -} - -internal String8 -str8_postfix(String8 str, U64 size){ - size = ClampTop(size, str.size); - str.str = (str.str + str.size) - size; - str.size = size; - return(str); -} - -internal String8 -str8_chop(String8 str, U64 amt){ - amt = ClampTop(amt, str.size); - str.size -= amt; - return(str); -} - -internal String8 -str8_skip_chop_whitespace(String8 string){ - U8 *first = string.str; - U8 *opl = first + string.size; - for (;first < opl; first += 1){ - if (!char_is_space(*first)){ - break; - } - } - for (;opl > first;){ - opl -= 1; - if (!char_is_space(*opl)){ - opl += 1; - break; - } - } - String8 result = str8_range(first, opl); - return(result); -} - -//////////////////////////////// -//~ rjf: String Formatting & Copying - -internal String8 -push_str8_cat(Arena *arena, String8 s1, String8 s2){ - String8 str; - str.size = s1.size + s2.size; - str.str = push_array_no_zero(arena, U8, str.size + 1); - MemoryCopy(str.str, s1.str, s1.size); - MemoryCopy(str.str + s1.size, s2.str, s2.size); - str.str[str.size] = 0; - return(str); -} - -internal String8 -push_str8_copy(Arena *arena, String8 s){ - String8 str; - str.size = s.size; - str.str = push_array_no_zero(arena, U8, str.size + 1); - MemoryCopy(str.str, s.str, s.size); - str.str[str.size] = 0; - return(str); -} - -internal String8 -push_str8fv(Arena *arena, char *fmt, va_list args){ - va_list args2; - va_copy(args2, args); - U32 needed_bytes = raddbg_vsnprintf(0, 0, fmt, args) + 1; - String8 result = {0}; - result.str = push_array_no_zero(arena, U8, needed_bytes); - result.size = raddbg_vsnprintf((char*)result.str, needed_bytes, fmt, args2); - result.str[result.size] = 0; - va_end(args2); - return(result); -} - -internal String8 -push_str8f(Arena *arena, char *fmt, ...){ - va_list args; - va_start(args, fmt); - String8 result = push_str8fv(arena, fmt, args); - va_end(args); - return(result); -} - -//////////////////////////////// -//~ rjf: String <=> Integer Conversions - -//- rjf: string -> integer - -internal S64 -sign_from_str8(String8 string, String8 *string_tail){ - // count negative signs - U64 neg_count = 0; - U64 i = 0; - for (; i < string.size; i += 1){ - if (string.str[i] == '-'){ - neg_count += 1; - } - else if (string.str[i] != '+'){ - break; - } - } - - // output part of string after signs - *string_tail = str8_skip(string, i); - - // output integer sign - S64 sign = (neg_count & 1)?-1:+1; - return(sign); -} - -internal B32 -str8_is_integer(String8 string, U32 radix){ - B32 result = 0; - if (string.size > 0){ - if (1 < radix && radix <= 16){ - result = 1; - for (U64 i = 0; i < string.size; i += 1){ - U8 c = string.str[i]; - if (!(c < 0x80) || integer_symbol_reverse[c] >= radix){ - result = 0; - break; - } - } - } - } - return(result); -} - -internal U64 -u64_from_str8(String8 string, U32 radix){ - U64 x = 0; - if (1 < radix && radix <= 16){ - for (U64 i = 0; i < string.size; i += 1){ - x *= radix; - x += integer_symbol_reverse[string.str[i]&0x7F]; - } - } - return(x); -} - -internal S64 -s64_from_str8(String8 string, U32 radix){ - S64 sign = sign_from_str8(string, &string); - S64 x = (S64)u64_from_str8(string, radix) * sign; - return(x); -} - -internal U32 -u32_from_str8(String8 string, U32 radix) -{ - U64 x64 = u64_from_str8(string, radix); - U32 x32 = safe_cast_u32(x64); - return x32; -} - -internal S32 -s32_from_str8(String8 string, U32 radix) -{ - S64 x64 = s64_from_str8(string, radix); - S32 x32 = safe_cast_s32(x64); - return x32; -} - -internal B32 -try_u64_from_str8_c_rules(String8 string, U64 *x){ - B32 is_integer = 0; - if (str8_is_integer(string, 10)){ - is_integer = 1; - *x = u64_from_str8(string, 10); - } - else{ - String8 hex_string = str8_skip(string, 2); - if (str8_match(str8_prefix(string, 2), str8_lit("0x"), 0) && - str8_is_integer(hex_string, 0x10)){ - is_integer = 1; - *x = u64_from_str8(hex_string, 0x10); - } - else if (str8_match(str8_prefix(string, 2), str8_lit("0b"), 0) && - str8_is_integer(hex_string, 2)){ - is_integer = 1; - *x = u64_from_str8(hex_string, 2); - } - else{ - String8 oct_string = str8_skip(string, 1); - if (str8_match(str8_prefix(string, 1), str8_lit("0"), 0) && - str8_is_integer(hex_string, 010)){ - is_integer = 1; - *x = u64_from_str8(oct_string, 010); - } - } - } - return(is_integer); -} - -internal B32 -try_s64_from_str8_c_rules(String8 string, S64 *x){ - String8 string_tail = {0}; - S64 sign = sign_from_str8(string, &string_tail); - U64 x_u64 = 0; - B32 is_integer = try_u64_from_str8_c_rules(string_tail, &x_u64); - *x = x_u64*sign; - return(is_integer); -} - -//- rjf: integer -> string - -internal String8 -str8_from_memory_size(Arena *arena, U64 size) -{ - String8 result; - - if(size < KB(1)) - { - result = push_str8f(arena, "%llu Bytes", size); - } - else if(size < MB(1)) - { - result = push_str8f(arena, "%llu.%02llu KiB", size / KB(1), ((size * 100) / KB(1)) % 100); - } - else if(size < GB(1)) - { - result = push_str8f(arena, "%llu.%02llu MiB", size / MB(1), ((size * 100) / MB(1)) % 100); - } - else if(size < TB(1)) - { - result = push_str8f(arena, "%llu.%02llu GiB", size / GB(1), ((size * 100) / GB(1)) % 100); - } - else - { - result = push_str8f(arena, "%llu.%02llu TiB", size / TB(1), ((size * 100) / TB(1)) % 100); - } - - return result; -} - -internal String8 -str8_from_count(Arena *arena, U64 count) -{ - String8 result; - - if(count < 1 * 1000) - { - result = push_str8f(arena, "%llu", count); - } - else if(count < 1000000) - { - U64 frac = ((count * 100) / 1000) % 100; - if(frac > 0) - { - result = push_str8f(arena, "%llu.%02lluK", count / 1000, frac); - } - else - { - result = push_str8f(arena, "%lluK", count / 1000); - } - } - else if(count < 1000000000) - { - U64 frac = ((count * 100) / 1000000) % 100; - if(frac > 0) - { - result = push_str8f(arena, "%llu.%02lluM", count / 1000000, frac); - } - else - { - result = push_str8f(arena, "%lluM", count / 1000000); - } - } - else - { - U64 frac = ((count * 100) * 1000000000) % 100; - if(frac > 0) - { - result = push_str8f(arena, "%llu.%02lluB", count / 1000000000, frac); - } - else - { - result = push_str8f(arena, "%lluB", count / 1000000000, frac); - } - } - - return result; -} - -internal String8 -str8_from_bits_u32(Arena *arena, U32 x) -{ - U8 c0 = 'a' + ((x >> 28) & 0xf); - U8 c1 = 'a' + ((x >> 24) & 0xf); - U8 c2 = 'a' + ((x >> 20) & 0xf); - U8 c3 = 'a' + ((x >> 16) & 0xf); - U8 c4 = 'a' + ((x >> 12) & 0xf); - U8 c5 = 'a' + ((x >> 8) & 0xf); - U8 c6 = 'a' + ((x >> 4) & 0xf); - U8 c7 = 'a' + ((x >> 0) & 0xf); - String8 result = push_str8f(arena, "%c%c%c%c%c%c%c%c", c0, c1, c2, c3, c4, c5, c6, c7); - return result; -} - -internal String8 -str8_from_bits_u64(Arena *arena, U64 x) -{ - U8 c0 = 'a' + ((x >> 60) & 0xf); - U8 c1 = 'a' + ((x >> 56) & 0xf); - U8 c2 = 'a' + ((x >> 52) & 0xf); - U8 c3 = 'a' + ((x >> 48) & 0xf); - U8 c4 = 'a' + ((x >> 44) & 0xf); - U8 c5 = 'a' + ((x >> 40) & 0xf); - U8 c6 = 'a' + ((x >> 36) & 0xf); - U8 c7 = 'a' + ((x >> 32) & 0xf); - U8 c8 = 'a' + ((x >> 28) & 0xf); - U8 c9 = 'a' + ((x >> 24) & 0xf); - U8 ca = 'a' + ((x >> 20) & 0xf); - U8 cb = 'a' + ((x >> 16) & 0xf); - U8 cc = 'a' + ((x >> 12) & 0xf); - U8 cd = 'a' + ((x >> 8) & 0xf); - U8 ce = 'a' + ((x >> 4) & 0xf); - U8 cf = 'a' + ((x >> 0) & 0xf); - String8 result = push_str8f(arena, - "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", - c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf); - return result; -} - -internal String8 -str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator) -{ - String8 result = {0}; - { - // rjf: prefix - String8 prefix = {0}; - switch(radix) - { - case 16:{prefix = str8_lit("0x");}break; - case 8: {prefix = str8_lit("0o");}break; - case 2: {prefix = str8_lit("0b");}break; - } - - // rjf: determine # of chars between separators - U8 digit_group_size = 3; - switch(radix) - { - default:break; - case 2: - case 8: - case 16: - {digit_group_size = 4;}break; - } - - // rjf: prep - U64 needed_leading_0s = 0; - { - U64 needed_digits = 1; - { - U64 u64_reduce = u64; - for(;;) - { - u64_reduce /= radix; - if(u64_reduce == 0) - { - break; - } - needed_digits += 1; - } - } - needed_leading_0s = (min_digits > needed_digits) ? min_digits - needed_digits : 0; - U64 needed_separators = 0; - if(digit_group_separator != 0) - { - needed_separators = (needed_digits+needed_leading_0s)/digit_group_size; - if(needed_separators > 0 && (needed_digits+needed_leading_0s)%digit_group_size == 0) - { - needed_separators -= 1; - } - } - result.size = prefix.size + needed_leading_0s + needed_separators + needed_digits; - result.str = push_array_no_zero(arena, U8, result.size + 1); - result.str[result.size] = 0; - } - - // rjf: fill contents - { - U64 u64_reduce = u64; - U64 digits_until_separator = digit_group_size; - for(U64 idx = 0; idx < result.size; idx += 1) - { - if(digits_until_separator == 0 && digit_group_separator != 0) - { - result.str[result.size - idx - 1] = digit_group_separator; - digits_until_separator = digit_group_size+1; - } - else - { - result.str[result.size - idx - 1] = char_to_lower(integer_symbols[u64_reduce%radix]); - u64_reduce /= radix; - } - digits_until_separator -= 1; - if(u64_reduce == 0) - { - break; - } - } - for(U64 leading_0_idx = 0; leading_0_idx < needed_leading_0s; leading_0_idx += 1) - { - result.str[prefix.size + leading_0_idx] = '0'; - } - } - - // rjf: fill prefix - if(prefix.size != 0) - { - MemoryCopy(result.str, prefix.str, prefix.size); - } - } - return result; -} - -internal String8 -str8_from_s64(Arena *arena, S64 s64, U32 radix, U8 min_digits, U8 digit_group_separator) -{ - String8 result = {0}; - // TODO(rjf): preeeeetty sloppy... - if(s64 < 0) - { - Temp scratch = scratch_begin(&arena, 1); - String8 numeric_part = str8_from_u64(scratch.arena, (U64)(-s64), radix, min_digits, digit_group_separator); - result = push_str8f(arena, "-%S", numeric_part); - scratch_end(scratch); - } - else - { - result = str8_from_u64(arena, (U64)s64, radix, min_digits, digit_group_separator); - } - return result; -} - -//////////////////////////////// -//~ rjf: String <=> Float Conversions - -internal F64 -f64_from_str8(String8 string) -{ - // TODO(rjf): crappy implementation for now that just uses atof. - F64 result = 0; - if(string.size > 0) - { - // rjf: find starting pos of numeric string, as well as sign - F64 sign = +1.0; - if(string.str[0] == '-') - { - sign = -1.0; - } - else if(string.str[0] == '+') - { - sign = 1.0; - } - - // rjf: gather numerics - U64 num_valid_chars = 0; - char buffer[64]; - B32 exp = 0; - for(U64 idx = 0; idx < string.size && num_valid_chars < sizeof(buffer)-1; idx += 1) - { - if(char_is_digit(string.str[idx], 10) || string.str[idx] == '.' || string.str[idx] == 'e' || - (exp && (string.str[idx] == '+' || string.str[idx] == '-'))) - { - buffer[num_valid_chars] = string.str[idx]; - num_valid_chars += 1; - exp = 0; - exp = (string.str[idx] == 'e'); - } - } - - // rjf: null-terminate (the reason for all of this!!!!!!) - buffer[num_valid_chars] = 0; - - // rjf: do final conversion - result = sign * atof(buffer); - } - return result; -} - -//////////////////////////////// -//~ rjf: String List Construction Functions - -internal String8Node* -str8_list_push_node(String8List *list, String8Node *node){ - SLLQueuePush(list->first, list->last, node); - list->node_count += 1; - list->total_size += node->string.size; - return(node); -} - -internal String8Node* -str8_list_push_node_set_string(String8List *list, String8Node *node, String8 string){ - SLLQueuePush(list->first, list->last, node); - list->node_count += 1; - list->total_size += string.size; - node->string = string; - return(node); -} - -internal String8Node* -str8_list_push_node_front(String8List *list, String8Node *node){ - SLLQueuePushFront(list->first, list->last, node); - list->node_count += 1; - list->total_size += node->string.size; - return(node); -} - -internal String8Node* -str8_list_push_node_front_set_string(String8List *list, String8Node *node, String8 string){ - SLLQueuePushFront(list->first, list->last, node); - list->node_count += 1; - list->total_size += string.size; - node->string = string; - return(node); -} - -internal String8Node* -str8_list_push(Arena *arena, String8List *list, String8 string){ - String8Node *node = push_array_no_zero(arena, String8Node, 1); - str8_list_push_node_set_string(list, node, string); - return(node); -} - -internal String8Node* -str8_list_push_front(Arena *arena, String8List *list, String8 string){ - String8Node *node = push_array_no_zero(arena, String8Node, 1); - str8_list_push_node_front_set_string(list, node, string); - return(node); -} - -internal void -str8_list_concat_in_place(String8List *list, String8List *to_push){ - if(to_push->node_count != 0){ - if (list->last){ - list->node_count += to_push->node_count; - list->total_size += to_push->total_size; - list->last->next = to_push->first; - list->last = to_push->last; - } - else{ - *list = *to_push; - } - MemoryZeroStruct(to_push); - } -} - -internal String8Node* -str8_list_push_aligner(Arena *arena, String8List *list, U64 min, U64 align){ - String8Node *node = push_array_no_zero(arena, String8Node, 1); - U64 new_size = list->total_size + min; - U64 increase_size = 0; - if (align > 1){ - // NOTE(allen): assert is power of 2 - Assert(((align - 1) & align) == 0); - U64 mask = align - 1; - new_size += mask; - new_size &= (~mask); - increase_size = new_size - list->total_size; - } - local_persist const U8 zeroes_buffer[64] = {0}; - Assert(increase_size <= ArrayCount(zeroes_buffer)); - SLLQueuePush(list->first, list->last, node); - list->node_count += 1; - list->total_size = new_size; - node->string.str = (U8*)zeroes_buffer; - node->string.size = increase_size; - return(node); -} - -internal String8Node* -str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...){ - va_list args; - va_start(args, fmt); - String8 string = push_str8fv(arena, fmt, args); - String8Node *result = str8_list_push(arena, list, string); - va_end(args); - return(result); -} - -internal String8Node* -str8_list_push_frontf(Arena *arena, String8List *list, char *fmt, ...){ - va_list args; - va_start(args, fmt); - String8 string = push_str8fv(arena, fmt, args); - String8Node *result = str8_list_push_front(arena, list, string); - va_end(args); - return(result); -} - -internal String8List -str8_list_copy(Arena *arena, String8List *list){ - String8List result = {0}; - for (String8Node *node = list->first; - node != 0; - node = node->next){ - String8Node *new_node = push_array_no_zero(arena, String8Node, 1); - String8 new_string = push_str8_copy(arena, node->string); - str8_list_push_node_set_string(&result, new_node, new_string); - } - return(result); -} - -internal String8List -str8_split(Arena *arena, String8 string, U8 *split_chars, U64 split_char_count, StringSplitFlags flags){ - String8List list = {0}; - - B32 keep_empties = (flags & StringSplitFlag_KeepEmpties); - - U8 *ptr = string.str; - U8 *opl = string.str + string.size; - for (;ptr < opl;){ - U8 *first = ptr; - for (;ptr < opl; ptr += 1){ - U8 c = *ptr; - B32 is_split = 0; - for (U64 i = 0; i < split_char_count; i += 1){ - if (split_chars[i] == c){ - is_split = 1; - break; - } - } - if (is_split){ - break; - } - } - - String8 string = str8_range(first, ptr); - if (keep_empties || string.size > 0){ - str8_list_push(arena, &list, string); - } - ptr += 1; - } - - return(list); -} - -internal String8List -str8_split_by_string_chars(Arena *arena, String8 string, String8 split_chars, StringSplitFlags flags){ - String8List list = str8_split(arena, string, split_chars.str, split_chars.size, flags); - return list; -} - -internal String8List -str8_list_split_by_string_chars(Arena *arena, String8List list, String8 split_chars, StringSplitFlags flags){ - String8List result = {0}; - for (String8Node *node = list.first; node != 0; node = node->next){ - String8List split = str8_split_by_string_chars(arena, node->string, split_chars, flags); - str8_list_concat_in_place(&result, &split); - } - return result; -} - -internal String8 -str8_list_join(Arena *arena, String8List *list, StringJoin *optional_params){ - StringJoin join = {0}; - if (optional_params != 0){ - MemoryCopyStruct(&join, optional_params); - } - - U64 sep_count = 0; - if (list->node_count > 0){ - sep_count = list->node_count - 1; - } - - String8 result; - result.size = join.pre.size + join.post.size + sep_count*join.sep.size + list->total_size; - U8 *ptr = result.str = push_array_no_zero(arena, U8, result.size + 1); - - MemoryCopy(ptr, join.pre.str, join.pre.size); - ptr += join.pre.size; - for (String8Node *node = list->first; - node != 0; - node = node->next){ - MemoryCopy(ptr, node->string.str, node->string.size); - ptr += node->string.size; - if (node->next != 0){ - MemoryCopy(ptr, join.sep.str, join.sep.size); - ptr += join.sep.size; - } - } - MemoryCopy(ptr, join.post.str, join.post.size); - ptr += join.post.size; - - *ptr = 0; - - return(result); -} - -internal void -str8_list_from_flags(Arena *arena, String8List *list, - U32 flags, String8 *flag_string_table, U32 flag_string_count){ - for (U32 i = 0; i < flag_string_count; i += 1){ - U32 flag = (1 << i); - if (flags & flag){ - str8_list_push(arena, list, flag_string_table[i]); - } - } -} - -//////////////////////////////// -//~ rjf; String Arrays - -internal String8Array -str8_array_from_list(Arena *arena, String8List *list) -{ - String8Array array; - array.count = list->node_count; - array.v = push_array_no_zero(arena, String8, array.count); - U64 idx = 0; - for(String8Node *n = list->first; n != 0; n = n->next, idx += 1) - { - array.v[idx] = n->string; - } - return array; -} - -internal String8Array -str8_array_reserve(Arena *arena, U64 count) -{ - String8Array arr; - arr.count = 0; - arr.v = push_array(arena, String8, count); - return arr; -} - -//////////////////////////////// -//~ rjf: String Path Helpers - -internal String8 -str8_chop_last_slash(String8 string){ - if (string.size > 0){ - U8 *ptr = string.str + string.size - 1; - for (;ptr >= string.str; ptr -= 1){ - if (*ptr == '/' || *ptr == '\\'){ - break; - } - } - if (ptr >= string.str){ - string.size = (U64)(ptr - string.str); - } - else{ - string.size = 0; - } - } - return(string); -} - -internal String8 -str8_skip_last_slash(String8 string){ - if (string.size > 0){ - U8 *ptr = string.str + string.size - 1; - for (;ptr >= string.str; ptr -= 1){ - if (*ptr == '/' || *ptr == '\\'){ - break; - } - } - if (ptr >= string.str){ - ptr += 1; - string.size = (U64)(string.str + string.size - ptr); - string.str = ptr; - } - } - return(string); -} - -internal String8 -str8_chop_last_dot(String8 string) -{ - String8 result = string; - U64 p = string.size; - for (;p > 0;){ - p -= 1; - if (string.str[p] == '.'){ - result = str8_prefix(string, p); - break; - } - } - return(result); -} - -internal String8 -str8_skip_last_dot(String8 string){ - String8 result = string; - U64 p = string.size; - for (;p > 0;){ - p -= 1; - if (string.str[p] == '.'){ - result = str8_skip(string, p + 1); - break; - } - } - return(result); -} - -internal PathStyle -path_style_from_str8(String8 string){ - PathStyle result = PathStyle_Relative; - if (string.size >= 1 && string.str[0] == '/'){ - result = PathStyle_UnixAbsolute; - } - else if (string.size >= 2 && - char_is_alpha(string.str[0]) && - string.str[1] == ':'){ - if (string.size == 2 || - char_is_slash(string.str[2])){ - result = PathStyle_WindowsAbsolute; - } - } - return(result); -} - -internal String8List -str8_split_path(Arena *arena, String8 string){ - String8List result = str8_split(arena, string, (U8*)"/\\", 2, 0); - return(result); -} - -internal void -str8_path_list_resolve_dots_in_place(String8List *path, PathStyle style){ - Temp scratch = scratch_begin(0, 0); - - String8MetaNode *stack = 0; - String8MetaNode *free_meta_node = 0; - String8Node *first = path->first; - - MemoryZeroStruct(path); - for (String8Node *node = first, *next = 0; - node != 0; - node = next){ - // save next now - next = node->next; - - // cases: - if (node == first && style == PathStyle_WindowsAbsolute){ - goto save_without_stack; - } - if (node->string.size == 1 && node->string.str[0] == '.'){ - goto do_nothing; - } - if (node->string.size == 2 && node->string.str[0] == '.' && node->string.str[1] == '.'){ - if (stack != 0){ - goto eliminate_stack_top; - } - else{ - goto save_without_stack; - } - } - goto save_with_stack; - - - // handlers: - save_with_stack: - { - str8_list_push_node(path, node); - - String8MetaNode *stack_node = free_meta_node; - if (stack_node != 0){ - SLLStackPop(free_meta_node); - } - else{ - stack_node = push_array_no_zero(scratch.arena, String8MetaNode, 1); - } - SLLStackPush(stack, stack_node); - stack_node->node = node; - - continue; - } - - save_without_stack: - { - str8_list_push_node(path, node); - - continue; - } - - eliminate_stack_top: - { - path->node_count -= 1; - path->total_size -= stack->node->string.size; - - SLLStackPop(stack); - - if (stack == 0){ - path->last = path->first; - } - else{ - path->last = stack->node; - } - continue; - } - - do_nothing: continue; - } - scratch_end(scratch); -} - -internal String8 -str8_path_list_join_by_style(Arena *arena, String8List *path, PathStyle style){ - StringJoin params = {0}; - switch(style) - { - case PathStyle_Null:{}break; - case PathStyle_Relative: - case PathStyle_WindowsAbsolute: - { - params.sep = str8_lit("/"); - }break; - - case PathStyle_UnixAbsolute: - { - params.pre = str8_lit("/"); - params.sep = str8_lit("/"); - }break; - } - String8 result = str8_list_join(arena, path, ¶ms); - return result; -} - -internal String8TxtPtPair -str8_txt_pt_pair_from_string(String8 string) -{ - String8TxtPtPair pair = {0}; - { - String8 file_part = {0}; - String8 line_part = {0}; - String8 col_part = {0}; - - // rjf: grab file part - for(U64 idx = 0; idx <= string.size; idx += 1) - { - U8 byte = (idx < string.size) ? (string.str[idx]) : 0; - U8 next_byte = ((idx+1 < string.size) ? (string.str[idx+1]) : 0); - if(byte == ':' && next_byte != '/' && next_byte != '\\') - { - file_part = str8_prefix(string, idx); - line_part = str8_skip(string, idx+1); - break; - } - else if(byte == 0) - { - file_part = string; - break; - } - } - - // rjf: grab line/column - { - U64 colon_pos = str8_find_needle(line_part, 0, str8_lit(":"), 0); - if(colon_pos < line_part.size) - { - col_part = str8_skip(line_part, colon_pos+1); - line_part = str8_prefix(line_part, colon_pos); - } - } - - // rjf: convert line/column strings to numerics - U64 line = 0; - U64 column = 0; - try_u64_from_str8_c_rules(line_part, &line); - try_u64_from_str8_c_rules(col_part, &column); - - // rjf: fill - pair.string = file_part; - pair.pt = txt_pt((S64)line, (S64)column); - if(pair.pt.line == 0) { pair.pt.line = 1; } - if(pair.pt.column == 0) { pair.pt.column = 1; } - } - return pair; -} - -//////////////////////////////// -//~ rjf: UTF-8 & UTF-16 Decoding/Encoding - -read_only global U8 utf8_class[32] = { - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,5, -}; - -internal UnicodeDecode -utf8_decode(U8 *str, U64 max){ - UnicodeDecode result = {1, max_U32}; - U8 byte = str[0]; - U8 byte_class = utf8_class[byte >> 3]; - switch (byte_class) - { - case 1: - { - result.codepoint = byte; - }break; - case 2: - { - if (2 < max) - { - U8 cont_byte = str[1]; - if (utf8_class[cont_byte >> 3] == 0) - { - result.codepoint = (byte & bitmask5) << 6; - result.codepoint |= (cont_byte & bitmask6); - result.inc = 2; - } - } - }break; - case 3: - { - if (2 < max) - { - U8 cont_byte[2] = {str[1], str[2]}; - if (utf8_class[cont_byte[0] >> 3] == 0 && - utf8_class[cont_byte[1] >> 3] == 0) - { - result.codepoint = (byte & bitmask4) << 12; - result.codepoint |= ((cont_byte[0] & bitmask6) << 6); - result.codepoint |= (cont_byte[1] & bitmask6); - result.inc = 3; - } - } - }break; - case 4: - { - if (3 < max) - { - U8 cont_byte[3] = {str[1], str[2], str[3]}; - if (utf8_class[cont_byte[0] >> 3] == 0 && - utf8_class[cont_byte[1] >> 3] == 0 && - utf8_class[cont_byte[2] >> 3] == 0) - { - result.codepoint = (byte & bitmask3) << 18; - result.codepoint |= ((cont_byte[0] & bitmask6) << 12); - result.codepoint |= ((cont_byte[1] & bitmask6) << 6); - result.codepoint |= (cont_byte[2] & bitmask6); - result.inc = 4; - } - } - } - } - return(result); -} - -internal UnicodeDecode -utf16_decode(U16 *str, U64 max){ - UnicodeDecode result = {1, max_U32}; - result.codepoint = str[0]; - result.inc = 1; - if (max > 1 && 0xD800 <= str[0] && str[0] < 0xDC00 && 0xDC00 <= str[1] && str[1] < 0xE000){ - result.codepoint = ((str[0] - 0xD800) << 10) | ((str[1] - 0xDC00) + 0x10000); - result.inc = 2; - } - return(result); -} - -internal U32 -utf8_encode(U8 *str, U32 codepoint){ - U32 inc = 0; - if (codepoint <= 0x7F){ - str[0] = (U8)codepoint; - inc = 1; - } - else if (codepoint <= 0x7FF){ - str[0] = (bitmask2 << 6) | ((codepoint >> 6) & bitmask5); - str[1] = bit8 | (codepoint & bitmask6); - inc = 2; - } - else if (codepoint <= 0xFFFF){ - str[0] = (bitmask3 << 5) | ((codepoint >> 12) & bitmask4); - str[1] = bit8 | ((codepoint >> 6) & bitmask6); - str[2] = bit8 | ( codepoint & bitmask6); - inc = 3; - } - else if (codepoint <= 0x10FFFF){ - str[0] = (bitmask4 << 4) | ((codepoint >> 18) & bitmask3); - str[1] = bit8 | ((codepoint >> 12) & bitmask6); - str[2] = bit8 | ((codepoint >> 6) & bitmask6); - str[3] = bit8 | ( codepoint & bitmask6); - inc = 4; - } - else{ - str[0] = '?'; - inc = 1; - } - return(inc); -} - -internal U32 -utf16_encode(U16 *str, U32 codepoint){ - U32 inc = 1; - if (codepoint == max_U32){ - str[0] = (U16)'?'; - } - else if (codepoint < 0x10000){ - str[0] = (U16)codepoint; - } - else{ - U32 v = codepoint - 0x10000; - str[0] = safe_cast_u16(0xD800 + (v >> 10)); - str[1] = safe_cast_u16(0xDC00 + (v & bitmask10)); - inc = 2; - } - return(inc); -} - -internal U32 -utf8_from_utf32_single(U8 *buffer, U32 character){ - return(utf8_encode(buffer, character)); -} - -//////////////////////////////// -//~ rjf: Unicode String Conversions - -internal String8 -str8_from_16(Arena *arena, String16 in) -{ - String8 result = str8_zero(); - if(in.size) - { - U64 cap = in.size*3; - U8 *str = push_array_no_zero(arena, U8, cap + 1); - U16 *ptr = in.str; - U16 *opl = ptr + in.size; - U64 size = 0; - UnicodeDecode consume; - for(;ptr < opl; ptr += consume.inc) - { - consume = utf16_decode(ptr, opl - ptr); - size += utf8_encode(str + size, consume.codepoint); - } - str[size] = 0; - arena_pop(arena, (cap - size)); - result = str8(str, size); - } - return result; -} - -internal String16 -str16_from_8(Arena *arena, String8 in) -{ - String16 result = str16_zero(); - if(in.size) - { - U64 cap = in.size*2; - U16 *str = push_array_no_zero(arena, U16, cap + 1); - U8 *ptr = in.str; - U8 *opl = ptr + in.size; - U64 size = 0; - UnicodeDecode consume; - for(;ptr < opl; ptr += consume.inc) - { - consume = utf8_decode(ptr, opl - ptr); - size += utf16_encode(str + size, consume.codepoint); - } - str[size] = 0; - arena_pop(arena, (cap - size)*2); - result = str16(str, size); - } - return result; -} - -internal String8 -str8_from_32(Arena *arena, String32 in) -{ - String8 result = str8_zero(); - if(in.size) - { - U64 cap = in.size*4; - U8 *str = push_array_no_zero(arena, U8, cap + 1); - U32 *ptr = in.str; - U32 *opl = ptr + in.size; - U64 size = 0; - for(;ptr < opl; ptr += 1) - { - size += utf8_encode(str + size, *ptr); - } - str[size] = 0; - arena_pop(arena, (cap - size)); - result = str8(str, size); - } - return result; -} - -internal String32 -str32_from_8(Arena *arena, String8 in) -{ - String32 result = str32_zero(); - if(in.size) - { - U64 cap = in.size; - U32 *str = push_array_no_zero(arena, U32, cap + 1); - U8 *ptr = in.str; - U8 *opl = ptr + in.size; - U64 size = 0; - UnicodeDecode consume; - for(;ptr < opl; ptr += consume.inc) - { - consume = utf8_decode(ptr, opl - ptr); - str[size] = consume.codepoint; - size += 1; - } - str[size] = 0; - arena_pop(arena, (cap - size)*4); - result = str32(str, size); - } - return result; -} - -//////////////////////////////// -//~ String -> Enum Conversions - -read_only global struct -{ - String8 string; - OperatingSystem os; -} g_os_enum_map[] = -{ - { str8_lit_comp(""), OperatingSystem_Null }, - { str8_lit_comp("Windows"), OperatingSystem_Windows, }, - { str8_lit_comp("Linux"), OperatingSystem_Linux, }, - { str8_lit_comp("Mac"), OperatingSystem_Mac, }, -}; -StaticAssert(ArrayCount(g_os_enum_map) == OperatingSystem_COUNT, g_os_enum_map_count_check); - -internal OperatingSystem -operating_system_from_string(String8 string) -{ - for(U64 i = 0; i < ArrayCount(g_os_enum_map); ++i) - { - if(str8_match(g_os_enum_map[i].string, string, StringMatchFlag_CaseInsensitive)) - { - return g_os_enum_map[i].os; - } - } - return OperatingSystem_Null; -} - -//////////////////////////////// -//~ rjf: Basic Types & Space Enum -> String Conversions - -internal String8 -string_from_dimension(Dimension dimension){ - local_persist String8 strings[] = { - str8_lit_comp("X"), - str8_lit_comp("Y"), - str8_lit_comp("Z"), - str8_lit_comp("W"), - }; - String8 result = str8_lit("error"); - if ((U32)dimension < 4){ - result = strings[dimension]; - } - return(result); -} - -internal String8 -string_from_side(Side side){ - local_persist String8 strings[] = { - str8_lit_comp("Min"), - str8_lit_comp("Max"), - }; - String8 result = str8_lit("error"); - if ((U32)side < 2){ - result = strings[side]; - } - return(result); -} - -internal String8 -string_from_operating_system(OperatingSystem os) -{ - String8 result = g_os_enum_map[OperatingSystem_Null].string; - if(os < ArrayCount(g_os_enum_map)) - { - result = g_os_enum_map[os].string; - } - return result; -} - -internal String8 -string_from_arch(Arch arch){ - local_persist String8 strings[] = { - str8_lit_comp("Null"), - str8_lit_comp("x64"), - str8_lit_comp("x86"), - str8_lit_comp("arm64"), - str8_lit_comp("arm32"), - }; - String8 result = str8_lit("error"); - if (arch < Arch_COUNT){ - result = strings[arch]; - } - return(result); -} - -//////////////////////////////// -//~ rjf: Time Types -> String - -internal String8 -string_from_week_day(WeekDay week_day){ - local_persist String8 strings[] = { - str8_lit_comp("Sun"), - str8_lit_comp("Mon"), - str8_lit_comp("Tue"), - str8_lit_comp("Wed"), - str8_lit_comp("Thu"), - str8_lit_comp("Fri"), - str8_lit_comp("Sat"), - }; - String8 result = str8_lit("Err"); - if ((U32)week_day < WeekDay_COUNT){ - result = strings[week_day]; - } - return(result); -} - -internal String8 -string_from_month(Month month){ - local_persist String8 strings[] = { - str8_lit_comp("Jan"), - str8_lit_comp("Feb"), - str8_lit_comp("Mar"), - str8_lit_comp("Apr"), - str8_lit_comp("May"), - str8_lit_comp("Jun"), - str8_lit_comp("Jul"), - str8_lit_comp("Aug"), - str8_lit_comp("Sep"), - str8_lit_comp("Oct"), - str8_lit_comp("Nov"), - str8_lit_comp("Dec"), - }; - String8 result = str8_lit("Err"); - if ((U32)month < Month_COUNT){ - result = strings[month]; - } - return(result); -} - -internal String8 -push_date_time_string(Arena *arena, DateTime *date_time){ - char *mon_str = (char*)string_from_month(date_time->month).str; - U32 adjusted_hour = date_time->hour%12; - if (adjusted_hour == 0){ - adjusted_hour = 12; - } - char *ampm = "am"; - if (date_time->hour >= 12){ - ampm = "pm"; - } - String8 result = push_str8f(arena, "%d %s %d, %02d:%02d:%02d %s", - date_time->day, mon_str, date_time->year, - adjusted_hour, date_time->min, date_time->sec, ampm); - return(result); -} - -internal String8 -push_file_name_date_time_string(Arena *arena, DateTime *date_time){ - char *mon_str = (char*)string_from_month(date_time->month).str; - String8 result = push_str8f(arena, "%d-%s-%0d--%02d-%02d-%02d", - date_time->year, mon_str, date_time->day, - date_time->hour, date_time->min, date_time->sec); - return(result); -} - -internal String8 -string_from_elapsed_time(Arena *arena, DateTime dt){ - Temp scratch = scratch_begin(&arena, 1); - String8List list = {0}; - if (dt.year){ - str8_list_pushf(scratch.arena, &list, "%dy", dt.year); - str8_list_pushf(scratch.arena, &list, "%um", dt.mon); - str8_list_pushf(scratch.arena, &list, "%ud", dt.day); - } else if (dt.mon){ - str8_list_pushf(scratch.arena, &list, "%um", dt.mon); - str8_list_pushf(scratch.arena, &list, "%ud", dt.day); - } else if (dt.day){ - str8_list_pushf(scratch.arena, &list, "%ud", dt.day); - } - str8_list_pushf(scratch.arena, &list, "%u:%u:%u:%u ms", dt.hour, dt.min, dt.sec, dt.msec); - StringJoin join = { str8_lit_comp(""), str8_lit_comp(" "), str8_lit_comp("") }; - String8 result = str8_list_join(arena, &list, &join); - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ Globally UNique Ids - -internal String8 -string_from_guid(Arena *arena, Guid guid) -{ - String8 result = push_str8f(arena, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", - guid.data1, - guid.data2, - guid.data3, - guid.data4[0], - guid.data4[1], - guid.data4[2], - guid.data4[3], - guid.data4[4], - guid.data4[5], - guid.data4[6], - guid.data4[7]); - return result; -} - -internal B32 -try_guid_from_string(String8 string, Guid *guid_out) -{ - Temp scratch = scratch_begin(0,0); - B32 is_parsed = 0; - String8List list = str8_split_by_string_chars(scratch.arena, string, str8_lit("-"), StringSplitFlag_KeepEmpties); - if(list.node_count == 5) - { - String8 data1_str = list.first->string; - String8 data2_str = list.first->next->string; - String8 data3_str = list.first->next->next->string; - String8 data4_hi_str = list.first->next->next->next->string; - String8 data4_lo_str = list.first->next->next->next->next->string; - if(str8_is_integer(data1_str, 16) && - str8_is_integer(data2_str, 16) && - str8_is_integer(data3_str, 16) && - str8_is_integer(data4_hi_str, 16) && - str8_is_integer(data4_lo_str, 16)) - { - U64 data1 = u64_from_str8(data1_str, 16); - U64 data2 = u64_from_str8(data2_str, 16); - U64 data3 = u64_from_str8(data3_str, 16); - U64 data4_hi = u64_from_str8(data4_hi_str, 16); - U64 data4_lo = u64_from_str8(data4_lo_str, 16); - if(data1 <= max_U32 && - data2 <= max_U16 && - data3 <= max_U16 && - data4_hi <= max_U16 && - data4_lo <= 0xffffffffffff) - { - guid_out->data1 = (U32)data1; - guid_out->data2 = (U16)data2; - guid_out->data3 = (U16)data3; - U64 data4 = (data4_hi << 48) | data4_lo; - MemoryCopy(&guid_out->data4[0], &data4, sizeof(data4)); - is_parsed = 1; - } - } - } - scratch_end(scratch); - return is_parsed; -} - -internal Guid -guid_from_string(String8 string) -{ - Guid guid = {0}; - try_guid_from_string(string, &guid); - return guid; -} - -//////////////////////////////// -//~ rjf: Basic Text Indentation - -internal String8 -indented_from_string(Arena *arena, String8 string) -{ - Temp scratch = scratch_begin(&arena, 1); - read_only local_persist U8 indentation_bytes[] = " "; - String8List indented_strings = {0}; - S64 depth = 0; - S64 next_depth = 0; - U64 line_begin_off = 0; - for(U64 off = 0; off <= string.size; off += 1) - { - U8 byte = off width_this_line){ - String8 line = str8_substr(string, line_range); - if (wrapped_indent_level > 0){ - line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); - } - str8_list_push(arena, &list, line); - line_range = r1u64(line_range.max+1, candidate_line_range.max); - wrapped_indent_level = ClampTop(64, wrap_indent); - } - else{ - line_range = candidate_line_range; - } - } - } - if (line_range.min < string.size && line_range.max > line_range.min){ - String8 line = str8_substr(string, line_range); - if (wrapped_indent_level > 0){ - line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); - } - str8_list_push(arena, &list, line); - } - return list; -} - -//////////////////////////////// -//~ rjf: String <-> Color - -internal String8 -hex_string_from_rgba_4f32(Arena *arena, Vec4F32 rgba) -{ - String8 hex_string = push_str8f(arena, "%02x%02x%02x%02x", (U8)(rgba.x*255.f), (U8)(rgba.y*255.f), (U8)(rgba.z*255.f), (U8)(rgba.w*255.f)); - return hex_string; -} - -internal Vec4F32 -rgba_from_hex_string_4f32(String8 hex_string) -{ - U8 byte_text[8] = {0}; - U64 byte_text_idx = 0; - for(U64 idx = 0; idx < hex_string.size && byte_text_idx < ArrayCount(byte_text); idx += 1) - { - if(char_is_digit(hex_string.str[idx], 16)) - { - byte_text[byte_text_idx] = char_to_lower(hex_string.str[idx]); - byte_text_idx += 1; - } - } - U8 byte_vals[4] = {0}; - for(U64 idx = 0; idx < 4; idx += 1) - { - byte_vals[idx] = (U8)u64_from_str8(str8(&byte_text[idx*2], 2), 16); - } - Vec4F32 rgba = v4f32(byte_vals[0]/255.f, byte_vals[1]/255.f, byte_vals[2]/255.f, byte_vals[3]/255.f); - return rgba; -} - -//////////////////////////////// -//~ rjf: String Fuzzy Matching - -internal FuzzyMatchRangeList -fuzzy_match_find(Arena *arena, String8 needle, String8 haystack) -{ - FuzzyMatchRangeList result = {0}; - Temp scratch = scratch_begin(&arena, 1); - String8List needles = str8_split(scratch.arena, needle, (U8*)" ", 1, 0); - result.needle_part_count = needles.node_count; - for(String8Node *needle_n = needles.first; needle_n != 0; needle_n = needle_n->next) - { - U64 find_pos = 0; - for(;find_pos < haystack.size;) - { - find_pos = str8_find_needle(haystack, find_pos, needle_n->string, StringMatchFlag_CaseInsensitive); - B32 is_in_gathered_ranges = 0; - for(FuzzyMatchRangeNode *n = result.first; n != 0; n = n->next) - { - if(n->range.min <= find_pos && find_pos < n->range.max) - { - is_in_gathered_ranges = 1; - find_pos = n->range.max; - break; - } - } - if(!is_in_gathered_ranges) - { - break; - } - } - if(find_pos < haystack.size) - { - Rng1U64 range = r1u64(find_pos, find_pos+needle_n->string.size); - FuzzyMatchRangeNode *n = push_array(arena, FuzzyMatchRangeNode, 1); - n->range = range; - SLLQueuePush(result.first, result.last, n); - result.count += 1; - result.total_dim += dim_1u64(range); - } - } - scratch_end(scratch); - return result; -} - -internal FuzzyMatchRangeList -fuzzy_match_range_list_copy(Arena *arena, FuzzyMatchRangeList *src) -{ - FuzzyMatchRangeList dst = {0}; - for(FuzzyMatchRangeNode *src_n = src->first; src_n != 0; src_n = src_n->next) - { - FuzzyMatchRangeNode *dst_n = push_array(arena, FuzzyMatchRangeNode, 1); - SLLQueuePush(dst.first, dst.last, dst_n); - dst_n->range = src_n->range; - } - dst.count = src->count; - dst.needle_part_count = src->needle_part_count; - dst.total_dim = src->total_dim; - return dst; -} - -//////////////////////////////// -//~ NOTE(allen): Serialization Helpers - -internal void -str8_serial_begin(Arena *arena, String8List *srl){ - String8Node *node = push_array(arena, String8Node, 1); - node->string.str = push_array_no_zero(arena, U8, 0); - srl->first = srl->last = node; - srl->node_count = 1; - srl->total_size = 0; -} - -internal String8 -str8_serial_end(Arena *arena, String8List *srl){ - U64 size = srl->total_size; - U8 *out = push_array_no_zero(arena, U8, size); - str8_serial_write_to_dst(srl, out); - String8 result = str8(out, size); - return result; -} - -internal void -str8_serial_write_to_dst(String8List *srl, void *out){ - U8 *ptr = (U8*)out; - for (String8Node *node = srl->first; - node != 0; - node = node->next){ - U64 size = node->string.size; - MemoryCopy(ptr, node->string.str, size); - ptr += size; - } -} - -internal U64 -str8_serial_push_align(Arena *arena, String8List *srl, U64 align){ - Assert(IsPow2(align)); - - U64 pos = srl->total_size; - U64 new_pos = AlignPow2(pos, align); - U64 size = (new_pos - pos); - - if(size != 0) - { - U8 *buf = push_array(arena, U8, size); - - String8 *str = &srl->last->string; - if (str->str + str->size == buf){ - srl->last->string.size += size; - srl->total_size += size; - } - else{ - str8_list_push(arena, srl, str8(buf, size)); - } - } - return size; -} - -internal void * -str8_serial_push_size(Arena *arena, String8List *srl, U64 size) -{ - void *result = 0; - if(size != 0) - { - U8 *buf = push_array_no_zero(arena, U8, size); - String8 *str = &srl->last->string; - if (str->str + str->size == buf){ - srl->last->string.size += size; - srl->total_size += size; - } - else{ - str8_list_push(arena, srl, str8(buf, size)); - } - result = buf; - } - return result; -} - -internal void * -str8_serial_push_data(Arena *arena, String8List *srl, void *data, U64 size){ - void *result = str8_serial_push_size(arena, srl, size); - if(result != 0) - { - MemoryCopy(result, data, size); - } - return result; -} - -internal void -str8_serial_push_data_list(Arena *arena, String8List *srl, String8Node *first){ - for (String8Node *node = first; - node != 0; - node = node->next){ - str8_serial_push_data(arena, srl, node->string.str, node->string.size); - } -} - -internal void -str8_serial_push_u64(Arena *arena, String8List *srl, U64 x){ - U8 *buf = push_array_no_zero(arena, U8, 8); - MemoryCopy(buf, &x, 8); - String8 *str = &srl->last->string; - if (str->str + str->size == buf){ - srl->last->string.size += 8; - srl->total_size += 8; - } - else{ - str8_list_push(arena, srl, str8(buf, 8)); - } -} - -internal void -str8_serial_push_u32(Arena *arena, String8List *srl, U32 x){ - U8 *buf = push_array_no_zero(arena, U8, 4); - MemoryCopy(buf, &x, 4); - String8 *str = &srl->last->string; - if (str->str + str->size == buf){ - srl->last->string.size += 4; - srl->total_size += 4; - } - else{ - str8_list_push(arena, srl, str8(buf, 4)); - } -} - -internal void -str8_serial_push_u16(Arena *arena, String8List *srl, U16 x){ - str8_serial_push_data(arena, srl, &x, sizeof(x)); -} - -internal void -str8_serial_push_u8(Arena *arena, String8List *srl, U8 x){ - str8_serial_push_data(arena, srl, &x, sizeof(x)); -} - -internal void -str8_serial_push_cstr(Arena *arena, String8List *srl, String8 str){ - str8_serial_push_data(arena, srl, str.str, str.size); - str8_serial_push_u8(arena, srl, 0); -} - -internal void -str8_serial_push_string(Arena *arena, String8List *srl, String8 str){ - str8_serial_push_data(arena, srl, str.str, str.size); -} - -//////////////////////////////// -//~ rjf: Deserialization Helpers - -internal U64 -str8_deserial_read(String8 string, U64 off, void *read_dst, U64 read_size, U64 granularity) -{ - U64 bytes_left = string.size-Min(off, string.size); - U64 actually_readable_size = Min(bytes_left, read_size); - U64 legally_readable_size = actually_readable_size - actually_readable_size%granularity; - if(legally_readable_size > 0) - { - MemoryCopy(read_dst, string.str+off, legally_readable_size); - } - return legally_readable_size; -} - -internal U64 -str8_deserial_find_first_match(String8 string, U64 off, U16 scan_val) -{ - U64 cursor = off; - for (;;) { - U16 val = 0; - str8_deserial_read_struct(string, cursor, &val); - if (val == scan_val) { - break; - } - cursor += sizeof(val); - } - return cursor; -} - -internal void * -str8_deserial_get_raw_ptr(String8 string, U64 off, U64 size) -{ - void *raw_ptr = 0; - if (off + size <= string.size) { - raw_ptr = string.str + off; - } - return raw_ptr; -} - -internal U64 -str8_deserial_read_cstr(String8 string, U64 off, String8 *cstr_out) -{ - U64 cstr_size = 0; - if (off < string.size) { - U8 *ptr = string.str + off; - U8 *cap = string.str + string.size; - *cstr_out = str8_cstring_capped(ptr, cap); - cstr_size = (cstr_out->size + 1); - } - return cstr_size; -} - -internal U64 -str8_deserial_read_windows_utf16_string16(String8 string, U64 off, String16 *str_out) -{ - U64 null_off = str8_deserial_find_first_match(string, off, 0); - U64 size = null_off - off; - U16 *str = (U16 *)str8_deserial_get_raw_ptr(string, off, size); - U64 count = size / sizeof(*str); - *str_out = str16(str, count); - - U64 read_size_with_null = size + sizeof(*str); - return read_size_with_null; -} - -internal U64 -str8_deserial_read_block(String8 string, U64 off, U64 size, String8 *block_out) -{ - Rng1U64 range = rng_1u64(off, off + size); - *block_out = str8_substr(string, range); - return block_out->size; -} - -internal U64 -str8_deserial_read_uleb128(String8 string, U64 off, U64 *value_out) -{ - U64 value = 0; - U64 shift = 0; - U64 cursor = off; - for(;;) - { - U8 byte = 0; - U64 bytes_read = str8_deserial_read_struct(string, cursor, &byte); - - if(bytes_read != sizeof(byte)) - { - break; - } - - U8 val = byte & 0x7fu; - value |= ((U64)val) << shift; - - cursor += bytes_read; - shift += 7u; - - if((byte & 0x80u) == 0) - { - break; - } - } - if(value_out != 0) - { - *value_out = value; - } - U64 bytes_read = cursor - off; - return bytes_read; -} - -internal U64 -str8_deserial_read_sleb128(String8 string, U64 off, S64 *value_out) -{ - U64 value = 0; - U64 shift = 0; - U64 cursor = off; - for(;;) - { - U8 byte; - U64 bytes_read = str8_deserial_read_struct(string, cursor, &byte); - if(bytes_read != sizeof(byte)) - { - break; - } - - U8 val = byte & 0x7fu; - value |= ((U64)val) << shift; - - cursor += bytes_read; - shift += 7u; - - if((byte & 0x80u) == 0) - { - if(shift < sizeof(value) * 8 && (byte & 0x40u) != 0) - { - value |= -(S64)(1ull << shift); - } - break; - } - } - if(value_out != 0) - { - *value_out = value; - } - U64 bytes_read = cursor - off; - return bytes_read; -} - +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Third Party Includes + +#if !BUILD_SUPPLEMENTARY_UNIT +# define STB_SPRINTF_IMPLEMENTATION +# define STB_SPRINTF_STATIC +# include "third_party/stb/stb_sprintf.h" +#endif + +//////////////////////////////// +//~ NOTE(allen): String <-> Integer Tables + +read_only global U8 integer_symbols[16] = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', +}; + +// NOTE(allen): Includes reverses for uppercase and lowercase hex. +read_only global U8 integer_symbol_reverse[128] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +}; + +read_only global U8 base64[64] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '_', '$', +}; + +read_only global U8 base64_reverse[128] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0x00, + 0xFF,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32, + 0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0x3E, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, + 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0xFF,0xFF,0xFF,0xFF,0xFF, +}; + +//////////////////////////////// +//~ rjf: Character Classification & Conversion Functions + +internal B32 +char_is_space(U8 c){ + return(c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f' || c == '\v'); +} + +internal B32 +char_is_upper(U8 c){ + return('A' <= c && c <= 'Z'); +} + +internal B32 +char_is_lower(U8 c){ + return('a' <= c && c <= 'z'); +} + +internal B32 +char_is_alpha(U8 c){ + return(char_is_upper(c) || char_is_lower(c)); +} + +internal B32 +char_is_slash(U8 c){ + return(c == '/' || c == '\\'); +} + +internal B32 +char_is_digit(U8 c, U32 base){ + B32 result = 0; + if (0 < base && base <= 16){ + U8 val = integer_symbol_reverse[c]; + if (val < base){ + result = 1; + } + } + return(result); +} + +internal U8 +char_to_lower(U8 c){ + if (char_is_upper(c)){ + c += ('a' - 'A'); + } + return(c); +} + +internal U8 +char_to_upper(U8 c){ + if (char_is_lower(c)){ + c += ('A' - 'a'); + } + return(c); +} + +internal U8 +char_to_correct_slash(U8 c){ + if(char_is_slash(c)){ + c = '/'; + } + return(c); +} + +//////////////////////////////// +//~ rjf: C-String Measurement + +internal U64 +cstring8_length(U8 *c){ + U8 *p = c; + for (;*p != 0; p += 1); + return(p - c); +} + +internal U64 +cstring16_length(U16 *c){ + U16 *p = c; + for (;*p != 0; p += 1); + return(p - c); +} + +internal U64 +cstring32_length(U32 *c){ + U32 *p = c; + for (;*p != 0; p += 1); + return(p - c); +} + +//////////////////////////////// +//~ rjf: String Constructors + +internal String8 +str8(U8 *str, U64 size){ + String8 result = {str, size}; + return(result); +} + +internal String8 +str8_range(U8 *first, U8 *one_past_last){ + String8 result = {first, (U64)(one_past_last - first)}; + return(result); +} + +internal String8 +str8_zero(void){ + String8 result = {0}; + return(result); +} + +internal String16 +str16(U16 *str, U64 size){ + String16 result = {str, size}; + return(result); +} + +internal String16 +str16_range(U16 *first, U16 *one_past_last){ + String16 result = {first, (U64)(one_past_last - first)}; + return(result); +} + +internal String16 +str16_zero(void){ + String16 result = {0}; + return(result); +} + +internal String32 +str32(U32 *str, U64 size){ + String32 result = {str, size}; + return(result); +} + +internal String32 +str32_range(U32 *first, U32 *one_past_last){ + String32 result = {first, (U64)(one_past_last - first)}; + return(result); +} + +internal String32 +str32_zero(void){ + String32 result = {0}; + return(result); +} + +internal String8 +str8_cstring(char *c){ + String8 result = {(U8*)c, cstring8_length((U8*)c)}; + return(result); +} + +internal String16 +str16_cstring(U16 *c){ + String16 result = {(U16*)c, cstring16_length((U16*)c)}; + return(result); +} + +internal String32 +str32_cstring(U32 *c){ + String32 result = {(U32*)c, cstring32_length((U32*)c)}; + return(result); +} + +internal String8 +str8_cstring_capped(void *cstr, void *cap) +{ + char *ptr = (char *)cstr; + char *opl = (char *)cap; + for (;ptr < opl && *ptr != 0; ptr += 1); + U64 size = (U64)(ptr - (char *)cstr); + String8 result = str8((U8*)cstr, size); + return result; +} + +internal String16 +str16_cstring_capped(void *cstr, void *cap) +{ + U16 *ptr = (U16 *)cstr; + U16 *opl = (U16 *)cap; + for (;ptr < opl && *ptr != 0; ptr += 1); + U64 size = (U64)(ptr - (U16 *)cstr); + String16 result = str16(cstr, size); + return result; +} + +internal String8 +str8_cstring_capped_reverse(void *raw_start, void *raw_cap) +{ + U8 *start = raw_start; + U8 *ptr = raw_cap; + for(; ptr > start; ) + { + ptr -= 1; + + if (*ptr == '\0') + { + break; + } + } + U64 size = (U64)(ptr - start); + String8 result = str8(start, size); + return result; +} + +//////////////////////////////// +//~ rjf: String Stylization + +internal String8 +upper_from_str8(Arena *arena, String8 string) +{ + string = push_str8_copy(arena, string); + for(U64 idx = 0; idx < string.size; idx += 1) + { + string.str[idx] = char_to_upper(string.str[idx]); + } + return string; +} + +internal String8 +lower_from_str8(Arena *arena, String8 string) +{ + string = push_str8_copy(arena, string); + for(U64 idx = 0; idx < string.size; idx += 1) + { + string.str[idx] = char_to_lower(string.str[idx]); + } + return string; +} + +internal String8 +backslashed_from_str8(Arena *arena, String8 string) +{ + string = push_str8_copy(arena, string); + for(U64 idx = 0; idx < string.size; idx += 1) + { + string.str[idx] = char_is_slash(string.str[idx]) ? '\\' : string.str[idx]; + } + return string; +} + +//////////////////////////////// +//~ rjf: String Matching + +internal B32 +str8_match(String8 a, String8 b, StringMatchFlags flags) +{ + B32 result = 0; + if(a.size == b.size && flags == 0) + { + result = MemoryMatch(a.str, b.str, b.size); + } + else if(a.size == b.size || (flags & StringMatchFlag_RightSideSloppy)) + { + B32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive); + B32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive); + U64 size = Min(a.size, b.size); + result = 1; + for(U64 i = 0; i < size; i += 1) + { + U8 at = a.str[i]; + U8 bt = b.str[i]; + if(case_insensitive) + { + at = char_to_upper(at); + bt = char_to_upper(bt); + } + if(slash_insensitive) + { + at = char_to_correct_slash(at); + bt = char_to_correct_slash(bt); + } + if(at != bt) + { + result = 0; + break; + } + } + } + return result; +} + +internal U64 +str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags){ + U8 *p = string.str + start_pos; + U64 stop_offset = Max(string.size + 1, needle.size) - needle.size; + U8 *stop_p = string.str + stop_offset; + if (needle.size > 0){ + U8 *string_opl = string.str + string.size; + String8 needle_tail = str8_skip(needle, 1); + StringMatchFlags adjusted_flags = flags | StringMatchFlag_RightSideSloppy; + U8 needle_first_char_adjusted = needle.str[0]; + if(adjusted_flags & StringMatchFlag_CaseInsensitive){ + needle_first_char_adjusted = char_to_upper(needle_first_char_adjusted); + } + for (;p < stop_p; p += 1){ + U8 haystack_char_adjusted = *p; + if(adjusted_flags & StringMatchFlag_CaseInsensitive){ + haystack_char_adjusted = char_to_upper(haystack_char_adjusted); + } + if (haystack_char_adjusted == needle_first_char_adjusted){ + if (str8_match(str8_range(p + 1, string_opl), needle_tail, adjusted_flags)){ + break; + } + } + } + } + U64 result = string.size; + if (p < stop_p){ + result = (U64)(p - string.str); + } + return(result); +} + +internal U64 +str8_find_needle_reverse(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags) +{ + U64 result = 0; + for(S64 i = string.size - start_pos - needle.size; i >= 0; --i) + { + String8 haystack = str8_substr(string, rng_1u64(i, i + needle.size)); + if(str8_match(haystack, needle, flags)) + { + result = (U64)i + needle.size; + break; + } + } + return result; +} + +internal B32 +str8_starts_with(String8 string, String8 start, StringMatchFlags flags){ + if(string.size < start.size) { + return 0; + } + String8 prefix = str8_prefix(string, start.size); + return str8_match(prefix, start, flags); +} + +internal B32 +str8_ends_with(String8 string, String8 end, StringMatchFlags flags){ + String8 postfix = str8_postfix(string, end.size); + B32 is_match = str8_match(end, postfix, flags); + return is_match; +} + +//////////////////////////////// +//~ rjf: String Slicing + +internal String8 +str8_substr(String8 str, Rng1U64 range){ + range.min = ClampTop(range.min, str.size); + range.max = ClampTop(range.max, str.size); + str.str += range.min; + str.size = dim_1u64(range); + return(str); +} + +internal String8 +str8_prefix(String8 str, U64 size){ + str.size = ClampTop(size, str.size); + return(str); +} + +internal String8 +str8_skip(String8 str, U64 amt){ + amt = ClampTop(amt, str.size); + str.str += amt; + str.size -= amt; + return(str); +} + +internal String8 +str8_postfix(String8 str, U64 size){ + size = ClampTop(size, str.size); + str.str = (str.str + str.size) - size; + str.size = size; + return(str); +} + +internal String8 +str8_chop(String8 str, U64 amt){ + amt = ClampTop(amt, str.size); + str.size -= amt; + return(str); +} + +internal String8 +str8_skip_chop_whitespace(String8 string){ + U8 *first = string.str; + U8 *opl = first + string.size; + for (;first < opl; first += 1){ + if (!char_is_space(*first)){ + break; + } + } + for (;opl > first;){ + opl -= 1; + if (!char_is_space(*opl)){ + opl += 1; + break; + } + } + String8 result = str8_range(first, opl); + return(result); +} + +//////////////////////////////// +//~ rjf: String Formatting & Copying + +internal String8 +push_str8_cat(Arena *arena, String8 s1, String8 s2){ + String8 str; + str.size = s1.size + s2.size; + str.str = push_array_no_zero(arena, U8, str.size + 1); + MemoryCopy(str.str, s1.str, s1.size); + MemoryCopy(str.str + s1.size, s2.str, s2.size); + str.str[str.size] = 0; + return(str); +} + +internal String8 +push_str8_copy(Arena *arena, String8 s){ + String8 str; + str.size = s.size; + str.str = push_array_no_zero(arena, U8, str.size + 1); + MemoryCopy(str.str, s.str, s.size); + str.str[str.size] = 0; + return(str); +} + +internal String8 +push_str8fv(Arena *arena, char *fmt, va_list args){ + va_list args2; + va_copy(args2, args); + U32 needed_bytes = raddbg_vsnprintf(0, 0, fmt, args) + 1; + String8 result = {0}; + result.str = push_array_no_zero(arena, U8, needed_bytes); + result.size = raddbg_vsnprintf((char*)result.str, needed_bytes, fmt, args2); + result.str[result.size] = 0; + va_end(args2); + return(result); +} + +internal String8 +push_str8f(Arena *arena, char *fmt, ...){ + va_list args; + va_start(args, fmt); + String8 result = push_str8fv(arena, fmt, args); + va_end(args); + return(result); +} + +//////////////////////////////// +//~ rjf: String <=> Integer Conversions + +//- rjf: string -> integer + +internal S64 +sign_from_str8(String8 string, String8 *string_tail){ + // count negative signs + U64 neg_count = 0; + U64 i = 0; + for (; i < string.size; i += 1){ + if (string.str[i] == '-'){ + neg_count += 1; + } + else if (string.str[i] != '+'){ + break; + } + } + + // output part of string after signs + *string_tail = str8_skip(string, i); + + // output integer sign + S64 sign = (neg_count & 1)?-1:+1; + return(sign); +} + +internal B32 +str8_is_integer(String8 string, U32 radix){ + B32 result = 0; + if (string.size > 0){ + if (1 < radix && radix <= 16){ + result = 1; + for (U64 i = 0; i < string.size; i += 1){ + U8 c = string.str[i]; + if (!(c < 0x80) || integer_symbol_reverse[c] >= radix){ + result = 0; + break; + } + } + } + } + return(result); +} + +internal U64 +u64_from_str8(String8 string, U32 radix){ + U64 x = 0; + if (1 < radix && radix <= 16){ + for (U64 i = 0; i < string.size; i += 1){ + x *= radix; + x += integer_symbol_reverse[string.str[i]&0x7F]; + } + } + return(x); +} + +internal S64 +s64_from_str8(String8 string, U32 radix){ + S64 sign = sign_from_str8(string, &string); + S64 x = (S64)u64_from_str8(string, radix) * sign; + return(x); +} + +internal U32 +u32_from_str8(String8 string, U32 radix) +{ + U64 x64 = u64_from_str8(string, radix); + U32 x32 = safe_cast_u32(x64); + return x32; +} + +internal S32 +s32_from_str8(String8 string, U32 radix) +{ + S64 x64 = s64_from_str8(string, radix); + S32 x32 = safe_cast_s32(x64); + return x32; +} + +internal B32 +try_u64_from_str8_c_rules(String8 string, U64 *x){ + B32 is_integer = 0; + if (str8_is_integer(string, 10)){ + is_integer = 1; + *x = u64_from_str8(string, 10); + } + else{ + String8 hex_string = str8_skip(string, 2); + if (str8_match(str8_prefix(string, 2), str8_lit("0x"), 0) && + str8_is_integer(hex_string, 0x10)){ + is_integer = 1; + *x = u64_from_str8(hex_string, 0x10); + } + else if (str8_match(str8_prefix(string, 2), str8_lit("0b"), 0) && + str8_is_integer(hex_string, 2)){ + is_integer = 1; + *x = u64_from_str8(hex_string, 2); + } + else{ + String8 oct_string = str8_skip(string, 1); + if (str8_match(str8_prefix(string, 1), str8_lit("0"), 0) && + str8_is_integer(hex_string, 010)){ + is_integer = 1; + *x = u64_from_str8(oct_string, 010); + } + } + } + return(is_integer); +} + +internal B32 +try_s64_from_str8_c_rules(String8 string, S64 *x){ + String8 string_tail = {0}; + S64 sign = sign_from_str8(string, &string_tail); + U64 x_u64 = 0; + B32 is_integer = try_u64_from_str8_c_rules(string_tail, &x_u64); + *x = x_u64*sign; + return(is_integer); +} + +//- rjf: integer -> string + +internal String8 +str8_from_memory_size(Arena *arena, U64 size) +{ + String8 result; + + if(size < KB(1)) + { + result = push_str8f(arena, "%llu Bytes", size); + } + else if(size < MB(1)) + { + result = push_str8f(arena, "%llu.%02llu KiB", size / KB(1), ((size * 100) / KB(1)) % 100); + } + else if(size < GB(1)) + { + result = push_str8f(arena, "%llu.%02llu MiB", size / MB(1), ((size * 100) / MB(1)) % 100); + } + else if(size < TB(1)) + { + result = push_str8f(arena, "%llu.%02llu GiB", size / GB(1), ((size * 100) / GB(1)) % 100); + } + else + { + result = push_str8f(arena, "%llu.%02llu TiB", size / TB(1), ((size * 100) / TB(1)) % 100); + } + + return result; +} + +internal String8 +str8_from_count(Arena *arena, U64 count) +{ + String8 result; + + if(count < 1 * 1000) + { + result = push_str8f(arena, "%llu", count); + } + else if(count < 1000000) + { + U64 frac = ((count * 100) / 1000) % 100; + if(frac > 0) + { + result = push_str8f(arena, "%llu.%02lluK", count / 1000, frac); + } + else + { + result = push_str8f(arena, "%lluK", count / 1000); + } + } + else if(count < 1000000000) + { + U64 frac = ((count * 100) / 1000000) % 100; + if(frac > 0) + { + result = push_str8f(arena, "%llu.%02lluM", count / 1000000, frac); + } + else + { + result = push_str8f(arena, "%lluM", count / 1000000); + } + } + else + { + U64 frac = ((count * 100) * 1000000000) % 100; + if(frac > 0) + { + result = push_str8f(arena, "%llu.%02lluB", count / 1000000000, frac); + } + else + { + result = push_str8f(arena, "%lluB", count / 1000000000, frac); + } + } + + return result; +} + +internal String8 +str8_from_bits_u32(Arena *arena, U32 x) +{ + U8 c0 = 'a' + ((x >> 28) & 0xf); + U8 c1 = 'a' + ((x >> 24) & 0xf); + U8 c2 = 'a' + ((x >> 20) & 0xf); + U8 c3 = 'a' + ((x >> 16) & 0xf); + U8 c4 = 'a' + ((x >> 12) & 0xf); + U8 c5 = 'a' + ((x >> 8) & 0xf); + U8 c6 = 'a' + ((x >> 4) & 0xf); + U8 c7 = 'a' + ((x >> 0) & 0xf); + String8 result = push_str8f(arena, "%c%c%c%c%c%c%c%c", c0, c1, c2, c3, c4, c5, c6, c7); + return result; +} + +internal String8 +str8_from_bits_u64(Arena *arena, U64 x) +{ + U8 c0 = 'a' + ((x >> 60) & 0xf); + U8 c1 = 'a' + ((x >> 56) & 0xf); + U8 c2 = 'a' + ((x >> 52) & 0xf); + U8 c3 = 'a' + ((x >> 48) & 0xf); + U8 c4 = 'a' + ((x >> 44) & 0xf); + U8 c5 = 'a' + ((x >> 40) & 0xf); + U8 c6 = 'a' + ((x >> 36) & 0xf); + U8 c7 = 'a' + ((x >> 32) & 0xf); + U8 c8 = 'a' + ((x >> 28) & 0xf); + U8 c9 = 'a' + ((x >> 24) & 0xf); + U8 ca = 'a' + ((x >> 20) & 0xf); + U8 cb = 'a' + ((x >> 16) & 0xf); + U8 cc = 'a' + ((x >> 12) & 0xf); + U8 cd = 'a' + ((x >> 8) & 0xf); + U8 ce = 'a' + ((x >> 4) & 0xf); + U8 cf = 'a' + ((x >> 0) & 0xf); + String8 result = push_str8f(arena, + "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", + c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf); + return result; +} + +internal String8 +str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator) +{ + String8 result = {0}; + { + // rjf: prefix + String8 prefix = {0}; + switch(radix) + { + case 16:{prefix = str8_lit("0x");}break; + case 8: {prefix = str8_lit("0o");}break; + case 2: {prefix = str8_lit("0b");}break; + } + + // rjf: determine # of chars between separators + U8 digit_group_size = 3; + switch(radix) + { + default:break; + case 2: + case 8: + case 16: + {digit_group_size = 4;}break; + } + + // rjf: prep + U64 needed_leading_0s = 0; + { + U64 needed_digits = 1; + { + U64 u64_reduce = u64; + for(;;) + { + u64_reduce /= radix; + if(u64_reduce == 0) + { + break; + } + needed_digits += 1; + } + } + needed_leading_0s = (min_digits > needed_digits) ? min_digits - needed_digits : 0; + U64 needed_separators = 0; + if(digit_group_separator != 0) + { + needed_separators = (needed_digits+needed_leading_0s)/digit_group_size; + if(needed_separators > 0 && (needed_digits+needed_leading_0s)%digit_group_size == 0) + { + needed_separators -= 1; + } + } + result.size = prefix.size + needed_leading_0s + needed_separators + needed_digits; + result.str = push_array_no_zero(arena, U8, result.size + 1); + result.str[result.size] = 0; + } + + // rjf: fill contents + { + U64 u64_reduce = u64; + U64 digits_until_separator = digit_group_size; + for(U64 idx = 0; idx < result.size; idx += 1) + { + if(digits_until_separator == 0 && digit_group_separator != 0) + { + result.str[result.size - idx - 1] = digit_group_separator; + digits_until_separator = digit_group_size+1; + } + else + { + result.str[result.size - idx - 1] = char_to_lower(integer_symbols[u64_reduce%radix]); + u64_reduce /= radix; + } + digits_until_separator -= 1; + if(u64_reduce == 0) + { + break; + } + } + for(U64 leading_0_idx = 0; leading_0_idx < needed_leading_0s; leading_0_idx += 1) + { + result.str[prefix.size + leading_0_idx] = '0'; + } + } + + // rjf: fill prefix + if(prefix.size != 0) + { + MemoryCopy(result.str, prefix.str, prefix.size); + } + } + return result; +} + +internal String8 +str8_from_s64(Arena *arena, S64 s64, U32 radix, U8 min_digits, U8 digit_group_separator) +{ + String8 result = {0}; + // TODO(rjf): preeeeetty sloppy... + if(s64 < 0) + { + Temp scratch = scratch_begin(&arena, 1); + String8 numeric_part = str8_from_u64(scratch.arena, (U64)(-s64), radix, min_digits, digit_group_separator); + result = push_str8f(arena, "-%S", numeric_part); + scratch_end(scratch); + } + else + { + result = str8_from_u64(arena, (U64)s64, radix, min_digits, digit_group_separator); + } + return result; +} + +//////////////////////////////// +//~ rjf: String <=> Float Conversions + +internal F64 +f64_from_str8(String8 string) +{ + // TODO(rjf): crappy implementation for now that just uses atof. + F64 result = 0; + if(string.size > 0) + { + // rjf: find starting pos of numeric string, as well as sign + F64 sign = +1.0; + if(string.str[0] == '-') + { + sign = -1.0; + } + else if(string.str[0] == '+') + { + sign = 1.0; + } + + // rjf: gather numerics + U64 num_valid_chars = 0; + char buffer[64]; + B32 exp = 0; + for(U64 idx = 0; idx < string.size && num_valid_chars < sizeof(buffer)-1; idx += 1) + { + if(char_is_digit(string.str[idx], 10) || string.str[idx] == '.' || string.str[idx] == 'e' || + (exp && (string.str[idx] == '+' || string.str[idx] == '-'))) + { + buffer[num_valid_chars] = string.str[idx]; + num_valid_chars += 1; + exp = 0; + exp = (string.str[idx] == 'e'); + } + } + + // rjf: null-terminate (the reason for all of this!!!!!!) + buffer[num_valid_chars] = 0; + + // rjf: do final conversion + result = sign * atof(buffer); + } + return result; +} + +//////////////////////////////// +//~ rjf: String List Construction Functions + +internal String8Node* +str8_list_push_node(String8List *list, String8Node *node){ + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size += node->string.size; + return(node); +} + +internal String8Node* +str8_list_push_node_set_string(String8List *list, String8Node *node, String8 string){ + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size += string.size; + node->string = string; + return(node); +} + +internal String8Node* +str8_list_push_node_front(String8List *list, String8Node *node){ + SLLQueuePushFront(list->first, list->last, node); + list->node_count += 1; + list->total_size += node->string.size; + return(node); +} + +internal String8Node* +str8_list_push_node_front_set_string(String8List *list, String8Node *node, String8 string){ + SLLQueuePushFront(list->first, list->last, node); + list->node_count += 1; + list->total_size += string.size; + node->string = string; + return(node); +} + +internal String8Node* +str8_list_push(Arena *arena, String8List *list, String8 string){ + String8Node *node = push_array_no_zero(arena, String8Node, 1); + str8_list_push_node_set_string(list, node, string); + return(node); +} + +internal String8Node* +str8_list_push_front(Arena *arena, String8List *list, String8 string){ + String8Node *node = push_array_no_zero(arena, String8Node, 1); + str8_list_push_node_front_set_string(list, node, string); + return(node); +} + +internal void +str8_list_concat_in_place(String8List *list, String8List *to_push){ + if(to_push->node_count != 0){ + if (list->last){ + list->node_count += to_push->node_count; + list->total_size += to_push->total_size; + list->last->next = to_push->first; + list->last = to_push->last; + } + else{ + *list = *to_push; + } + MemoryZeroStruct(to_push); + } +} + +internal String8Node* +str8_list_push_aligner(Arena *arena, String8List *list, U64 min, U64 align){ + String8Node *node = push_array_no_zero(arena, String8Node, 1); + U64 new_size = list->total_size + min; + U64 increase_size = 0; + if (align > 1){ + // NOTE(allen): assert is power of 2 + Assert(((align - 1) & align) == 0); + U64 mask = align - 1; + new_size += mask; + new_size &= (~mask); + increase_size = new_size - list->total_size; + } + local_persist const U8 zeroes_buffer[64] = {0}; + Assert(increase_size <= ArrayCount(zeroes_buffer)); + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size = new_size; + node->string.str = (U8*)zeroes_buffer; + node->string.size = increase_size; + return(node); +} + +internal String8Node* +str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...){ + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(arena, fmt, args); + String8Node *result = str8_list_push(arena, list, string); + va_end(args); + return(result); +} + +internal String8Node* +str8_list_push_frontf(Arena *arena, String8List *list, char *fmt, ...){ + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(arena, fmt, args); + String8Node *result = str8_list_push_front(arena, list, string); + va_end(args); + return(result); +} + +internal String8List +str8_list_copy(Arena *arena, String8List *list){ + String8List result = {0}; + for (String8Node *node = list->first; + node != 0; + node = node->next){ + String8Node *new_node = push_array_no_zero(arena, String8Node, 1); + String8 new_string = push_str8_copy(arena, node->string); + str8_list_push_node_set_string(&result, new_node, new_string); + } + return(result); +} + +internal String8List +str8_split(Arena *arena, String8 string, U8 *split_chars, U64 split_char_count, StringSplitFlags flags){ + String8List list = {0}; + + B32 keep_empties = (flags & StringSplitFlag_KeepEmpties); + + U8 *ptr = string.str; + U8 *opl = string.str + string.size; + for (;ptr < opl;){ + U8 *first = ptr; + for (;ptr < opl; ptr += 1){ + U8 c = *ptr; + B32 is_split = 0; + for (U64 i = 0; i < split_char_count; i += 1){ + if (split_chars[i] == c){ + is_split = 1; + break; + } + } + if (is_split){ + break; + } + } + + String8 string = str8_range(first, ptr); + if (keep_empties || string.size > 0){ + str8_list_push(arena, &list, string); + } + ptr += 1; + } + + return(list); +} + +internal String8List +str8_split_by_string_chars(Arena *arena, String8 string, String8 split_chars, StringSplitFlags flags){ + String8List list = str8_split(arena, string, split_chars.str, split_chars.size, flags); + return list; +} + +internal String8List +str8_list_split_by_string_chars(Arena *arena, String8List list, String8 split_chars, StringSplitFlags flags){ + String8List result = {0}; + for (String8Node *node = list.first; node != 0; node = node->next){ + String8List split = str8_split_by_string_chars(arena, node->string, split_chars, flags); + str8_list_concat_in_place(&result, &split); + } + return result; +} + +internal String8 +str8_list_join(Arena *arena, String8List *list, StringJoin *optional_params){ + StringJoin join = {0}; + if (optional_params != 0){ + MemoryCopyStruct(&join, optional_params); + } + + U64 sep_count = 0; + if (list->node_count > 0){ + sep_count = list->node_count - 1; + } + + String8 result; + result.size = join.pre.size + join.post.size + sep_count*join.sep.size + list->total_size; + U8 *ptr = result.str = push_array_no_zero(arena, U8, result.size + 1); + + MemoryCopy(ptr, join.pre.str, join.pre.size); + ptr += join.pre.size; + for (String8Node *node = list->first; + node != 0; + node = node->next){ + MemoryCopy(ptr, node->string.str, node->string.size); + ptr += node->string.size; + if (node->next != 0){ + MemoryCopy(ptr, join.sep.str, join.sep.size); + ptr += join.sep.size; + } + } + MemoryCopy(ptr, join.post.str, join.post.size); + ptr += join.post.size; + + *ptr = 0; + + return(result); +} + +internal void +str8_list_from_flags(Arena *arena, String8List *list, + U32 flags, String8 *flag_string_table, U32 flag_string_count){ + for (U32 i = 0; i < flag_string_count; i += 1){ + U32 flag = (1 << i); + if (flags & flag){ + str8_list_push(arena, list, flag_string_table[i]); + } + } +} + +//////////////////////////////// +//~ rjf; String Arrays + +internal String8Array +str8_array_from_list(Arena *arena, String8List *list) +{ + String8Array array; + array.count = list->node_count; + array.v = push_array_no_zero(arena, String8, array.count); + U64 idx = 0; + for(String8Node *n = list->first; n != 0; n = n->next, idx += 1) + { + array.v[idx] = n->string; + } + return array; +} + +internal String8Array +str8_array_reserve(Arena *arena, U64 count) +{ + String8Array arr; + arr.count = 0; + arr.v = push_array(arena, String8, count); + return arr; +} + +//////////////////////////////// +//~ rjf: String Path Helpers + +internal String8 +str8_chop_last_slash(String8 string){ + if (string.size > 0){ + U8 *ptr = string.str + string.size - 1; + for (;ptr >= string.str; ptr -= 1){ + if (*ptr == '/' || *ptr == '\\'){ + break; + } + } + if (ptr >= string.str){ + string.size = (U64)(ptr - string.str); + } + else{ + string.size = 0; + } + } + return(string); +} + +internal String8 +str8_skip_last_slash(String8 string){ + if (string.size > 0){ + U8 *ptr = string.str + string.size - 1; + for (;ptr >= string.str; ptr -= 1){ + if (*ptr == '/' || *ptr == '\\'){ + break; + } + } + if (ptr >= string.str){ + ptr += 1; + string.size = (U64)(string.str + string.size - ptr); + string.str = ptr; + } + } + return(string); +} + +internal String8 +str8_chop_last_dot(String8 string) +{ + String8 result = string; + U64 p = string.size; + for (;p > 0;){ + p -= 1; + if (string.str[p] == '.'){ + result = str8_prefix(string, p); + break; + } + } + return(result); +} + +internal String8 +str8_skip_last_dot(String8 string){ + String8 result = string; + U64 p = string.size; + for (;p > 0;){ + p -= 1; + if (string.str[p] == '.'){ + result = str8_skip(string, p + 1); + break; + } + } + return(result); +} + +internal PathStyle +path_style_from_str8(String8 string){ + PathStyle result = PathStyle_Relative; + if (string.size >= 1 && string.str[0] == '/'){ + result = PathStyle_UnixAbsolute; + } + else if (string.size >= 2 && + char_is_alpha(string.str[0]) && + string.str[1] == ':'){ + if (string.size == 2 || + char_is_slash(string.str[2])){ + result = PathStyle_WindowsAbsolute; + } + } + return(result); +} + +internal String8List +str8_split_path(Arena *arena, String8 string){ + String8List result = str8_split(arena, string, (U8*)"/\\", 2, 0); + return(result); +} + +internal void +str8_path_list_resolve_dots_in_place(String8List *path, PathStyle style){ + Temp scratch = scratch_begin(0, 0); + + String8MetaNode *stack = 0; + String8MetaNode *free_meta_node = 0; + String8Node *first = path->first; + + MemoryZeroStruct(path); + for (String8Node *node = first, *next = 0; + node != 0; + node = next){ + // save next now + next = node->next; + + // cases: + if (node == first && style == PathStyle_WindowsAbsolute){ + goto save_without_stack; + } + if (node->string.size == 1 && node->string.str[0] == '.'){ + goto do_nothing; + } + if (node->string.size == 2 && node->string.str[0] == '.' && node->string.str[1] == '.'){ + if (stack != 0){ + goto eliminate_stack_top; + } + else{ + goto save_without_stack; + } + } + goto save_with_stack; + + + // handlers: + save_with_stack: + { + str8_list_push_node(path, node); + + String8MetaNode *stack_node = free_meta_node; + if (stack_node != 0){ + SLLStackPop(free_meta_node); + } + else{ + stack_node = push_array_no_zero(scratch.arena, String8MetaNode, 1); + } + SLLStackPush(stack, stack_node); + stack_node->node = node; + + continue; + } + + save_without_stack: + { + str8_list_push_node(path, node); + + continue; + } + + eliminate_stack_top: + { + path->node_count -= 1; + path->total_size -= stack->node->string.size; + + SLLStackPop(stack); + + if (stack == 0){ + path->last = path->first; + } + else{ + path->last = stack->node; + } + continue; + } + + do_nothing: continue; + } + scratch_end(scratch); +} + +internal String8 +str8_path_list_join_by_style(Arena *arena, String8List *path, PathStyle style){ + StringJoin params = {0}; + switch(style) + { + case PathStyle_Null:{}break; + case PathStyle_Relative: + case PathStyle_WindowsAbsolute: + { + params.sep = str8_lit("/"); + }break; + + case PathStyle_UnixAbsolute: + { + params.pre = str8_lit("/"); + params.sep = str8_lit("/"); + }break; + } + String8 result = str8_list_join(arena, path, ¶ms); + return result; +} + +internal String8TxtPtPair +str8_txt_pt_pair_from_string(String8 string) +{ + String8TxtPtPair pair = {0}; + { + String8 file_part = {0}; + String8 line_part = {0}; + String8 col_part = {0}; + + // rjf: grab file part + for(U64 idx = 0; idx <= string.size; idx += 1) + { + U8 byte = (idx < string.size) ? (string.str[idx]) : 0; + U8 next_byte = ((idx+1 < string.size) ? (string.str[idx+1]) : 0); + if(byte == ':' && next_byte != '/' && next_byte != '\\') + { + file_part = str8_prefix(string, idx); + line_part = str8_skip(string, idx+1); + break; + } + else if(byte == 0) + { + file_part = string; + break; + } + } + + // rjf: grab line/column + { + U64 colon_pos = str8_find_needle(line_part, 0, str8_lit(":"), 0); + if(colon_pos < line_part.size) + { + col_part = str8_skip(line_part, colon_pos+1); + line_part = str8_prefix(line_part, colon_pos); + } + } + + // rjf: convert line/column strings to numerics + U64 line = 0; + U64 column = 0; + try_u64_from_str8_c_rules(line_part, &line); + try_u64_from_str8_c_rules(col_part, &column); + + // rjf: fill + pair.string = file_part; + pair.pt = txt_pt((S64)line, (S64)column); + if(pair.pt.line == 0) { pair.pt.line = 1; } + if(pair.pt.column == 0) { pair.pt.column = 1; } + } + return pair; +} + +//////////////////////////////// +//~ rjf: UTF-8 & UTF-16 Decoding/Encoding + +read_only global U8 utf8_class[32] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,5, +}; + +internal UnicodeDecode +utf8_decode(U8 *str, U64 max){ + UnicodeDecode result = {1, max_U32}; + U8 byte = str[0]; + U8 byte_class = utf8_class[byte >> 3]; + switch (byte_class) + { + case 1: + { + result.codepoint = byte; + }break; + case 2: + { + if (2 < max) + { + U8 cont_byte = str[1]; + if (utf8_class[cont_byte >> 3] == 0) + { + result.codepoint = (byte & bitmask5) << 6; + result.codepoint |= (cont_byte & bitmask6); + result.inc = 2; + } + } + }break; + case 3: + { + if (2 < max) + { + U8 cont_byte[2] = {str[1], str[2]}; + if (utf8_class[cont_byte[0] >> 3] == 0 && + utf8_class[cont_byte[1] >> 3] == 0) + { + result.codepoint = (byte & bitmask4) << 12; + result.codepoint |= ((cont_byte[0] & bitmask6) << 6); + result.codepoint |= (cont_byte[1] & bitmask6); + result.inc = 3; + } + } + }break; + case 4: + { + if (3 < max) + { + U8 cont_byte[3] = {str[1], str[2], str[3]}; + if (utf8_class[cont_byte[0] >> 3] == 0 && + utf8_class[cont_byte[1] >> 3] == 0 && + utf8_class[cont_byte[2] >> 3] == 0) + { + result.codepoint = (byte & bitmask3) << 18; + result.codepoint |= ((cont_byte[0] & bitmask6) << 12); + result.codepoint |= ((cont_byte[1] & bitmask6) << 6); + result.codepoint |= (cont_byte[2] & bitmask6); + result.inc = 4; + } + } + } + } + return(result); +} + +internal UnicodeDecode +utf16_decode(U16 *str, U64 max){ + UnicodeDecode result = {1, max_U32}; + result.codepoint = str[0]; + result.inc = 1; + if (max > 1 && 0xD800 <= str[0] && str[0] < 0xDC00 && 0xDC00 <= str[1] && str[1] < 0xE000){ + result.codepoint = ((str[0] - 0xD800) << 10) | ((str[1] - 0xDC00) + 0x10000); + result.inc = 2; + } + return(result); +} + +internal U32 +utf8_encode(U8 *str, U32 codepoint){ + U32 inc = 0; + if (codepoint <= 0x7F){ + str[0] = (U8)codepoint; + inc = 1; + } + else if (codepoint <= 0x7FF){ + str[0] = (bitmask2 << 6) | ((codepoint >> 6) & bitmask5); + str[1] = bit8 | (codepoint & bitmask6); + inc = 2; + } + else if (codepoint <= 0xFFFF){ + str[0] = (bitmask3 << 5) | ((codepoint >> 12) & bitmask4); + str[1] = bit8 | ((codepoint >> 6) & bitmask6); + str[2] = bit8 | ( codepoint & bitmask6); + inc = 3; + } + else if (codepoint <= 0x10FFFF){ + str[0] = (bitmask4 << 4) | ((codepoint >> 18) & bitmask3); + str[1] = bit8 | ((codepoint >> 12) & bitmask6); + str[2] = bit8 | ((codepoint >> 6) & bitmask6); + str[3] = bit8 | ( codepoint & bitmask6); + inc = 4; + } + else{ + str[0] = '?'; + inc = 1; + } + return(inc); +} + +internal U32 +utf16_encode(U16 *str, U32 codepoint){ + U32 inc = 1; + if (codepoint == max_U32){ + str[0] = (U16)'?'; + } + else if (codepoint < 0x10000){ + str[0] = (U16)codepoint; + } + else{ + U32 v = codepoint - 0x10000; + str[0] = safe_cast_u16(0xD800 + (v >> 10)); + str[1] = safe_cast_u16(0xDC00 + (v & bitmask10)); + inc = 2; + } + return(inc); +} + +internal U32 +utf8_from_utf32_single(U8 *buffer, U32 character){ + return(utf8_encode(buffer, character)); +} + +//////////////////////////////// +//~ rjf: Unicode String Conversions + +internal String8 +str8_from_16(Arena *arena, String16 in) +{ + String8 result = str8_zero(); + if(in.size) + { + U64 cap = in.size*3; + U8 *str = push_array_no_zero(arena, U8, cap + 1); + U16 *ptr = in.str; + U16 *opl = ptr + in.size; + U64 size = 0; + UnicodeDecode consume; + for(;ptr < opl; ptr += consume.inc) + { + consume = utf16_decode(ptr, opl - ptr); + size += utf8_encode(str + size, consume.codepoint); + } + str[size] = 0; + arena_pop(arena, (cap - size)); + result = str8(str, size); + } + return result; +} + +internal String16 +str16_from_8(Arena *arena, String8 in) +{ + String16 result = str16_zero(); + if(in.size) + { + U64 cap = in.size*2; + U16 *str = push_array_no_zero(arena, U16, cap + 1); + U8 *ptr = in.str; + U8 *opl = ptr + in.size; + U64 size = 0; + UnicodeDecode consume; + for(;ptr < opl; ptr += consume.inc) + { + consume = utf8_decode(ptr, opl - ptr); + size += utf16_encode(str + size, consume.codepoint); + } + str[size] = 0; + arena_pop(arena, (cap - size)*2); + result = str16(str, size); + } + return result; +} + +internal String8 +str8_from_32(Arena *arena, String32 in) +{ + String8 result = str8_zero(); + if(in.size) + { + U64 cap = in.size*4; + U8 *str = push_array_no_zero(arena, U8, cap + 1); + U32 *ptr = in.str; + U32 *opl = ptr + in.size; + U64 size = 0; + for(;ptr < opl; ptr += 1) + { + size += utf8_encode(str + size, *ptr); + } + str[size] = 0; + arena_pop(arena, (cap - size)); + result = str8(str, size); + } + return result; +} + +internal String32 +str32_from_8(Arena *arena, String8 in) +{ + String32 result = str32_zero(); + if(in.size) + { + U64 cap = in.size; + U32 *str = push_array_no_zero(arena, U32, cap + 1); + U8 *ptr = in.str; + U8 *opl = ptr + in.size; + U64 size = 0; + UnicodeDecode consume; + for(;ptr < opl; ptr += consume.inc) + { + consume = utf8_decode(ptr, opl - ptr); + str[size] = consume.codepoint; + size += 1; + } + str[size] = 0; + arena_pop(arena, (cap - size)*4); + result = str32(str, size); + } + return result; +} + +//////////////////////////////// +//~ String -> Enum Conversions + +read_only global struct +{ + String8 string; + OperatingSystem os; +} g_os_enum_map[] = +{ + { str8_lit_comp(""), OperatingSystem_Null }, + { str8_lit_comp("Windows"), OperatingSystem_Windows, }, + { str8_lit_comp("Linux"), OperatingSystem_Linux, }, + { str8_lit_comp("Mac"), OperatingSystem_Mac, }, +}; +StaticAssert(ArrayCount(g_os_enum_map) == OperatingSystem_COUNT, g_os_enum_map_count_check); + +internal OperatingSystem +operating_system_from_string(String8 string) +{ + for(U64 i = 0; i < ArrayCount(g_os_enum_map); ++i) + { + if(str8_match(g_os_enum_map[i].string, string, StringMatchFlag_CaseInsensitive)) + { + return g_os_enum_map[i].os; + } + } + return OperatingSystem_Null; +} + +//////////////////////////////// +//~ rjf: Basic Types & Space Enum -> String Conversions + +internal String8 +string_from_dimension(Dimension dimension){ + local_persist String8 strings[] = { + str8_lit_comp("X"), + str8_lit_comp("Y"), + str8_lit_comp("Z"), + str8_lit_comp("W"), + }; + String8 result = str8_lit("error"); + if ((U32)dimension < 4){ + result = strings[dimension]; + } + return(result); +} + +internal String8 +string_from_side(Side side){ + local_persist String8 strings[] = { + str8_lit_comp("Min"), + str8_lit_comp("Max"), + }; + String8 result = str8_lit("error"); + if ((U32)side < 2){ + result = strings[side]; + } + return(result); +} + +internal String8 +string_from_operating_system(OperatingSystem os) +{ + String8 result = g_os_enum_map[OperatingSystem_Null].string; + if(os < ArrayCount(g_os_enum_map)) + { + result = g_os_enum_map[os].string; + } + return result; +} + +internal String8 +string_from_arch(Arch arch){ + local_persist String8 strings[] = { + str8_lit_comp("Null"), + str8_lit_comp("x64"), + str8_lit_comp("x86"), + str8_lit_comp("arm64"), + str8_lit_comp("arm32"), + }; + String8 result = str8_lit("error"); + if (arch < Arch_COUNT){ + result = strings[arch]; + } + return(result); +} + +//////////////////////////////// +//~ rjf: Time Types -> String + +internal String8 +string_from_week_day(WeekDay week_day){ + local_persist String8 strings[] = { + str8_lit_comp("Sun"), + str8_lit_comp("Mon"), + str8_lit_comp("Tue"), + str8_lit_comp("Wed"), + str8_lit_comp("Thu"), + str8_lit_comp("Fri"), + str8_lit_comp("Sat"), + }; + String8 result = str8_lit("Err"); + if ((U32)week_day < WeekDay_COUNT){ + result = strings[week_day]; + } + return(result); +} + +internal String8 +string_from_month(Month month){ + local_persist String8 strings[] = { + str8_lit_comp("Jan"), + str8_lit_comp("Feb"), + str8_lit_comp("Mar"), + str8_lit_comp("Apr"), + str8_lit_comp("May"), + str8_lit_comp("Jun"), + str8_lit_comp("Jul"), + str8_lit_comp("Aug"), + str8_lit_comp("Sep"), + str8_lit_comp("Oct"), + str8_lit_comp("Nov"), + str8_lit_comp("Dec"), + }; + String8 result = str8_lit("Err"); + if ((U32)month < Month_COUNT){ + result = strings[month]; + } + return(result); +} + +internal String8 +push_date_time_string(Arena *arena, DateTime *date_time){ + char *mon_str = (char*)string_from_month(date_time->month).str; + U32 adjusted_hour = date_time->hour%12; + if (adjusted_hour == 0){ + adjusted_hour = 12; + } + char *ampm = "am"; + if (date_time->hour >= 12){ + ampm = "pm"; + } + String8 result = push_str8f(arena, "%d %s %d, %02d:%02d:%02d %s", + date_time->day, mon_str, date_time->year, + adjusted_hour, date_time->min, date_time->sec, ampm); + return(result); +} + +internal String8 +push_file_name_date_time_string(Arena *arena, DateTime *date_time){ + char *mon_str = (char*)string_from_month(date_time->month).str; + String8 result = push_str8f(arena, "%d-%s-%0d--%02d-%02d-%02d", + date_time->year, mon_str, date_time->day, + date_time->hour, date_time->min, date_time->sec); + return(result); +} + +internal String8 +string_from_elapsed_time(Arena *arena, DateTime dt){ + Temp scratch = scratch_begin(&arena, 1); + String8List list = {0}; + if (dt.year){ + str8_list_pushf(scratch.arena, &list, "%dy", dt.year); + str8_list_pushf(scratch.arena, &list, "%um", dt.mon); + str8_list_pushf(scratch.arena, &list, "%ud", dt.day); + } else if (dt.mon){ + str8_list_pushf(scratch.arena, &list, "%um", dt.mon); + str8_list_pushf(scratch.arena, &list, "%ud", dt.day); + } else if (dt.day){ + str8_list_pushf(scratch.arena, &list, "%ud", dt.day); + } + str8_list_pushf(scratch.arena, &list, "%u:%u:%u:%u ms", dt.hour, dt.min, dt.sec, dt.msec); + StringJoin join = { str8_lit_comp(""), str8_lit_comp(" "), str8_lit_comp("") }; + String8 result = str8_list_join(arena, &list, &join); + scratch_end(scratch); + return(result); +} + +//////////////////////////////// +//~ Globally UNique Ids + +internal String8 +string_from_guid(Arena *arena, Guid guid) +{ + String8 result = push_str8f(arena, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + guid.data1, + guid.data2, + guid.data3, + guid.data4[0], + guid.data4[1], + guid.data4[2], + guid.data4[3], + guid.data4[4], + guid.data4[5], + guid.data4[6], + guid.data4[7]); + return result; +} + +internal B32 +try_guid_from_string(String8 string, Guid *guid_out) +{ + Temp scratch = scratch_begin(0,0); + B32 is_parsed = 0; + String8List list = str8_split_by_string_chars(scratch.arena, string, str8_lit("-"), StringSplitFlag_KeepEmpties); + if(list.node_count == 5) + { + String8 data1_str = list.first->string; + String8 data2_str = list.first->next->string; + String8 data3_str = list.first->next->next->string; + String8 data4_hi_str = list.first->next->next->next->string; + String8 data4_lo_str = list.first->next->next->next->next->string; + if(str8_is_integer(data1_str, 16) && + str8_is_integer(data2_str, 16) && + str8_is_integer(data3_str, 16) && + str8_is_integer(data4_hi_str, 16) && + str8_is_integer(data4_lo_str, 16)) + { + U64 data1 = u64_from_str8(data1_str, 16); + U64 data2 = u64_from_str8(data2_str, 16); + U64 data3 = u64_from_str8(data3_str, 16); + U64 data4_hi = u64_from_str8(data4_hi_str, 16); + U64 data4_lo = u64_from_str8(data4_lo_str, 16); + if(data1 <= max_U32 && + data2 <= max_U16 && + data3 <= max_U16 && + data4_hi <= max_U16 && + data4_lo <= 0xffffffffffff) + { + guid_out->data1 = (U32)data1; + guid_out->data2 = (U16)data2; + guid_out->data3 = (U16)data3; + U64 data4 = (data4_hi << 48) | data4_lo; + MemoryCopy(&guid_out->data4[0], &data4, sizeof(data4)); + is_parsed = 1; + } + } + } + scratch_end(scratch); + return is_parsed; +} + +internal Guid +guid_from_string(String8 string) +{ + Guid guid = {0}; + try_guid_from_string(string, &guid); + return guid; +} + +//////////////////////////////// +//~ rjf: Basic Text Indentation + +internal String8 +indented_from_string(Arena *arena, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + read_only local_persist U8 indentation_bytes[] = " "; + String8List indented_strings = {0}; + S64 depth = 0; + S64 next_depth = 0; + U64 line_begin_off = 0; + for(U64 off = 0; off <= string.size; off += 1) + { + U8 byte = off width_this_line){ + String8 line = str8_substr(string, line_range); + if (wrapped_indent_level > 0){ + line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); + } + str8_list_push(arena, &list, line); + line_range = r1u64(line_range.max+1, candidate_line_range.max); + wrapped_indent_level = ClampTop(64, wrap_indent); + } + else{ + line_range = candidate_line_range; + } + } + } + if (line_range.min < string.size && line_range.max > line_range.min){ + String8 line = str8_substr(string, line_range); + if (wrapped_indent_level > 0){ + line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); + } + str8_list_push(arena, &list, line); + } + return list; +} + +//////////////////////////////// +//~ rjf: String <-> Color + +internal String8 +hex_string_from_rgba_4f32(Arena *arena, Vec4F32 rgba) +{ + String8 hex_string = push_str8f(arena, "%02x%02x%02x%02x", (U8)(rgba.x*255.f), (U8)(rgba.y*255.f), (U8)(rgba.z*255.f), (U8)(rgba.w*255.f)); + return hex_string; +} + +internal Vec4F32 +rgba_from_hex_string_4f32(String8 hex_string) +{ + U8 byte_text[8] = {0}; + U64 byte_text_idx = 0; + for(U64 idx = 0; idx < hex_string.size && byte_text_idx < ArrayCount(byte_text); idx += 1) + { + if(char_is_digit(hex_string.str[idx], 16)) + { + byte_text[byte_text_idx] = char_to_lower(hex_string.str[idx]); + byte_text_idx += 1; + } + } + U8 byte_vals[4] = {0}; + for(U64 idx = 0; idx < 4; idx += 1) + { + byte_vals[idx] = (U8)u64_from_str8(str8(&byte_text[idx*2], 2), 16); + } + Vec4F32 rgba = v4f32(byte_vals[0]/255.f, byte_vals[1]/255.f, byte_vals[2]/255.f, byte_vals[3]/255.f); + return rgba; +} + +//////////////////////////////// +//~ rjf: String Fuzzy Matching + +internal FuzzyMatchRangeList +fuzzy_match_find(Arena *arena, String8 needle, String8 haystack) +{ + FuzzyMatchRangeList result = {0}; + Temp scratch = scratch_begin(&arena, 1); + String8List needles = str8_split(scratch.arena, needle, (U8*)" ", 1, 0); + result.needle_part_count = needles.node_count; + for(String8Node *needle_n = needles.first; needle_n != 0; needle_n = needle_n->next) + { + U64 find_pos = 0; + for(;find_pos < haystack.size;) + { + find_pos = str8_find_needle(haystack, find_pos, needle_n->string, StringMatchFlag_CaseInsensitive); + B32 is_in_gathered_ranges = 0; + for(FuzzyMatchRangeNode *n = result.first; n != 0; n = n->next) + { + if(n->range.min <= find_pos && find_pos < n->range.max) + { + is_in_gathered_ranges = 1; + find_pos = n->range.max; + break; + } + } + if(!is_in_gathered_ranges) + { + break; + } + } + if(find_pos < haystack.size) + { + Rng1U64 range = r1u64(find_pos, find_pos+needle_n->string.size); + FuzzyMatchRangeNode *n = push_array(arena, FuzzyMatchRangeNode, 1); + n->range = range; + SLLQueuePush(result.first, result.last, n); + result.count += 1; + result.total_dim += dim_1u64(range); + } + } + scratch_end(scratch); + return result; +} + +internal FuzzyMatchRangeList +fuzzy_match_range_list_copy(Arena *arena, FuzzyMatchRangeList *src) +{ + FuzzyMatchRangeList dst = {0}; + for(FuzzyMatchRangeNode *src_n = src->first; src_n != 0; src_n = src_n->next) + { + FuzzyMatchRangeNode *dst_n = push_array(arena, FuzzyMatchRangeNode, 1); + SLLQueuePush(dst.first, dst.last, dst_n); + dst_n->range = src_n->range; + } + dst.count = src->count; + dst.needle_part_count = src->needle_part_count; + dst.total_dim = src->total_dim; + return dst; +} + +//////////////////////////////// +//~ NOTE(allen): Serialization Helpers + +internal void +str8_serial_begin(Arena *arena, String8List *srl){ + String8Node *node = push_array(arena, String8Node, 1); + node->string.str = push_array_no_zero(arena, U8, 0); + srl->first = srl->last = node; + srl->node_count = 1; + srl->total_size = 0; +} + +internal String8 +str8_serial_end(Arena *arena, String8List *srl){ + U64 size = srl->total_size; + U8 *out = push_array_no_zero(arena, U8, size); + str8_serial_write_to_dst(srl, out); + String8 result = str8(out, size); + return result; +} + +internal void +str8_serial_write_to_dst(String8List *srl, void *out){ + U8 *ptr = (U8*)out; + for (String8Node *node = srl->first; + node != 0; + node = node->next){ + U64 size = node->string.size; + MemoryCopy(ptr, node->string.str, size); + ptr += size; + } +} + +internal U64 +str8_serial_push_align(Arena *arena, String8List *srl, U64 align){ + Assert(IsPow2(align)); + + U64 pos = srl->total_size; + U64 new_pos = AlignPow2(pos, align); + U64 size = (new_pos - pos); + + if(size != 0) + { + U8 *buf = push_array(arena, U8, size); + + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += size; + srl->total_size += size; + } + else{ + str8_list_push(arena, srl, str8(buf, size)); + } + } + return size; +} + +internal void * +str8_serial_push_size(Arena *arena, String8List *srl, U64 size) +{ + void *result = 0; + if(size != 0) + { + U8 *buf = push_array_no_zero(arena, U8, size); + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += size; + srl->total_size += size; + } + else{ + str8_list_push(arena, srl, str8(buf, size)); + } + result = buf; + } + return result; +} + +internal void * +str8_serial_push_data(Arena *arena, String8List *srl, void *data, U64 size){ + void *result = str8_serial_push_size(arena, srl, size); + if(result != 0) + { + MemoryCopy(result, data, size); + } + return result; +} + +internal void +str8_serial_push_data_list(Arena *arena, String8List *srl, String8Node *first){ + for (String8Node *node = first; + node != 0; + node = node->next){ + str8_serial_push_data(arena, srl, node->string.str, node->string.size); + } +} + +internal void +str8_serial_push_u64(Arena *arena, String8List *srl, U64 x){ + U8 *buf = push_array_no_zero(arena, U8, 8); + MemoryCopy(buf, &x, 8); + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += 8; + srl->total_size += 8; + } + else{ + str8_list_push(arena, srl, str8(buf, 8)); + } +} + +internal void +str8_serial_push_u32(Arena *arena, String8List *srl, U32 x){ + U8 *buf = push_array_no_zero(arena, U8, 4); + MemoryCopy(buf, &x, 4); + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += 4; + srl->total_size += 4; + } + else{ + str8_list_push(arena, srl, str8(buf, 4)); + } +} + +internal void +str8_serial_push_u16(Arena *arena, String8List *srl, U16 x){ + str8_serial_push_data(arena, srl, &x, sizeof(x)); +} + +internal void +str8_serial_push_u8(Arena *arena, String8List *srl, U8 x){ + str8_serial_push_data(arena, srl, &x, sizeof(x)); +} + +internal void +str8_serial_push_cstr(Arena *arena, String8List *srl, String8 str){ + str8_serial_push_data(arena, srl, str.str, str.size); + str8_serial_push_u8(arena, srl, 0); +} + +internal void +str8_serial_push_string(Arena *arena, String8List *srl, String8 str){ + str8_serial_push_data(arena, srl, str.str, str.size); +} + +//////////////////////////////// +//~ rjf: Deserialization Helpers + +internal U64 +str8_deserial_read(String8 string, U64 off, void *read_dst, U64 read_size, U64 granularity) +{ + U64 bytes_left = string.size-Min(off, string.size); + U64 actually_readable_size = Min(bytes_left, read_size); + U64 legally_readable_size = actually_readable_size - actually_readable_size%granularity; + if(legally_readable_size > 0) + { + MemoryCopy(read_dst, string.str+off, legally_readable_size); + } + return legally_readable_size; +} + +internal U64 +str8_deserial_find_first_match(String8 string, U64 off, U16 scan_val) +{ + U64 cursor = off; + for (;;) { + U16 val = 0; + str8_deserial_read_struct(string, cursor, &val); + if (val == scan_val) { + break; + } + cursor += sizeof(val); + } + return cursor; +} + +internal void * +str8_deserial_get_raw_ptr(String8 string, U64 off, U64 size) +{ + void *raw_ptr = 0; + if (off + size <= string.size) { + raw_ptr = string.str + off; + } + return raw_ptr; +} + +internal U64 +str8_deserial_read_cstr(String8 string, U64 off, String8 *cstr_out) +{ + U64 cstr_size = 0; + if (off < string.size) { + U8 *ptr = string.str + off; + U8 *cap = string.str + string.size; + *cstr_out = str8_cstring_capped(ptr, cap); + cstr_size = (cstr_out->size + 1); + } + return cstr_size; +} + +internal U64 +str8_deserial_read_windows_utf16_string16(String8 string, U64 off, String16 *str_out) +{ + U64 null_off = str8_deserial_find_first_match(string, off, 0); + U64 size = null_off - off; + U16 *str = (U16 *)str8_deserial_get_raw_ptr(string, off, size); + U64 count = size / sizeof(*str); + *str_out = str16(str, count); + + U64 read_size_with_null = size + sizeof(*str); + return read_size_with_null; +} + +internal U64 +str8_deserial_read_block(String8 string, U64 off, U64 size, String8 *block_out) +{ + Rng1U64 range = rng_1u64(off, off + size); + *block_out = str8_substr(string, range); + return block_out->size; +} + +internal U64 +str8_deserial_read_uleb128(String8 string, U64 off, U64 *value_out) +{ + U64 value = 0; + U64 shift = 0; + U64 cursor = off; + for(;;) + { + U8 byte = 0; + U64 bytes_read = str8_deserial_read_struct(string, cursor, &byte); + + if(bytes_read != sizeof(byte)) + { + break; + } + + U8 val = byte & 0x7fu; + value |= ((U64)val) << shift; + + cursor += bytes_read; + shift += 7u; + + if((byte & 0x80u) == 0) + { + break; + } + } + if(value_out != 0) + { + *value_out = value; + } + U64 bytes_read = cursor - off; + return bytes_read; +} + +internal U64 +str8_deserial_read_sleb128(String8 string, U64 off, S64 *value_out) +{ + U64 value = 0; + U64 shift = 0; + U64 cursor = off; + for(;;) + { + U8 byte; + U64 bytes_read = str8_deserial_read_struct(string, cursor, &byte); + if(bytes_read != sizeof(byte)) + { + break; + } + + U8 val = byte & 0x7fu; + value |= ((U64)val) << shift; + + cursor += bytes_read; + shift += 7u; + + if((byte & 0x80u) == 0) + { + if(shift < sizeof(value) * 8 && (byte & 0x40u) != 0) + { + value |= -(S64)(1ull << shift); + } + break; + } + } + if(value_out != 0) + { + *value_out = value; + } + U64 bytes_read = cursor - off; + return bytes_read; +} + diff --git a/src/base/base_strings.h b/src/base/base_strings.h index 67e90349a..e66d19c8f 100644 --- a/src/base/base_strings.h +++ b/src/base/base_strings.h @@ -1,415 +1,417 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef BASE_STRINGS_H -#define BASE_STRINGS_H - -//////////////////////////////// -//~ rjf: Third Party Includes - -#define STB_SPRINTF_DECORATE(name) raddbg_##name -#include "third_party/stb/stb_sprintf.h" - -//////////////////////////////// -//~ rjf: String Types - -typedef struct String8 String8; -struct String8 -{ - U8 *str; - U64 size; -}; - -typedef struct String16 String16; -struct String16 -{ - U16 *str; - U64 size; -}; - -typedef struct String32 String32; -struct String32 -{ - U32 *str; - U64 size; -}; - -//////////////////////////////// -//~ rjf: String List & Array Types - -typedef struct String8Node String8Node; -struct String8Node -{ - String8Node *next; - String8 string; -}; - -typedef struct String8MetaNode String8MetaNode; -struct String8MetaNode -{ - String8MetaNode *next; - String8Node *node; -}; - -typedef struct String8List String8List; -struct String8List -{ - String8Node *first; - String8Node *last; - U64 node_count; - U64 total_size; -}; - -typedef struct String8Array String8Array; -struct String8Array -{ - String8 *v; - U64 count; -}; - -//////////////////////////////// -//~ rjf: String Matching, Splitting, & Joining Types - -typedef U32 StringMatchFlags; -enum -{ - StringMatchFlag_CaseInsensitive = (1 << 0), - StringMatchFlag_RightSideSloppy = (1 << 1), - StringMatchFlag_SlashInsensitive = (1 << 2), -}; - -typedef U32 StringSplitFlags; -enum -{ - StringSplitFlag_KeepEmpties = (1 << 0), -}; - -typedef enum PathStyle -{ - PathStyle_Null, - PathStyle_Relative, - PathStyle_WindowsAbsolute, - PathStyle_UnixAbsolute, - -#if OS_WINDOWS - PathStyle_SystemAbsolute = PathStyle_WindowsAbsolute -#elif OS_LINUX - PathStyle_SystemAbsolute = PathStyle_UnixAbsolute -#else -# error "absolute path style is undefined for this OS" -#endif -} -PathStyle; - -typedef struct StringJoin StringJoin; -struct StringJoin -{ - String8 pre; - String8 sep; - String8 post; -}; - -//////////////////////////////// -//~ rjf: String Pair Types - -typedef struct String8TxtPtPair String8TxtPtPair; -struct String8TxtPtPair -{ - String8 string; - TxtPt pt; -}; - -//////////////////////////////// -//~ rjf: UTF Decoding Types - -typedef struct UnicodeDecode UnicodeDecode; -struct UnicodeDecode -{ - U32 inc; - U32 codepoint; -}; - -//////////////////////////////// -//~ rjf: String Fuzzy Matching Types - -typedef struct FuzzyMatchRangeNode FuzzyMatchRangeNode; -struct FuzzyMatchRangeNode -{ - FuzzyMatchRangeNode *next; - Rng1U64 range; -}; - -typedef struct FuzzyMatchRangeList FuzzyMatchRangeList; -struct FuzzyMatchRangeList -{ - FuzzyMatchRangeNode *first; - FuzzyMatchRangeNode *last; - U64 count; - U64 needle_part_count; - U64 total_dim; -}; - -//////////////////////////////// -//~ rjf: Character Classification & Conversion Functions - -internal B32 char_is_space(U8 c); -internal B32 char_is_upper(U8 c); -internal B32 char_is_lower(U8 c); -internal B32 char_is_alpha(U8 c); -internal B32 char_is_slash(U8 c); -internal B32 char_is_digit(U8 c, U32 base); -internal U8 char_to_lower(U8 c); -internal U8 char_to_upper(U8 c); -internal U8 char_to_correct_slash(U8 c); - -//////////////////////////////// -//~ rjf: C-String Measurement - -internal U64 cstring8_length(U8 *c); -internal U64 cstring16_length(U16 *c); -internal U64 cstring32_length(U32 *c); - -//////////////////////////////// -//~ rjf: String Constructors - -#define str8_lit(S) str8((U8*)(S), sizeof(S) - 1) -#define str8_lit_comp(S) {(U8*)(S), sizeof(S) - 1,} -#define str8_varg(S) (int)((S).size), ((S).str) - -#define str8_array(S,C) str8((U8*)(S), sizeof(*(S))*(C)) -#define str8_array_fixed(S) str8((U8*)(S), sizeof(S)) -#define str8_struct(S) str8((U8*)(S), sizeof(*(S))) - -internal String8 str8(U8 *str, U64 size); -internal String8 str8_range(U8 *first, U8 *one_past_last); -internal String8 str8_zero(void); -internal String16 str16(U16 *str, U64 size); -internal String16 str16_range(U16 *first, U16 *one_past_last); -internal String16 str16_zero(void); -internal String32 str32(U32 *str, U64 size); -internal String32 str32_range(U32 *first, U32 *one_past_last); -internal String32 str32_zero(void); -internal String8 str8_cstring(char *c); -internal String16 str16_cstring(U16 *c); -internal String32 str32_cstring(U32 *c); -internal String8 str8_cstring_capped(void *cstr, void *cap); -internal String16 str16_cstring_capped(void *cstr, void *cap); -internal String8 str8_cstring_capped_reverse(void *raw_start, void *raw_cap); - -//////////////////////////////// -//~ rjf: String Stylization - -internal String8 upper_from_str8(Arena *arena, String8 string); -internal String8 lower_from_str8(Arena *arena, String8 string); -internal String8 backslashed_from_str8(Arena *arena, String8 string); - -//////////////////////////////// -//~ rjf: String Matching - -#define str8_match_lit(a_lit, b, flags) str8_match(str8_lit(a_lit), (b), (flags)) -#define str8_match_cstr(a_cstr, b, flags) str8_match(str8_cstring(a_cstr), (b), (flags)) -internal B32 str8_match(String8 a, String8 b, StringMatchFlags flags); -internal U64 str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags); -internal U64 str8_find_needle_reverse(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags); -internal B32 str8_ends_with(String8 string, String8 end, StringMatchFlags flags); -#define str8_ends_with_lit(string, end_lit, flags) str8_ends_with((string), str8_lit(end_lit), (flags)) - -//////////////////////////////// -//~ rjf: String Slicing - -internal String8 str8_substr(String8 str, Rng1U64 range); -internal String8 str8_prefix(String8 str, U64 size); -internal String8 str8_skip(String8 str, U64 amt); -internal String8 str8_postfix(String8 str, U64 size); -internal String8 str8_chop(String8 str, U64 amt); -internal String8 str8_skip_chop_whitespace(String8 string); - -//////////////////////////////// -//~ rjf: String Formatting & Copying - -internal String8 push_str8_cat(Arena *arena, String8 s1, String8 s2); -internal String8 push_str8_copy(Arena *arena, String8 s); -internal String8 push_str8fv(Arena *arena, char *fmt, va_list args); -internal String8 push_str8f(Arena *arena, char *fmt, ...); - -//////////////////////////////// -//~ rjf: String <=> Integer Conversions - -//- rjf: string -> integer -internal S64 sign_from_str8(String8 string, String8 *string_tail); -internal B32 str8_is_integer(String8 string, U32 radix); - -internal U64 u64_from_str8(String8 string, U32 radix); -internal S64 s64_from_str8(String8 string, U32 radix); -internal U32 u32_from_str8(String8 string, U32 radix); -internal S32 s32_from_str8(String8 string, U32 radix); -internal B32 try_u64_from_str8_c_rules(String8 string, U64 *x); -internal B32 try_s64_from_str8_c_rules(String8 string, S64 *x); - -//- rjf: integer -> string -internal String8 str8_from_memory_size(Arena *arena, U64 size); -internal String8 str8_from_count(Arena *arena, U64 count); -internal String8 str8_from_bits_u32(Arena *arena, U32 x); -internal String8 str8_from_bits_u64(Arena *arena, U64 x); -internal String8 str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator); -internal String8 str8_from_s64(Arena *arena, S64 s64, U32 radix, U8 min_digits, U8 digit_group_separator); - -//////////////////////////////// -//~ rjf: String <=> Float Conversions - -internal F64 f64_from_str8(String8 string); - -//////////////////////////////// -//~ rjf: String List Construction Functions - -internal String8Node* str8_list_push_node(String8List *list, String8Node *node); -internal String8Node* str8_list_push_node_set_string(String8List *list, String8Node *node, String8 string); -internal String8Node* str8_list_push_node_front(String8List *list, String8Node *node); -internal String8Node* str8_list_push_node_front_set_string(String8List *list, String8Node *node, String8 string); -internal String8Node* str8_list_push(Arena *arena, String8List *list, String8 string); -internal String8Node* str8_list_push_front(Arena *arena, String8List *list, String8 string); -internal void str8_list_concat_in_place(String8List *list, String8List *to_push); -internal String8Node* str8_list_push_aligner(Arena *arena, String8List *list, U64 min, U64 align); -internal String8Node* str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...); -internal String8Node* str8_list_push_frontf(Arena *arena, String8List *list, char *fmt, ...); -internal String8List str8_list_copy(Arena *arena, String8List *list); -#define str8_list_first(list) ((list)->first ? (list)->first->string : str8_zero()) - -//////////////////////////////// -//~ rjf: String Splitting & Joining - -internal String8List str8_split(Arena *arena, String8 string, U8 *split_chars, U64 split_char_count, StringSplitFlags flags); -internal String8List str8_split_by_string_chars(Arena *arena, String8 string, String8 split_chars, StringSplitFlags flags); -internal String8List str8_list_split_by_string_chars(Arena *arena, String8List list, String8 split_chars, StringSplitFlags flags); -internal String8 str8_list_join(Arena *arena, String8List *list, StringJoin *optional_params); -internal void str8_list_from_flags(Arena *arena, String8List *list, U32 flags, String8 *flag_string_table, U32 flag_string_count); - -//////////////////////////////// -//~ rjf; String Arrays - -internal String8Array str8_array_from_list(Arena *arena, String8List *list); -internal String8Array str8_array_reserve(Arena *arena, U64 count); - -//////////////////////////////// -//~ rjf: String Path Helpers - -internal String8 str8_chop_last_slash(String8 string); -internal String8 str8_skip_last_slash(String8 string); -internal String8 str8_chop_last_dot(String8 string); -internal String8 str8_skip_last_dot(String8 string); - -internal PathStyle path_style_from_str8(String8 string); -internal String8List str8_split_path(Arena *arena, String8 string); -internal void str8_path_list_resolve_dots_in_place(String8List *path, PathStyle style); -internal String8 str8_path_list_join_by_style(Arena *arena, String8List *path, PathStyle style); - -internal String8TxtPtPair str8_txt_pt_pair_from_string(String8 string); - -//////////////////////////////// -//~ rjf: UTF-8 & UTF-16 Decoding/Encoding - -internal UnicodeDecode utf8_decode(U8 *str, U64 max); -internal UnicodeDecode utf16_decode(U16 *str, U64 max); -internal U32 utf8_encode(U8 *str, U32 codepoint); -internal U32 utf16_encode(U16 *str, U32 codepoint); -internal U32 utf8_from_utf32_single(U8 *buffer, U32 character); - -//////////////////////////////// -//~ rjf: Unicode String Conversions - -internal String8 str8_from_16(Arena *arena, String16 in); -internal String16 str16_from_8(Arena *arena, String8 in); -internal String8 str8_from_32(Arena *arena, String32 in); -internal String32 str32_from_8(Arena *arena, String8 in); - -//////////////////////////////// -//~ String -> Enum Conversions - -internal OperatingSystem operating_system_from_string(String8 string); - -//////////////////////////////// -//~ rjf: Basic Types & Space Enum -> String Conversions - -internal String8 string_from_dimension(Dimension dimension); -internal String8 string_from_side(Side side); -internal String8 string_from_operating_system(OperatingSystem os); -internal String8 string_from_arch(Arch arch); - -//////////////////////////////// -//~ rjf: Time Types -> String - -internal String8 string_from_week_day(WeekDay week_day); -internal String8 string_from_month(Month month); -internal String8 push_date_time_string(Arena *arena, DateTime *date_time); -internal String8 push_file_name_date_time_string(Arena *arena, DateTime *date_time); -internal String8 string_from_elapsed_time(Arena *arena, DateTime dt); - -//////////////////////////////// -//~ Globally Unique Ids - -internal String8 string_from_guid(Arena *arena, Guid guid); -internal B32 try_guid_from_string(String8 string, Guid *guid_out); -internal Guid guid_from_string(String8 string); - -//////////////////////////////// -//~ rjf: Basic Text Indentation - -internal String8 indented_from_string(Arena *arena, String8 string); - -//////////////////////////////// -//~ rjf: Text Escaping - -internal String8 escaped_from_raw_str8(Arena *arena, String8 string); -internal String8 raw_from_escaped_str8(Arena *arena, String8 string); - -//////////////////////////////// -//~ rjf: Text Wrapping - -internal String8List wrapped_lines_from_string(Arena *arena, String8 string, U64 first_line_max_width, U64 max_width, U64 wrap_indent); - -//////////////////////////////// -//~ rjf: String <-> Color - -internal String8 hex_string_from_rgba_4f32(Arena *arena, Vec4F32 rgba); -internal Vec4F32 rgba_from_hex_string_4f32(String8 hex_string); - -//////////////////////////////// -//~ rjf: String Fuzzy Matching - -internal FuzzyMatchRangeList fuzzy_match_find(Arena *arena, String8 needle, String8 haystack); -internal FuzzyMatchRangeList fuzzy_match_range_list_copy(Arena *arena, FuzzyMatchRangeList *src); - -//////////////////////////////// -//~ NOTE(allen): Serialization Helpers - -internal void str8_serial_begin(Arena *arena, String8List *srl); -internal String8 str8_serial_end(Arena *arena, String8List *srl); -internal void str8_serial_write_to_dst(String8List *srl, void *out); -internal U64 str8_serial_push_align(Arena *arena, String8List *srl, U64 align); -internal void * str8_serial_push_size(Arena *arena, String8List *srl, U64 size); -internal void * str8_serial_push_data(Arena *arena, String8List *srl, void *data, U64 size); -internal void str8_serial_push_data_list(Arena *arena, String8List *srl, String8Node *first); -internal void str8_serial_push_u64(Arena *arena, String8List *srl, U64 x); -internal void str8_serial_push_u32(Arena *arena, String8List *srl, U32 x); -internal void str8_serial_push_u16(Arena *arena, String8List *srl, U16 x); -internal void str8_serial_push_u8(Arena *arena, String8List *srl, U8 x); -internal void str8_serial_push_cstr(Arena *arena, String8List *srl, String8 str); -internal void str8_serial_push_string(Arena *arena, String8List *srl, String8 str); -#define str8_serial_push_array(arena, srl, ptr, count) str8_serial_push_data(arena, srl, ptr, sizeof(*(ptr)) * (count)) -#define str8_serial_push_struct(arena, srl, ptr) str8_serial_push_array(arena, srl, ptr, 1) - -//////////////////////////////// -//~ rjf: Deserialization Helpers - -internal U64 str8_deserial_read(String8 string, U64 off, void *read_dst, U64 read_size, U64 granularity); -internal U64 str8_deserial_find_first_match(String8 string, U64 off, U16 scan_val); -internal void * str8_deserial_get_raw_ptr(String8 string, U64 off, U64 size); -internal U64 str8_deserial_read_cstr(String8 string, U64 off, String8 *cstr_out); -internal U64 str8_deserial_read_windows_utf16_string16(String8 string, U64 off, String16 *str_out); -internal U64 str8_deserial_read_block(String8 string, U64 off, U64 size, String8 *block_out); -internal U64 str8_deserial_read_uleb128(String8 string, U64 off, U64 *value_out); -internal U64 str8_deserial_read_sleb128(String8 string, U64 off, S64 *value_out); -#define str8_deserial_read_array(string, off, ptr, count) str8_deserial_read((string), (off), (ptr), sizeof(*(ptr))*(count), sizeof(*(ptr))) -#define str8_deserial_read_struct(string, off, ptr) str8_deserial_read_array(string, off, ptr, 1) - -#endif // BASE_STRINGS_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_STRINGS_H +#define BASE_STRINGS_H + +//////////////////////////////// +//~ rjf: Third Party Includes + +#define STB_SPRINTF_DECORATE(name) raddbg_##name +#include "third_party/stb/stb_sprintf.h" + +//////////////////////////////// +//~ rjf: String Types + +typedef struct String8 String8; +struct String8 +{ + U8 *str; + U64 size; +}; + +typedef struct String16 String16; +struct String16 +{ + U16 *str; + U64 size; +}; + +typedef struct String32 String32; +struct String32 +{ + U32 *str; + U64 size; +}; + +//////////////////////////////// +//~ rjf: String List & Array Types + +typedef struct String8Node String8Node; +struct String8Node +{ + String8Node *next; + String8 string; +}; + +typedef struct String8MetaNode String8MetaNode; +struct String8MetaNode +{ + String8MetaNode *next; + String8Node *node; +}; + +typedef struct String8List String8List; +struct String8List +{ + String8Node *first; + String8Node *last; + U64 node_count; + U64 total_size; +}; + +typedef struct String8Array String8Array; +struct String8Array +{ + String8 *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: String Matching, Splitting, & Joining Types + +typedef U32 StringMatchFlags; +enum +{ + StringMatchFlag_CaseInsensitive = (1 << 0), + StringMatchFlag_RightSideSloppy = (1 << 1), + StringMatchFlag_SlashInsensitive = (1 << 2), +}; + +typedef U32 StringSplitFlags; +enum +{ + StringSplitFlag_KeepEmpties = (1 << 0), +}; + +typedef enum PathStyle +{ + PathStyle_Null, + PathStyle_Relative, + PathStyle_WindowsAbsolute, + PathStyle_UnixAbsolute, + +#if OS_WINDOWS + PathStyle_SystemAbsolute = PathStyle_WindowsAbsolute +#elif OS_LINUX + PathStyle_SystemAbsolute = PathStyle_UnixAbsolute +#else +# error "absolute path style is undefined for this OS" +#endif +} +PathStyle; + +typedef struct StringJoin StringJoin; +struct StringJoin +{ + String8 pre; + String8 sep; + String8 post; +}; + +//////////////////////////////// +//~ rjf: String Pair Types + +typedef struct String8TxtPtPair String8TxtPtPair; +struct String8TxtPtPair +{ + String8 string; + TxtPt pt; +}; + +//////////////////////////////// +//~ rjf: UTF Decoding Types + +typedef struct UnicodeDecode UnicodeDecode; +struct UnicodeDecode +{ + U32 inc; + U32 codepoint; +}; + +//////////////////////////////// +//~ rjf: String Fuzzy Matching Types + +typedef struct FuzzyMatchRangeNode FuzzyMatchRangeNode; +struct FuzzyMatchRangeNode +{ + FuzzyMatchRangeNode *next; + Rng1U64 range; +}; + +typedef struct FuzzyMatchRangeList FuzzyMatchRangeList; +struct FuzzyMatchRangeList +{ + FuzzyMatchRangeNode *first; + FuzzyMatchRangeNode *last; + U64 count; + U64 needle_part_count; + U64 total_dim; +}; + +//////////////////////////////// +//~ rjf: Character Classification & Conversion Functions + +internal B32 char_is_space(U8 c); +internal B32 char_is_upper(U8 c); +internal B32 char_is_lower(U8 c); +internal B32 char_is_alpha(U8 c); +internal B32 char_is_slash(U8 c); +internal B32 char_is_digit(U8 c, U32 base); +internal U8 char_to_lower(U8 c); +internal U8 char_to_upper(U8 c); +internal U8 char_to_correct_slash(U8 c); + +//////////////////////////////// +//~ rjf: C-String Measurement + +internal U64 cstring8_length(U8 *c); +internal U64 cstring16_length(U16 *c); +internal U64 cstring32_length(U32 *c); + +//////////////////////////////// +//~ rjf: String Constructors + +#define str8_lit(S) str8((U8*)(S), sizeof(S) - 1) +#define str8_lit_comp(S) {(U8*)(S), sizeof(S) - 1,} +#define str8_varg(S) (int)((S).size), ((S).str) + +#define str8_array(S,C) str8((U8*)(S), sizeof(*(S))*(C)) +#define str8_array_fixed(S) str8((U8*)(S), sizeof(S)) +#define str8_struct(S) str8((U8*)(S), sizeof(*(S))) + +internal String8 str8(U8 *str, U64 size); +internal String8 str8_range(U8 *first, U8 *one_past_last); +internal String8 str8_zero(void); +internal String16 str16(U16 *str, U64 size); +internal String16 str16_range(U16 *first, U16 *one_past_last); +internal String16 str16_zero(void); +internal String32 str32(U32 *str, U64 size); +internal String32 str32_range(U32 *first, U32 *one_past_last); +internal String32 str32_zero(void); +internal String8 str8_cstring(char *c); +internal String16 str16_cstring(U16 *c); +internal String32 str32_cstring(U32 *c); +internal String8 str8_cstring_capped(void *cstr, void *cap); +internal String16 str16_cstring_capped(void *cstr, void *cap); +internal String8 str8_cstring_capped_reverse(void *raw_start, void *raw_cap); + +//////////////////////////////// +//~ rjf: String Stylization + +internal String8 upper_from_str8(Arena *arena, String8 string); +internal String8 lower_from_str8(Arena *arena, String8 string); +internal String8 backslashed_from_str8(Arena *arena, String8 string); + +//////////////////////////////// +//~ rjf: String Matching + +#define str8_match_lit(a_lit, b, flags) str8_match(str8_lit(a_lit), (b), (flags)) +#define str8_match_cstr(a_cstr, b, flags) str8_match(str8_cstring(a_cstr), (b), (flags)) +internal B32 str8_match(String8 a, String8 b, StringMatchFlags flags); +internal U64 str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags); +internal U64 str8_find_needle_reverse(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags); +internal B32 str8_starts_with(String8 string, String8 start, StringMatchFlags flags); +#define str8_starts_with_lit(string, start_lit, flags) str8_starts_with((string), str8_lit(start_lit), (flags)) +internal B32 str8_ends_with(String8 string, String8 end, StringMatchFlags flags); +#define str8_ends_with_lit(string, end_lit, flags) str8_ends_with((string), str8_lit(end_lit), (flags)) + +//////////////////////////////// +//~ rjf: String Slicing + +internal String8 str8_substr(String8 str, Rng1U64 range); +internal String8 str8_prefix(String8 str, U64 size); +internal String8 str8_skip(String8 str, U64 amt); +internal String8 str8_postfix(String8 str, U64 size); +internal String8 str8_chop(String8 str, U64 amt); +internal String8 str8_skip_chop_whitespace(String8 string); + +//////////////////////////////// +//~ rjf: String Formatting & Copying + +internal String8 push_str8_cat(Arena *arena, String8 s1, String8 s2); +internal String8 push_str8_copy(Arena *arena, String8 s); +internal String8 push_str8fv(Arena *arena, char *fmt, va_list args); +internal String8 push_str8f(Arena *arena, char *fmt, ...); + +//////////////////////////////// +//~ rjf: String <=> Integer Conversions + +//- rjf: string -> integer +internal S64 sign_from_str8(String8 string, String8 *string_tail); +internal B32 str8_is_integer(String8 string, U32 radix); + +internal U64 u64_from_str8(String8 string, U32 radix); +internal S64 s64_from_str8(String8 string, U32 radix); +internal U32 u32_from_str8(String8 string, U32 radix); +internal S32 s32_from_str8(String8 string, U32 radix); +internal B32 try_u64_from_str8_c_rules(String8 string, U64 *x); +internal B32 try_s64_from_str8_c_rules(String8 string, S64 *x); + +//- rjf: integer -> string +internal String8 str8_from_memory_size(Arena *arena, U64 size); +internal String8 str8_from_count(Arena *arena, U64 count); +internal String8 str8_from_bits_u32(Arena *arena, U32 x); +internal String8 str8_from_bits_u64(Arena *arena, U64 x); +internal String8 str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator); +internal String8 str8_from_s64(Arena *arena, S64 s64, U32 radix, U8 min_digits, U8 digit_group_separator); + +//////////////////////////////// +//~ rjf: String <=> Float Conversions + +internal F64 f64_from_str8(String8 string); + +//////////////////////////////// +//~ rjf: String List Construction Functions + +internal String8Node* str8_list_push_node(String8List *list, String8Node *node); +internal String8Node* str8_list_push_node_set_string(String8List *list, String8Node *node, String8 string); +internal String8Node* str8_list_push_node_front(String8List *list, String8Node *node); +internal String8Node* str8_list_push_node_front_set_string(String8List *list, String8Node *node, String8 string); +internal String8Node* str8_list_push(Arena *arena, String8List *list, String8 string); +internal String8Node* str8_list_push_front(Arena *arena, String8List *list, String8 string); +internal void str8_list_concat_in_place(String8List *list, String8List *to_push); +internal String8Node* str8_list_push_aligner(Arena *arena, String8List *list, U64 min, U64 align); +internal String8Node* str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...); +internal String8Node* str8_list_push_frontf(Arena *arena, String8List *list, char *fmt, ...); +internal String8List str8_list_copy(Arena *arena, String8List *list); +#define str8_list_first(list) ((list)->first ? (list)->first->string : str8_zero()) + +//////////////////////////////// +//~ rjf: String Splitting & Joining + +internal String8List str8_split(Arena *arena, String8 string, U8 *split_chars, U64 split_char_count, StringSplitFlags flags); +internal String8List str8_split_by_string_chars(Arena *arena, String8 string, String8 split_chars, StringSplitFlags flags); +internal String8List str8_list_split_by_string_chars(Arena *arena, String8List list, String8 split_chars, StringSplitFlags flags); +internal String8 str8_list_join(Arena *arena, String8List *list, StringJoin *optional_params); +internal void str8_list_from_flags(Arena *arena, String8List *list, U32 flags, String8 *flag_string_table, U32 flag_string_count); + +//////////////////////////////// +//~ rjf; String Arrays + +internal String8Array str8_array_from_list(Arena *arena, String8List *list); +internal String8Array str8_array_reserve(Arena *arena, U64 count); + +//////////////////////////////// +//~ rjf: String Path Helpers + +internal String8 str8_chop_last_slash(String8 string); +internal String8 str8_skip_last_slash(String8 string); +internal String8 str8_chop_last_dot(String8 string); +internal String8 str8_skip_last_dot(String8 string); + +internal PathStyle path_style_from_str8(String8 string); +internal String8List str8_split_path(Arena *arena, String8 string); +internal void str8_path_list_resolve_dots_in_place(String8List *path, PathStyle style); +internal String8 str8_path_list_join_by_style(Arena *arena, String8List *path, PathStyle style); + +internal String8TxtPtPair str8_txt_pt_pair_from_string(String8 string); + +//////////////////////////////// +//~ rjf: UTF-8 & UTF-16 Decoding/Encoding + +internal UnicodeDecode utf8_decode(U8 *str, U64 max); +internal UnicodeDecode utf16_decode(U16 *str, U64 max); +internal U32 utf8_encode(U8 *str, U32 codepoint); +internal U32 utf16_encode(U16 *str, U32 codepoint); +internal U32 utf8_from_utf32_single(U8 *buffer, U32 character); + +//////////////////////////////// +//~ rjf: Unicode String Conversions + +internal String8 str8_from_16(Arena *arena, String16 in); +internal String16 str16_from_8(Arena *arena, String8 in); +internal String8 str8_from_32(Arena *arena, String32 in); +internal String32 str32_from_8(Arena *arena, String8 in); + +//////////////////////////////// +//~ String -> Enum Conversions + +internal OperatingSystem operating_system_from_string(String8 string); + +//////////////////////////////// +//~ rjf: Basic Types & Space Enum -> String Conversions + +internal String8 string_from_dimension(Dimension dimension); +internal String8 string_from_side(Side side); +internal String8 string_from_operating_system(OperatingSystem os); +internal String8 string_from_arch(Arch arch); + +//////////////////////////////// +//~ rjf: Time Types -> String + +internal String8 string_from_week_day(WeekDay week_day); +internal String8 string_from_month(Month month); +internal String8 push_date_time_string(Arena *arena, DateTime *date_time); +internal String8 push_file_name_date_time_string(Arena *arena, DateTime *date_time); +internal String8 string_from_elapsed_time(Arena *arena, DateTime dt); + +//////////////////////////////// +//~ Globally Unique Ids + +internal String8 string_from_guid(Arena *arena, Guid guid); +internal B32 try_guid_from_string(String8 string, Guid *guid_out); +internal Guid guid_from_string(String8 string); + +//////////////////////////////// +//~ rjf: Basic Text Indentation + +internal String8 indented_from_string(Arena *arena, String8 string); + +//////////////////////////////// +//~ rjf: Text Escaping + +internal String8 escaped_from_raw_str8(Arena *arena, String8 string); +internal String8 raw_from_escaped_str8(Arena *arena, String8 string); + +//////////////////////////////// +//~ rjf: Text Wrapping + +internal String8List wrapped_lines_from_string(Arena *arena, String8 string, U64 first_line_max_width, U64 max_width, U64 wrap_indent); + +//////////////////////////////// +//~ rjf: String <-> Color + +internal String8 hex_string_from_rgba_4f32(Arena *arena, Vec4F32 rgba); +internal Vec4F32 rgba_from_hex_string_4f32(String8 hex_string); + +//////////////////////////////// +//~ rjf: String Fuzzy Matching + +internal FuzzyMatchRangeList fuzzy_match_find(Arena *arena, String8 needle, String8 haystack); +internal FuzzyMatchRangeList fuzzy_match_range_list_copy(Arena *arena, FuzzyMatchRangeList *src); + +//////////////////////////////// +//~ NOTE(allen): Serialization Helpers + +internal void str8_serial_begin(Arena *arena, String8List *srl); +internal String8 str8_serial_end(Arena *arena, String8List *srl); +internal void str8_serial_write_to_dst(String8List *srl, void *out); +internal U64 str8_serial_push_align(Arena *arena, String8List *srl, U64 align); +internal void * str8_serial_push_size(Arena *arena, String8List *srl, U64 size); +internal void * str8_serial_push_data(Arena *arena, String8List *srl, void *data, U64 size); +internal void str8_serial_push_data_list(Arena *arena, String8List *srl, String8Node *first); +internal void str8_serial_push_u64(Arena *arena, String8List *srl, U64 x); +internal void str8_serial_push_u32(Arena *arena, String8List *srl, U32 x); +internal void str8_serial_push_u16(Arena *arena, String8List *srl, U16 x); +internal void str8_serial_push_u8(Arena *arena, String8List *srl, U8 x); +internal void str8_serial_push_cstr(Arena *arena, String8List *srl, String8 str); +internal void str8_serial_push_string(Arena *arena, String8List *srl, String8 str); +#define str8_serial_push_array(arena, srl, ptr, count) str8_serial_push_data(arena, srl, ptr, sizeof(*(ptr)) * (count)) +#define str8_serial_push_struct(arena, srl, ptr) str8_serial_push_array(arena, srl, ptr, 1) + +//////////////////////////////// +//~ rjf: Deserialization Helpers + +internal U64 str8_deserial_read(String8 string, U64 off, void *read_dst, U64 read_size, U64 granularity); +internal U64 str8_deserial_find_first_match(String8 string, U64 off, U16 scan_val); +internal void * str8_deserial_get_raw_ptr(String8 string, U64 off, U64 size); +internal U64 str8_deserial_read_cstr(String8 string, U64 off, String8 *cstr_out); +internal U64 str8_deserial_read_windows_utf16_string16(String8 string, U64 off, String16 *str_out); +internal U64 str8_deserial_read_block(String8 string, U64 off, U64 size, String8 *block_out); +internal U64 str8_deserial_read_uleb128(String8 string, U64 off, U64 *value_out); +internal U64 str8_deserial_read_sleb128(String8 string, U64 off, S64 *value_out); +#define str8_deserial_read_array(string, off, ptr, count) str8_deserial_read((string), (off), (ptr), sizeof(*(ptr))*(count), sizeof(*(ptr))) +#define str8_deserial_read_struct(string, off, ptr) str8_deserial_read_array(string, off, ptr, 1) + +#endif // BASE_STRINGS_H diff --git a/src/font_cache/font_cache.c b/src/font_cache/font_cache.c index f302dd22d..e975ea5a6 100644 --- a/src/font_cache/font_cache.c +++ b/src/font_cache/font_cache.c @@ -773,7 +773,15 @@ fnt_push_run_from_string(Arena *arena, FNT_Tag tag, F32 size, F32 base_align_px, info->subrect = chosen_atlas_region; info->atlas_num = chosen_atlas_num; info->raster_dim = raster.atlas_dim; - info->advance = raster.advance; + info->advance = 0; + info->first_glyph_bitmap_top = 0; + if (raster.glyph_count > 0) { + info->first_glyph_bitmap_top = raster.metrics[0].bitmap_top; + for(U64 j = 0; j < raster.glyph_count; ++j) + { + info->advance += raster.metrics[j].advance_x; + } + } } } @@ -819,11 +827,12 @@ fnt_push_run_from_string(Arena *arena, FNT_Tag tag, F32 size, F32 base_align_px, info->subrect.y0 + info->raster_dim.y); piece->advance = advance; piece->decode_size = piece_substring.size; - piece->offset = v2s16(0, -(hash2style_node->ascent + hash2style_node->descent)); + piece->offset.x = 0; + piece->offset.y = -(info->first_glyph_bitmap_top); } base_align_px += advance; dim.x += piece->advance; - dim.y = Max(dim.y, info->raster_dim.y); + dim.y = Max(dim.y, (F32)(hash2style_node->ascent + hash2style_node->descent)); } } } diff --git a/src/font_cache/font_cache.h b/src/font_cache/font_cache.h index 9f343eca6..ba538b8a4 100644 --- a/src/font_cache/font_cache.h +++ b/src/font_cache/font_cache.h @@ -97,9 +97,10 @@ typedef struct F_RasterCacheInfo F_RasterCacheInfo; struct F_RasterCacheInfo { Rng2S16 subrect; - Vec2S16 raster_dim; S16 atlas_num; + Vec2S16 raster_dim; F32 advance; + S16 first_glyph_bitmap_top; }; typedef struct F_Hash2InfoRasterCacheNode F_Hash2InfoRasterCacheNode; diff --git a/src/font_provider/font_provider.h b/src/font_provider/font_provider.h index f836aa191..b30a4a033 100644 --- a/src/font_provider/font_provider.h +++ b/src/font_provider/font_provider.h @@ -1,59 +1,79 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef FONT_PROVIDER_H -#define FONT_PROVIDER_H - -#define fp_hook C_LINKAGE - -//////////////////////////////// -//~ rjf: Types - -typedef U32 FP_RasterFlags; -enum -{ - FP_RasterFlag_Smooth = (1<<0), - FP_RasterFlag_Hinted = (1<<1), -}; - -typedef struct FP_Handle FP_Handle; -struct FP_Handle -{ - U64 u64[2]; -}; - -typedef struct FP_Metrics FP_Metrics; -struct FP_Metrics -{ - F32 design_units_per_em; - F32 ascent; - F32 descent; - F32 line_gap; - F32 capital_height; -}; - -typedef struct FP_RasterResult FP_RasterResult; -struct FP_RasterResult -{ - Vec2S16 atlas_dim; - void *atlas; - F32 advance; -}; - -//////////////////////////////// -//~ rjf: Basic Type Functions - -internal FP_Handle fp_handle_zero(void); -internal B32 fp_handle_match(FP_Handle a, FP_Handle b); - -//////////////////////////////// -//~ rjf: Backend Hooks - -fp_hook void fp_init(void); -fp_hook FP_Handle fp_font_open(String8 path); -fp_hook FP_Handle fp_font_open_from_static_data_string(String8 *data_ptr); -fp_hook void fp_font_close(FP_Handle handle); -fp_hook FP_Metrics fp_metrics_from_font(FP_Handle font); -fp_hook NO_ASAN FP_RasterResult fp_raster(Arena *arena, FP_Handle font, F32 size, FP_RasterFlags flags, String8 string); - -#endif // FONT_PROVIDER_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef FONT_PROVIDER_H +#define FONT_PROVIDER_H + +#define fp_hook C_LINKAGE + +//////////////////////////////// +//~ rjf: Types + +typedef U32 FP_RasterFlags; +enum +{ + FP_RasterFlag_Smooth = (1<<0), + FP_RasterFlag_Hinted = (1<<1), +}; + +typedef struct FP_Handle FP_Handle; +struct FP_Handle +{ + U64 u64[2]; +}; + +typedef struct FP_Metrics FP_Metrics; +struct FP_Metrics +{ + F32 design_units_per_em; + F32 ascent; + F32 descent; + F32 line_gap; + F32 capital_height; +}; + +typedef struct FP_GlyphMetrics FP_GlyphMetrics; +struct FP_GlyphMetrics +{ + // Atlas Position & Size (Source Rect) + Rng2S16 src_rect_px; // Position (x0,y0) and dimensions (x1-x0, y1-y0) within the atlas texture + + // Layout Metrics (relative to baseline & pen position) + S16 bitmap_left; // Horizontal offset from pen_x to bitmap's left edge + S16 bitmap_top; // Vertical offset from baseline up to bitmap's top edge + F32 advance_x; // Horizontal advance width for this glyph (in pixels) + + // Original Character Info (optional, but useful for debugging) + U32 codepoint; + U64 string_index; +}; + +typedef struct FP_RasterResult FP_RasterResult; +struct FP_RasterResult +{ + // Atlas Data + Vec2S16 atlas_dim; // Dimensions of the atlas texture + void *atlas; // Pixel data (RGBA8), aligned + + // Per-Glyph Layout Information + U64 glyph_count; // Number of glyphs in the metrics array + FP_GlyphMetrics *metrics; // Array of metrics for each glyph +}; + +//////////////////////////////// +//~ rjf: Basic Type Functions + +internal FP_Handle fp_handle_zero(void); +internal B32 fp_handle_match(FP_Handle a, FP_Handle b); + +//////////////////////////////// +//~ rjf: Backend Hooks + +fp_hook void fp_init(void); +fp_hook FP_Handle fp_font_open(String8 path); +fp_hook FP_Handle fp_font_open_from_static_data_string(String8 *data_ptr); +fp_hook void fp_font_close(FP_Handle handle); +fp_hook FP_Metrics fp_metrics_from_font(FP_Handle font); +fp_hook NO_ASAN FP_RasterResult fp_raster(Arena *arena, FP_Handle font, F32 size, FP_RasterFlags flags, String8 string); + +#endif // FONT_PROVIDER_H diff --git a/src/font_provider/font_provider_inc.c b/src/font_provider/font_provider_inc.c index d5c7665bd..c589b27ae 100644 --- a/src/font_provider/font_provider_inc.c +++ b/src/font_provider/font_provider_inc.c @@ -5,6 +5,8 @@ #if FP_BACKEND == FP_BACKEND_DWRITE # include "dwrite/font_provider_dwrite.c" +#elif FP_BACKEND == FP_BACKEND_FREETYPE +# include "freetype/font_provider_freetype.c" #else # error Font provider backend not specified. #endif diff --git a/src/font_provider/freetype/font_provider_freetype.c b/src/font_provider/freetype/font_provider_freetype.c index 8ac6bce5f..28e29c8b1 100644 --- a/src/font_provider/freetype/font_provider_freetype.c +++ b/src/font_provider/freetype/font_provider_freetype.c @@ -1,2 +1,551 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ dan: Backend Implementations + +fp_hook void +fp_init(void) +{ + ProfBeginFunction(); + + //- dan: initialize main state + { + Arena *arena = arena_alloc(); + fp_freetype_state = push_array(arena, FP_Freetype_State, 1); + fp_freetype_state->arena = arena; + } + + //- dan: initialize freetype library + FT_Error error = FT_Init_FreeType(&fp_freetype_state->library); + if(error) + { + log_infof("fp_init: FT_Init_FreeType failed with error %d", error); + } + // Enable LCD filtering for potentially better subpixel rendering + FT_Library_SetLcdFilter(fp_freetype_state->library, FT_LCD_FILTER_DEFAULT); + + ProfEnd(); +} + +fp_hook FP_Handle +fp_font_open(String8 path) +{ + ProfBeginFunction(); + FP_Handle handle = {0}; + FT_Face face; + Temp scratch = scratch_begin(0, 0); + + // Freetype needs a null-terminated path + String8 path_copy = push_str8_copy(scratch.arena, path); + + FT_Error error = FT_New_Face(fp_freetype_state->library, + (const char *)path_copy.str, + 0, // Use first face index + &face); + + if(!error) + { + handle.u64[0] = (U64)face; + } + else + { + log_infof("fp_font_open: FT_New_Face failed for path '%S' with error %d", path_copy, error); + } + + scratch_end(scratch); + ProfEnd(); + return handle; +} + +fp_hook FP_Handle +fp_font_open_from_static_data_string(String8 *data_ptr) +{ + ProfBeginFunction(); + FP_Handle handle = {0}; + FT_Face face = 0; // Initialize face to 0 + + if(data_ptr != 0 && data_ptr->str != 0 && data_ptr->size > 0) + { + // FT_New_Memory_Face requires a pointer to the data and its size. + FT_Error error = FT_New_Memory_Face(fp_freetype_state->library, + (const FT_Byte *)data_ptr->str, + (FT_Long)data_ptr->size, + 0, // Use first face index + &face); + + if(!error) + { + handle.u64[0] = (U64)face; + } + else + { + log_infof("fp_font_open_from_static_data_string: FT_New_Memory_Face failed with error %d", error); + face = 0; // Ensure face is 0 on error + } + } + // If data_ptr is null or invalid, handle remains zero. + + ProfEnd(); + return handle; +} + +fp_hook void +fp_font_close(FP_Handle handle) +{ + ProfBeginFunction(); + FT_Face face = (FT_Face)handle.u64[0]; + if(face != 0) + { + FT_Error error = FT_Done_Face(face); + if(error) + { + log_infof("fp_font_close: FT_Done_Face failed with error %d", error); + } + } + ProfEnd(); +} + +fp_hook FP_Metrics +fp_metrics_from_font(FP_Handle handle) +{ + ProfBeginFunction(); + FP_Metrics result = {0}; + FT_Face face = (FT_Face)handle.u64[0]; + + if(face != 0) + { + result.design_units_per_em = (F32)face->units_per_EM; + result.ascent = (F32)face->ascender; + // FreeType descender is typically negative, FP_Metrics likely expects positive. + result.descent = (F32)face->descender; + if (result.descent < 0) + { + result.descent = -result.descent; + } + // face->height is the distance between baselines. line_gap = height - (ascender - descender) + result.line_gap = (F32)(face->height - (face->ascender - face->descender)); + + // Capital height retrieval from OS/2 table + result.capital_height = 0; // Default to 0 + FT_Byte* os2_table = (FT_Byte*)FT_Get_Sfnt_Table(face, ft_sfnt_os2); + if (os2_table) + { + // OS/2 table version determines where sCapHeight is located. + // Read version (ushort at offset 0) + // We need fields up to sCapHeight (offset 88 in version >= 2) + // Check table size first. Minimum size for version 0/1 is 78 bytes. + // For version >= 2, minimum size is 96 bytes. + // FT_ULong length = 0; // FT_Get_Sfnt_Table does not provide length directly + // Need another way to check table size or rely on FreeType's internal handling. + // Let's assume FreeType provides a valid pointer if the table exists. + + FT_UShort version = (FT_UShort)((os2_table[0] << 8) | os2_table[1]); + + // sCapHeight is at offset 88 (bytes) in OS/2 version 2 and later. + // It's optional and might be 0 even if present. + // Check if version >= 2 (assuming table is large enough) + // A more robust check would involve FT_Load_Sfnt_Table if length was needed. + if (version >= 2) + { + // Read sCapHeight (short at offset 88) + FT_Short cap_height_units = (FT_Short)((os2_table[88] << 8) | os2_table[89]); + if (cap_height_units > 0) // Use only if positive + { + result.capital_height = (F32)cap_height_units; + } + } + + // If cap height is still 0 (not found or invalid), use ascender as fallback + if (result.capital_height == 0) + { + result.capital_height = (F32)face->ascender; + } + } + else + { + // Fallback if OS/2 table doesn't exist: Use ascender + result.capital_height = (F32)face->ascender; + } + } + + ProfEnd(); + return result; +} + +fp_hook NO_ASAN FP_RasterResult +fp_raster(Arena *arena, FP_Handle font_handle, F32 size, FP_RasterFlags flags, String8 string) +{ + ProfBeginFunction(); + FP_RasterResult result = {0}; + FT_Face face = (FT_Face)font_handle.u64[0]; + if (face == 0) + { + ProfEnd(); + return result; + } + + Temp scratch = scratch_begin(&arena, 1); + String32 string32 = str32_from_8(scratch.arena, string); + if (string32.size == 0) + { + scratch_end(scratch); + ProfEnd(); + return result; + } + + //- dan: Define glyph render info storage (layout pass temporary data) + typedef struct GlyphLayoutInfo GlyphLayoutInfo; + struct GlyphLayoutInfo { + FT_UInt glyph_index; + S32 render_pen_x; // Pen position before this glyph (26.6 fixed point) + // Store metrics needed for the second pass and the final result + S16 bitmap_left; + S16 bitmap_top; + U16 bitmap_width; + U16 bitmap_rows; + S32 advance_x; // Advance width for this glyph (26.6 fixed point) + }; + + //- dan: Set pixel size + FT_UInt pixel_height = (FT_UInt)(size + 0.5f); + FT_Error error = FT_Set_Pixel_Sizes(face, 0, pixel_height); + if (error) + { + log_infof("fp_raster: FT_Set_Pixel_Sizes failed for size %.2f with error %d", size, error); + scratch_end(scratch); + ProfEnd(); + return result; + } + + //- dan: Determine FreeType load flags based on FP_RasterFlags + FT_Int32 load_flags = FT_LOAD_DEFAULT | FT_LOAD_COLOR; + FT_Render_Mode render_mode = FT_RENDER_MODE_NORMAL; + // Determine the appropriate kerning mode based on hinting + FT_Kerning_Mode kerning_mode = FT_KERNING_DEFAULT; // Default (scaled, hinted) + + switch (flags) + { + case 0: // Sharp, Unhinted + load_flags |= FT_LOAD_NO_HINTING | FT_LOAD_TARGET_NORMAL; + render_mode = FT_RENDER_MODE_NORMAL; + kerning_mode = FT_KERNING_UNFITTED; // Unhinted kerning + break; + + case FP_RasterFlag_Hinted: // Sharp, Hinted + load_flags |= FT_LOAD_TARGET_NORMAL; // Hinting enabled + render_mode = FT_RENDER_MODE_NORMAL; + kerning_mode = FT_KERNING_DEFAULT; // Hinted kerning + break; + + case FP_RasterFlag_Smooth: // Smooth, Unhinted + load_flags |= FT_LOAD_NO_HINTING | FT_LOAD_TARGET_LCD; + render_mode = FT_RENDER_MODE_LCD; + kerning_mode = FT_KERNING_UNFITTED; // Unhinted kerning + // NOTE: Current implementation converts LCD to grayscale alpha. + break; + + case FP_RasterFlag_Smooth | FP_RasterFlag_Hinted: // Smooth, Hinted + load_flags |= FT_LOAD_TARGET_LCD; // Hinting enabled + render_mode = FT_RENDER_MODE_LCD; + kerning_mode = FT_KERNING_DEFAULT; // Hinted kerning + // NOTE: Current implementation converts LCD to grayscale alpha. + break; + + default: // Fallback to default (Sharp, Hinted) + load_flags |= FT_LOAD_TARGET_NORMAL; + render_mode = FT_RENDER_MODE_NORMAL; + kerning_mode = FT_KERNING_DEFAULT; + break; + } + + //- dan: First pass: Calculate layout metrics and store glyph positions + GlyphLayoutInfo *layout_infos = push_array(scratch.arena, GlyphLayoutInfo, string32.size); + MemoryZero(layout_infos, sizeof(GlyphLayoutInfo) * string32.size); + + S32 total_width_fixed = 0; // Use 26.6 for total width calculation + S32 max_ascent = 0; + S32 max_descent = 0; + S32 pen_x = 0; // Use 26.6 fixed-point + FT_UInt prev_glyph_index = 0; + + for (U64 i = 0; i < string32.size; ++i) + { + FT_UInt glyph_index = FT_Get_Char_Index(face, string32.str[i]); + layout_infos[i].glyph_index = glyph_index; + + // Load glyph metrics *using the final load flags* + // FT_LOAD_NO_BITMAP could optimize this, but FT_LOAD_DEFAULT is okay. + error = FT_Load_Glyph(face, glyph_index, load_flags); // <<< USE CONSISTENT load_flags + if (error) { + log_infof("fp_raster: FT_Load_Glyph (metrics pass) failed for glyph index %u (char 0x%x) with error %d", glyph_index, string32.str[i], error); + layout_infos[i].render_pen_x = pen_x; + layout_infos[i].advance_x = 0; + // Store zero metrics for failed glyphs + layout_infos[i].bitmap_left = 0; + layout_infos[i].bitmap_top = 0; + layout_infos[i].bitmap_width = 0; + layout_infos[i].bitmap_rows = 0; + prev_glyph_index = glyph_index; + continue; + } + + FT_GlyphSlot slot = face->glyph; + + // Apply kerning *using the consistent kerning mode* + if (prev_glyph_index && glyph_index && FT_HAS_KERNING(face)) + { + FT_Vector delta; + FT_Get_Kerning(face, prev_glyph_index, glyph_index, kerning_mode, &delta); // <<< USE CONSISTENT kerning_mode + pen_x += delta.x; + } + + layout_infos[i].render_pen_x = pen_x; // Store pen position before advance + + // Use metrics (ascent/descent) from the loaded glyph (now potentially hinted) + // NOTE: DO NOT render here. Use metrics directly from the loaded glyph slot. + // FT_Render_Glyph might change metrics slightly due to hinting. + // if (slot->format != FT_GLYPH_FORMAT_BITMAP) + // { + // // Render temporarily to get bitmap metrics, won't affect final render pass + // FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); + // } + + // Use metrics directly from the slot after FT_Load_Glyph + S32 glyph_ascent = slot->metrics.horiBearingY >> 6; // Use FUnit metrics initially? + S32 glyph_descent = (slot->metrics.height >> 6) - glyph_ascent; + // Use bitmap metrics if available and potentially more accurate after load_flags applied? + // Let's stick to bitmap_top/rows for consistency with blitting, but load them *before* render. + S32 reported_bitmap_top = slot->bitmap_top; + S32 reported_bitmap_rows = (S32)slot->bitmap.rows; + + // Recalculate ascent/descent based on bitmap positioning relative to baseline + glyph_ascent = reported_bitmap_top; + glyph_descent = reported_bitmap_rows - reported_bitmap_top; + + if (glyph_ascent > max_ascent) { max_ascent = glyph_ascent; } + if (glyph_descent > max_descent) { max_descent = glyph_descent; } + + // Store metrics needed for second pass and result (using values from loaded slot) + layout_infos[i].bitmap_left = (S16)slot->bitmap_left; + layout_infos[i].bitmap_top = (S16)reported_bitmap_top; // Store the value used for max_ascent + layout_infos[i].bitmap_width = (U16)slot->bitmap.width; + layout_infos[i].bitmap_rows = (U16)reported_bitmap_rows; // Store the value used for glyph_descent calc + + // Use advance from the potentially hinted glyph + pen_x += slot->advance.x; + layout_infos[i].advance_x = slot->advance.x; // Store potentially hinted advance + + prev_glyph_index = glyph_index; + } + + // Also log the final calculated atlas height parameters + log_infof("fp_raster Metric Log: Final max_ascent=%d, max_descent=%d, total_height=%d", max_ascent, max_descent, max_ascent + max_descent); + + // Calculate total width and height based on layout (using potentially hinted metrics) + total_width_fixed = pen_x; + S32 total_width_pixels = (total_width_fixed + 32) >> 6; // Round up width slightly maybe? or +63? + if (total_width_pixels < 0) total_width_pixels = 0; + + // Calculate total height based on maximum ascent/descent values recorded during layout. + S32 total_height = max_ascent + max_descent; + if (total_height < 0) total_height = 0; + + // Handle cases like empty strings where calculated dimensions might be zero. + if (total_width_pixels <= 0 || total_height <= 0) + { + // Return zero dimensions but still allocate metrics array for consistency + result.atlas_dim = v2s16(0, 0); + result.atlas = 0; + result.glyph_count = string32.size; + result.metrics = push_array(arena, FP_GlyphMetrics, string32.size); // Allocate zeroed metrics + // Populate metrics with zero values or minimal info + for (U64 i = 0; i < string32.size; ++i) { + result.metrics[i].src_rect_px = r2s16p(0, 0, 0, 0); + result.metrics[i].bitmap_left = 0; + result.metrics[i].bitmap_top = 0; + result.metrics[i].advance_x = (F32)(layout_infos[i].advance_x >> 6); // Still provide advance + result.metrics[i].codepoint = string32.str[i]; + result.metrics[i].string_index = i; + } + scratch_end(scratch); + ProfEnd(); + return result; + } + + // Allocate the atlas buffer using the precise, unpadded dimensions. + result.atlas_dim = v2s16((S16)total_width_pixels, (S16)total_height); + U64 atlas_bytes = (U64)total_width_pixels * (U64)total_height * 4; + result.atlas = push_array_aligned(arena, U8, atlas_bytes, 16); // Align atlas memory + + // Allocate the result metrics array + result.glyph_count = string32.size; + result.metrics = push_array(arena, FP_GlyphMetrics, string32.size); + MemoryZero(result.metrics, sizeof(FP_GlyphMetrics) * result.glyph_count); // Zero initialize + + // Calculate row pitch for blitting operations. + U8 *out_base = (U8 *)result.atlas; + U64 out_pitch = (U64)total_width_pixels * 4; // Bytes per row. + + //- dan: Second pass: Render and blit glyphs using stored positions, populate metrics + U64 non_empty_pixel_count = 0; + + for (U64 i = 0; i < string32.size; ++i) + { + FT_UInt glyph_index = layout_infos[i].glyph_index; + S32 render_pen_x_fixed = layout_infos[i].render_pen_x; // Stored 26.6 pen position + + // Populate FP_GlyphMetrics for this glyph using layout_infos data + FP_GlyphMetrics *out_metric = &result.metrics[i]; + out_metric->bitmap_left = layout_infos[i].bitmap_left; + out_metric->bitmap_top = layout_infos[i].bitmap_top; + out_metric->advance_x = (F32)(layout_infos[i].advance_x / 64.0f); // Convert 26.6 advance to float pixels + out_metric->codepoint = string32.str[i]; + out_metric->string_index = i; + + // Load glyph again (might be necessary if FT_Load_Glyph doesn't cache everything) + // or ensure first pass loaded everything needed. Reloading is safer. + error = FT_Load_Glyph(face, glyph_index, load_flags); // <<< Use final load_flags + if (error) { + out_metric->src_rect_px = r2s16p(0,0,0,0); // Zero rect for errors + continue; + } + + // Render glyph if not already a bitmap + if (face->glyph->format != FT_GLYPH_FORMAT_BITMAP) + { + error = FT_Render_Glyph(face->glyph, render_mode); // <<< Use final render_mode + if (error) { + out_metric->src_rect_px = r2s16p(0,0,0,0); // Zero rect for errors + continue; + } + } + + FT_GlyphSlot slot = face->glyph; + FT_Bitmap *bitmap = &slot->bitmap; + + // REMOVED Assertions: Dimensions might differ slightly after rendering due to hinting. + // Assert((U16)bitmap->width == layout_infos[i].bitmap_width); + // Assert((U16)bitmap->rows == layout_infos[i].bitmap_rows); + // Assert((S16)slot->bitmap_left == layout_infos[i].bitmap_left); + // Assert((S16)slot->bitmap_top == layout_infos[i].bitmap_top); + + + // Calculate blit position using metrics stored from the FIRST pass (layout_infos) + S32 blit_x = (render_pen_x_fixed >> 6) + layout_infos[i].bitmap_left; + S32 blit_y = max_ascent - layout_infos[i].bitmap_top; + + // Set the source rect in the output metrics using the blit position + // and the ACTUAL rendered bitmap dimensions from the SECOND pass. + out_metric->src_rect_px.x0 = (S16)blit_x; + out_metric->src_rect_px.y0 = (S16)blit_y; + out_metric->src_rect_px.x1 = (S16)(blit_x + bitmap->width); // Use rendered width + out_metric->src_rect_px.y1 = (S16)(blit_y + bitmap->rows); // Use rendered height + + + // Blit the bitmap to the RGBA atlas + U8 *in_row = bitmap->buffer; + for (unsigned int y = 0; y < bitmap->rows; ++y) + { + S32 atlas_y = blit_y + (S32)y; + if (atlas_y >= 0 && atlas_y < total_height) // Clip vertically + { + U8 *out_row = out_base + atlas_y * out_pitch; + // Handle different pixel modes + if (bitmap->pixel_mode == FT_PIXEL_MODE_GRAY) + { + U8 *in_pixel_ptr = in_row; + for (unsigned int x = 0; x < bitmap->width; ++x) + { + S32 atlas_x = blit_x + (S32)x; + if (atlas_x >= 0 && atlas_x < total_width_pixels) // Clip horizontally + { + U8 *out_pixel = out_row + atlas_x * 4; + U8 in_val = *in_pixel_ptr; + out_pixel[0] = 255; + out_pixel[1] = 255; + out_pixel[2] = 255; + out_pixel[3] = in_val; + if (in_val > 0) { + non_empty_pixel_count++; + } + } + in_pixel_ptr++; + } + } + else if (bitmap->pixel_mode == FT_PIXEL_MODE_LCD) + { + U8 *in_pixel_rgb = in_row; + for (unsigned int x = 0; x < bitmap->width / 3; ++x) // Iterate over RGB triples + { + S32 atlas_x = blit_x + (S32)x; + if (atlas_x >= 0 && atlas_x < total_width_pixels) // Clip horizontally + { + U8 *out_pixel = out_row + atlas_x * 4; + // Convert RGB coverage to single alpha (suboptimal for true subpixel) + U8 r = in_pixel_rgb[0]; + U8 g = in_pixel_rgb[1]; + U8 b = in_pixel_rgb[2]; + // Standard weights for luminance conversion + U32 alpha_u32 = (U32)(0.2126f * r + 0.7152f * g + 0.0722f * b); + // U32 alpha_u32 = (54*r + 183*g + 19*b) >> 8; // Alternative integer weights + U8 alpha = (U8)(alpha_u32 > 255 ? 255 : alpha_u32); + out_pixel[0] = 255; + out_pixel[1] = 255; + out_pixel[2] = 255; + out_pixel[3] = alpha; + if (alpha > 0) { + non_empty_pixel_count++; + } + } + in_pixel_rgb += 3; + } + } + else if (bitmap->pixel_mode == FT_PIXEL_MODE_BGRA) // BGRA Color Bitmap + { + U8 *in_pixel_bgra = in_row; + for (unsigned int x = 0; x < bitmap->width; ++x) + { + S32 atlas_x = blit_x + (S32)x; + if (atlas_x >= 0 && atlas_x < total_width_pixels) // Clip horizontally + { + U8 *out_pixel = out_row + atlas_x * 4; + U8 b = in_pixel_bgra[0]; + U8 g = in_pixel_bgra[1]; + U8 r = in_pixel_bgra[2]; + U8 a = in_pixel_bgra[3]; + out_pixel[0] = r; + out_pixel[1] = g; + out_pixel[2] = b; + out_pixel[3] = a; + if (a > 0) { + non_empty_pixel_count++; + } + } + in_pixel_bgra += 4; + } + } + else if (bitmap->pixel_mode == FT_PIXEL_MODE_MONO) + { + // TODO: Handle mono bitmaps if necessary + } + } + in_row += bitmap->pitch; + } + } + + //- dan: Finalize result (Removed advance field) + // result.advance = (F32)(total_width_fixed >> 6); // Use final fixed-point width from first pass + + // If nothing visible rendered, ensure atlas dimensions reflect that. + if (non_empty_pixel_count == 0) + { + result.atlas_dim = v2s16(0, 0); + // Atlas buffer is already allocated but will be effectively empty. + // Caller should ideally check atlas_dim. + } + + scratch_end(scratch); + ProfEnd(); + return result; +} diff --git a/src/font_provider/freetype/font_provider_freetype.h b/src/font_provider/freetype/font_provider_freetype.h index 2efe2a500..ae7a2dee4 100644 --- a/src/font_provider/freetype/font_provider_freetype.h +++ b/src/font_provider/freetype/font_provider_freetype.h @@ -4,4 +4,33 @@ #ifndef FONT_PROVIDER_FREETYPE_H #define FONT_PROVIDER_FREETYPE_H +//////////////////////////////// +//~ rjf: Includes + +#pragma push_macro("internal") +#undef internal +#include +#include FT_FREETYPE_H +#include FT_LCD_FILTER_H // For FT_Library_SetLcdFilter, FT_LCD_FILTER_DEFAULT +#include FT_SFNT_NAMES_H // For FT_Get_Sfnt_Table, FT_SFNT_OS2 +#include FT_TRUETYPE_TABLES_H // For TT_OS2, FT_SFNT_OS2 +#pragma pop_macro("internal") + +#define internal static + +//////////////////////////////// +//~ rjf: Types + +typedef struct FP_Freetype_State FP_Freetype_State; +struct FP_Freetype_State +{ + Arena *arena; + FT_Library library; +}; + +//////////////////////////////// +//~ rjf: Globals + +global FP_Freetype_State *fp_freetype_state = 0; + #endif // FONT_PROVIDER_FREETYPE_H diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 3b2c73b1c..654dbe5df 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1,1330 +1,2398 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Helpers - -internal DateTime -os_lnx_date_time_from_tm(tm in, U32 msec) -{ - DateTime dt = {0}; - dt.sec = in.tm_sec; - dt.min = in.tm_min; - dt.hour = in.tm_hour; - dt.day = in.tm_mday-1; - dt.mon = in.tm_mon; - dt.year = in.tm_year+1900; - dt.msec = msec; - return dt; -} - -internal tm -os_lnx_tm_from_date_time(DateTime dt) -{ - tm result = {0}; - result.tm_sec = dt.sec; - result.tm_min = dt.min; - result.tm_hour= dt.hour; - result.tm_mday= dt.day+1; - result.tm_mon = dt.mon; - result.tm_year= dt.year-1900; - return result; -} - -internal timespec -os_lnx_timespec_from_date_time(DateTime dt) -{ - tm tm_val = os_lnx_tm_from_date_time(dt); - time_t seconds = timegm(&tm_val); - timespec result = {0}; - result.tv_sec = seconds; - return result; -} - -internal DenseTime -os_lnx_dense_time_from_timespec(timespec in) -{ - DenseTime result = 0; - { - struct tm tm_time = {0}; - gmtime_r(&in.tv_sec, &tm_time); - DateTime date_time = os_lnx_date_time_from_tm(tm_time, in.tv_nsec/Million(1)); - result = dense_time_from_date_time(date_time); - } - return result; -} - -internal FileProperties -os_lnx_file_properties_from_stat(struct stat *s) -{ - FileProperties props = {0}; - props.size = s->st_size; - props.created = os_lnx_dense_time_from_timespec(s->st_ctim); - props.modified = os_lnx_dense_time_from_timespec(s->st_mtim); - if(s->st_mode & S_IFDIR) - { - props.flags |= FilePropertyFlag_IsFolder; - } - return props; -} - -internal void -os_lnx_safe_call_sig_handler(int x) -{ - OS_LNX_SafeCallChain *chain = os_lnx_safe_call_chain; - if(chain != 0 && chain->fail_handler != 0) - { - chain->fail_handler(chain->ptr); - } - abort(); -} - -//////////////////////////////// -//~ rjf: Entities - -internal OS_LNX_Entity * -os_lnx_entity_alloc(OS_LNX_EntityKind kind) -{ - OS_LNX_Entity *entity = 0; - DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), - pthread_mutex_unlock(&os_lnx_state.entity_mutex)) - { - entity = os_lnx_state.entity_free; - if(entity) - { - SLLStackPop(os_lnx_state.entity_free); - } - else - { - entity = push_array_no_zero(os_lnx_state.entity_arena, OS_LNX_Entity, 1); - } - } - MemoryZeroStruct(entity); - entity->kind = kind; - return entity; -} - -internal void -os_lnx_entity_release(OS_LNX_Entity *entity) -{ - DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), - pthread_mutex_unlock(&os_lnx_state.entity_mutex)) - { - SLLStackPush(os_lnx_state.entity_free, entity); - } -} - -//////////////////////////////// -//~ rjf: Thread Entry Point - -internal void * -os_lnx_thread_entry_point(void *ptr) -{ - OS_LNX_Entity *entity = (OS_LNX_Entity *)ptr; - OS_ThreadFunctionType *func = entity->thread.func; - void *thread_ptr = entity->thread.ptr; - TCTX tctx_; - tctx_init_and_equip(&tctx_); - func(thread_ptr); - tctx_release(); - return 0; -} - -//////////////////////////////// -//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) - -internal OS_SystemInfo * -os_get_system_info(void) -{ - return &os_lnx_state.system_info; -} - -internal OS_ProcessInfo * -os_get_process_info(void) -{ - return &os_lnx_state.process_info; -} - -internal String8 -os_get_current_path(Arena *arena) -{ - char *cwdir = getcwd(0, 0); - String8 string = push_str8_copy(arena, str8_cstring(cwdir)); - free(cwdir); - return string; -} - -internal U32 -os_get_process_start_time_unix(void) -{ - Temp scratch = scratch_begin(0,0); - U64 start_time = 0; - pid_t pid = getpid(); - String8 path = push_str8f(scratch.arena, "/proc/%u", pid); - struct stat st; - int err = stat((char*)path.str, &st); - if(err == 0) - { - start_time = st.st_mtime; - } - scratch_end(scratch); - return (U32)start_time; -} - -//////////////////////////////// -//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) - -//- rjf: basic - -internal void * -os_reserve(U64 size) -{ - void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - if(result == MAP_FAILED) - { - result = 0; - } - return result; -} - -internal B32 -os_commit(void *ptr, U64 size) -{ - mprotect(ptr, size, PROT_READ|PROT_WRITE); - return 1; -} - -internal void -os_decommit(void *ptr, U64 size) -{ - madvise(ptr, size, MADV_DONTNEED); - mprotect(ptr, size, PROT_NONE); -} - -internal void -os_release(void *ptr, U64 size) -{ - munmap(ptr, size); -} - -//- rjf: large pages - -internal void * -os_reserve_large(U64 size) -{ - void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0); - if(result == MAP_FAILED) - { - result = 0; - } - return result; -} - -internal B32 -os_commit_large(void *ptr, U64 size) -{ - mprotect(ptr, size, PROT_READ|PROT_WRITE); - return 1; -} - -//////////////////////////////// -//~ rjf: @os_hooks Thread Info (Implemented Per-OS) - -internal U32 -os_tid(void) -{ - U32 result = gettid(); - return result; -} - -internal void -os_set_thread_name(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String8 name_copy = push_str8_copy(scratch.arena, name); - pthread_t current_thread = pthread_self(); - pthread_setname_np(current_thread, (char *)name_copy.str); - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: @os_hooks Aborting (Implemented Per-OS) - -internal void -os_abort(S32 exit_code) -{ - exit(exit_code); -} - -//////////////////////////////// -//~ rjf: @os_hooks File System (Implemented Per-OS) - -//- rjf: files - -internal OS_Handle -os_file_open(OS_AccessFlags flags, String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String8 path_copy = push_str8_copy(scratch.arena, path); - int lnx_flags = 0; - if(flags & OS_AccessFlag_Read && flags & OS_AccessFlag_Write) - { - lnx_flags = O_RDWR; - } - else if(flags & OS_AccessFlag_Write) - { - lnx_flags = O_WRONLY; - } - else if(flags & OS_AccessFlag_Read) - { - lnx_flags = O_RDONLY; - } - if(flags & OS_AccessFlag_Append) - { - lnx_flags |= O_APPEND; - } - if(flags & (OS_AccessFlag_Write|OS_AccessFlag_Append)) - { - lnx_flags |= O_CREAT; - } - int fd = open((char *)path_copy.str, lnx_flags, 0755); - OS_Handle handle = {0}; - if(fd != -1) - { - handle.u64[0] = fd; - } - scratch_end(scratch); - return handle; -} - -internal void -os_file_close(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { return; } - int fd = (int)file.u64[0]; - close(fd); -} - -internal U64 -os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - int fd = (int)file.u64[0]; - U64 total_num_bytes_to_read = dim_1u64(rng); - U64 total_num_bytes_read = 0; - U64 total_num_bytes_left_to_read = total_num_bytes_to_read; - for(;total_num_bytes_left_to_read > 0;) - { - int read_result = pread(fd, (U8 *)out_data + total_num_bytes_read, total_num_bytes_left_to_read, rng.min + total_num_bytes_read); - if(read_result >= 0) - { - total_num_bytes_read += read_result; - total_num_bytes_left_to_read -= read_result; - } - else if(errno != EINTR) - { - break; - } - } - return total_num_bytes_read; -} - -internal U64 -os_file_write(OS_Handle file, Rng1U64 rng, void *data) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - int fd = (int)file.u64[0]; - U64 total_num_bytes_to_write = dim_1u64(rng); - U64 total_num_bytes_written = 0; - U64 total_num_bytes_left_to_write = total_num_bytes_to_write; - for(;total_num_bytes_left_to_write > 0;) - { - int write_result = pwrite(fd, (U8 *)data + total_num_bytes_written, total_num_bytes_left_to_write, rng.min + total_num_bytes_written); - if(write_result >= 0) - { - total_num_bytes_written += write_result; - total_num_bytes_left_to_write -= write_result; - } - else if(errno != EINTR) - { - break; - } - } - return total_num_bytes_written; -} - -internal B32 -os_file_set_times(OS_Handle file, DateTime date_time) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - int fd = (int)file.u64[0]; - timespec time = os_lnx_timespec_from_date_time(date_time); - timespec times[2] = {time, time}; - int futimens_result = futimens(fd, times); - B32 good = (futimens_result != -1); - return good; -} - -internal FileProperties -os_properties_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { return (FileProperties){0}; } - int fd = (int)file.u64[0]; - struct stat fd_stat = {0}; - int fstat_result = fstat(fd, &fd_stat); - FileProperties props = {0}; - if(fstat_result != -1) - { - props = os_lnx_file_properties_from_stat(&fd_stat); - } - return props; -} - -internal OS_FileID -os_id_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { return (OS_FileID){0}; } - int fd = (int)file.u64[0]; - struct stat fd_stat = {0}; - int fstat_result = fstat(fd, &fd_stat); - OS_FileID id = {0}; - if(fstat_result != -1) - { - id.v[0] = fd_stat.st_dev; - id.v[1] = fd_stat.st_ino; - } - return id; -} - -internal B32 -os_delete_file_at_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 result = 0; - String8 path_copy = push_str8_copy(scratch.arena, path); - if(remove((char*)path_copy.str) != -1) - { - result = 1; - } - scratch_end(scratch); - return result; -} - -internal B32 -os_copy_file_path(String8 dst, String8 src) -{ - B32 result = 0; - OS_Handle src_h = os_file_open(OS_AccessFlag_Read, src); - OS_Handle dst_h = os_file_open(OS_AccessFlag_Write, dst); - if(!os_handle_match(src_h, os_handle_zero()) && - !os_handle_match(dst_h, os_handle_zero())) - { - int src_fd = (int)src_h.u64[0]; - int dst_fd = (int)dst_h.u64[0]; - FileProperties src_props = os_properties_from_file(src_h); - U64 size = src_props.size; - U64 total_bytes_copied = 0; - U64 bytes_left_to_copy = size; - for(;bytes_left_to_copy > 0;) - { - off_t sendfile_off = total_bytes_copied; - int send_result = sendfile(dst_fd, src_fd, &sendfile_off, bytes_left_to_copy); - if(send_result <= 0) - { - break; - } - U64 bytes_copied = (U64)send_result; - bytes_left_to_copy -= bytes_copied; - total_bytes_copied += bytes_copied; - } - } - os_file_close(src_h); - os_file_close(dst_h); - return result; -} - -internal String8 -os_full_path_from_path(Arena *arena, String8 path) -{ - Temp scratch = scratch_begin(&arena, 1); - String8 path_copy = push_str8_copy(scratch.arena, path); - char buffer[PATH_MAX] = {0}; - realpath((char *)path_copy.str, buffer); - String8 result = push_str8_copy(arena, str8_cstring(buffer)); - scratch_end(scratch); - return result; -} - -internal B32 -os_file_path_exists(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String8 path_copy = push_str8_copy(scratch.arena, path); - int access_result = access((char *)path_copy.str, F_OK); - B32 result = 0; - if(access_result == 0) - { - result = 1; - } - scratch_end(scratch); - return result; -} - -internal B32 -os_folder_path_exists(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 exists = 0; - String8 path_copy = push_str8_copy(scratch.arena, path); - DIR *handle = opendir((char*)path_copy.str); - if(handle) - { - closedir(handle); - exists = 1; - } - scratch_end(scratch); - return exists; -} - -internal FileProperties -os_properties_from_file_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String8 path_copy = push_str8_copy(scratch.arena, path); - struct stat f_stat = {0}; - int stat_result = stat((char *)path_copy.str, &f_stat); - FileProperties props = {0}; - if(stat_result != -1) - { - props = os_lnx_file_properties_from_stat(&f_stat); - } - scratch_end(scratch); - return props; -} - -//- rjf: file maps - -internal OS_Handle -os_file_map_open(OS_AccessFlags flags, OS_Handle file) -{ - OS_Handle map = file; - return map; -} - -internal void -os_file_map_close(OS_Handle map) -{ - // NOTE(rjf): nothing to do; `map` handles are the same as `file` handles in - // the linux implementation (on Windows they require separate handles) -} - -internal void * -os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) -{ - if(os_handle_match(map, os_handle_zero())) { return 0; } - int fd = (int)map.u64[0]; - int prot_flags = 0; - if(flags & OS_AccessFlag_Write) { prot_flags |= PROT_WRITE; } - if(flags & OS_AccessFlag_Read) { prot_flags |= PROT_READ; } - int map_flags = MAP_PRIVATE; - void *base = mmap(0, dim_1u64(range), prot_flags, map_flags, fd, range.min); - if(base == MAP_FAILED) - { - base = 0; - } - return base; -} - -internal void -os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range) -{ - munmap(ptr, dim_1u64(range)); -} - -//- rjf: directory iteration - -internal OS_FileIter * -os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) -{ - OS_FileIter *base_iter = push_array(arena, OS_FileIter, 1); - base_iter->flags = flags; - OS_LNX_FileIter *iter = (OS_LNX_FileIter *)base_iter->memory; - { - String8 path_copy = push_str8_copy(arena, path); - iter->dir = opendir((char *)path_copy.str); - iter->path = path_copy; - } - return base_iter; -} - -internal B32 -os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) -{ - B32 good = 0; - OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; - for(;;) - { - // rjf: get next entry - lnx_iter->dp = readdir(lnx_iter->dir); - good = (lnx_iter->dp != 0); - - // rjf: unpack entry info - struct stat st = {0}; - int stat_result = 0; - if(good) - { - Temp scratch = scratch_begin(&arena, 1); - String8 full_path = push_str8f(scratch.arena, "%S/%s", lnx_iter->path, lnx_iter->dp->d_name); - stat_result = stat((char *)full_path.str, &st); - scratch_end(scratch); - } - - // rjf: determine if filtered - B32 filtered = 0; - if(good) - { - filtered = ((st.st_mode == S_IFDIR && iter->flags & OS_FileIterFlag_SkipFolders) || - (st.st_mode == S_IFREG && iter->flags & OS_FileIterFlag_SkipFiles) || - (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == 0) || - (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == '.' && lnx_iter->dp->d_name[2] == 0)); - } - - // rjf: output & exit, if good & unfiltered - if(good && !filtered) - { - info_out->name = push_str8_copy(arena, str8_cstring(lnx_iter->dp->d_name)); - if(stat_result != -1) - { - info_out->props = os_lnx_file_properties_from_stat(&st); - } - break; - } - - // rjf: exit if not good - if(!good) - { - break; - } - } - return good; -} - -internal void -os_file_iter_end(OS_FileIter *iter) -{ - OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; - closedir(lnx_iter->dir); -} - -//- rjf: directory creation - -internal B32 -os_make_directory(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 result = 0; - String8 path_copy = push_str8_copy(scratch.arena, path); - if(mkdir((char*)path_copy.str, 0755) != -1) - { - result = 1; - } - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) - -internal OS_Handle -os_shared_memory_alloc(U64 size, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String8 name_copy = push_str8_copy(scratch.arena, name); - int id = shm_open((char *)name_copy.str, O_RDWR, 0); - ftruncate(id, size); - OS_Handle result = {(U64)id}; - scratch_end(scratch); - return result; -} - -internal OS_Handle -os_shared_memory_open(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String8 name_copy = push_str8_copy(scratch.arena, name); - int id = shm_open((char *)name_copy.str, O_RDWR, 0); - OS_Handle result = {(U64)id}; - scratch_end(scratch); - return result; -} - -internal void -os_shared_memory_close(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())){return;} - int id = (int)handle.u64[0]; - close(id); -} - -internal void * -os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) -{ - if(os_handle_match(handle, os_handle_zero())){return 0;} - int id = (int)handle.u64[0]; - void *base = mmap(0, dim_1u64(range), PROT_READ|PROT_WRITE, MAP_SHARED, id, range.min); - if(base == MAP_FAILED) - { - base = 0; - } - return base; -} - -internal void -os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range) -{ - if(os_handle_match(handle, os_handle_zero())){return;} - munmap(ptr, dim_1u64(range)); -} - -//////////////////////////////// -//~ rjf: @os_hooks Time (Implemented Per-OS) - -internal U64 -os_now_microseconds(void) -{ - struct timespec t; - clock_gettime(CLOCK_MONOTONIC, &t); - U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); - return result; -} - -internal U32 -os_now_unix(void) -{ - time_t t = time(0); - return (U32)t; -} - -internal DateTime -os_now_universal_time(void) -{ - time_t t = 0; - time(&t); - struct tm universal_tm = {0}; - gmtime_r(&t, &universal_tm); - DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); - return result; -} - -internal DateTime -os_universal_time_from_local(DateTime *date_time) -{ - // rjf: local DateTime -> universal time_t - tm local_tm = os_lnx_tm_from_date_time(*date_time); - local_tm.tm_isdst = -1; - time_t universal_t = mktime(&local_tm); - - // rjf: universal time_t -> DateTime - tm universal_tm = {0}; - gmtime_r(&universal_t, &universal_tm); - DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); - return result; -} - -internal DateTime -os_local_time_from_universal(DateTime *date_time) -{ - // rjf: universal DateTime -> local time_t - tm universal_tm = os_lnx_tm_from_date_time(*date_time); - universal_tm.tm_isdst = -1; - time_t universal_t = timegm(&universal_tm); - tm local_tm = {0}; - localtime_r(&universal_t, &local_tm); - - // rjf: local tm -> DateTime - DateTime result = os_lnx_date_time_from_tm(local_tm, 0); - return result; -} - -internal void -os_sleep_milliseconds(U32 msec) -{ - usleep(msec*Thousand(1)); -} - -//////////////////////////////// -//~ rjf: @os_hooks Child Processes (Implemented Per-OS) - -internal OS_Handle -os_process_launch(OS_ProcessLaunchParams *params) -{ - NotImplemented; -} - -internal B32 -os_process_join(OS_Handle handle, U64 endt_us) -{ - NotImplemented; -} - -internal void -os_process_detach(OS_Handle handle) -{ - NotImplemented; -} - -//////////////////////////////// -//~ rjf: @os_hooks Threads (Implemented Per-OS) - -internal OS_Handle -os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params) -{ - OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Thread); - entity->thread.func = func; - entity->thread.ptr = ptr; - { - int pthread_result = pthread_create(&entity->thread.handle, 0, os_lnx_thread_entry_point, entity); - if(pthread_result == -1) - { - os_lnx_entity_release(entity); - entity = 0; - } - } - OS_Handle handle = {(U64)entity}; - return handle; -} - -internal B32 -os_thread_join(OS_Handle handle, U64 endt_us) -{ - if(os_handle_match(handle, os_handle_zero())) { return 0; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; - int join_result = pthread_join(entity->thread.handle, 0); - B32 result = (join_result == 0); - os_lnx_entity_release(entity); - return result; -} - -internal void -os_thread_detach(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; - os_lnx_entity_release(entity); -} - -//////////////////////////////// -//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) - -//- rjf: mutexes - -internal OS_Handle -os_mutex_alloc(void) -{ - OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Mutex); - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - int init_result = pthread_mutex_init(&entity->mutex_handle, &attr); - pthread_mutexattr_destroy(&attr); - if(init_result == -1) - { - os_lnx_entity_release(entity); - entity = 0; - } - OS_Handle handle = {(U64)entity}; - return handle; -} - -internal void -os_mutex_release(OS_Handle mutex) -{ - if(os_handle_match(mutex, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; - pthread_mutex_destroy(&entity->mutex_handle); - os_lnx_entity_release(entity); -} - -internal void -os_mutex_take(OS_Handle mutex) -{ - if(os_handle_match(mutex, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; - pthread_mutex_lock(&entity->mutex_handle); -} - -internal void -os_mutex_drop(OS_Handle mutex) -{ - if(os_handle_match(mutex, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; - pthread_mutex_unlock(&entity->mutex_handle); -} - -//- rjf: reader/writer mutexes - -internal OS_Handle -os_rw_mutex_alloc(void) -{ - OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_RWMutex); - int init_result = pthread_rwlock_init(&entity->rwmutex_handle, 0); - if(init_result == -1) - { - os_lnx_entity_release(entity); - entity = 0; - } - OS_Handle handle = {(U64)entity}; - return handle; -} - -internal void -os_rw_mutex_release(OS_Handle rw_mutex) -{ - if(os_handle_match(rw_mutex, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; - pthread_rwlock_destroy(&entity->rwmutex_handle); - os_lnx_entity_release(entity); -} - -internal void -os_rw_mutex_take_r(OS_Handle rw_mutex) -{ - if(os_handle_match(rw_mutex, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; - pthread_rwlock_rdlock(&entity->rwmutex_handle); -} - -internal void -os_rw_mutex_drop_r(OS_Handle rw_mutex) -{ - if(os_handle_match(rw_mutex, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; - pthread_rwlock_unlock(&entity->rwmutex_handle); -} - -internal void -os_rw_mutex_take_w(OS_Handle rw_mutex) -{ - if(os_handle_match(rw_mutex, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; - pthread_rwlock_wrlock(&entity->rwmutex_handle); -} - -internal void -os_rw_mutex_drop_w(OS_Handle rw_mutex) -{ - if(os_handle_match(rw_mutex, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; - pthread_rwlock_unlock(&entity->rwmutex_handle); -} - -//- rjf: condition variables - -internal OS_Handle -os_condition_variable_alloc(void) -{ - OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_ConditionVariable); - int init_result = pthread_cond_init(&entity->cv.cond_handle, 0); - if(init_result == -1) - { - os_lnx_entity_release(entity); - entity = 0; - } - int init2_result = 0; - if(entity) - { - init2_result = pthread_mutex_init(&entity->cv.rwlock_mutex_handle, 0); - } - if(init2_result == -1) - { - pthread_cond_destroy(&entity->cv.cond_handle); - os_lnx_entity_release(entity); - entity = 0; - } - OS_Handle handle = {(U64)entity}; - return handle; -} - -internal void -os_condition_variable_release(OS_Handle cv) -{ - if(os_handle_match(cv, os_handle_zero())) { return; } - OS_LNX_Entity *entity = (OS_LNX_Entity *)cv.u64[0]; - pthread_cond_destroy(&entity->cv.cond_handle); - pthread_mutex_destroy(&entity->cv.rwlock_mutex_handle); - os_lnx_entity_release(entity); -} - -internal B32 -os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us) -{ - if(os_handle_match(cv, os_handle_zero())) { return 0; } - if(os_handle_match(mutex, os_handle_zero())) { return 0; } - OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; - OS_LNX_Entity *mutex_entity = (OS_LNX_Entity *)mutex.u64[0]; - struct timespec endt_timespec; - endt_timespec.tv_sec = endt_us/Million(1); - endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); - int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &mutex_entity->mutex_handle, &endt_timespec); - B32 result = (wait_result != ETIMEDOUT); - return result; -} - -internal B32 -os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) -{ - // TODO(rjf): because pthread does not supply cv/rw natively, I had to hack - // this together, but this would probably just be a lot better if we just - // implemented the primitives ourselves with e.g. futexes - // - if(os_handle_match(cv, os_handle_zero())) { return 0; } - if(os_handle_match(mutex_rw, os_handle_zero())) { return 0; } - OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; - OS_LNX_Entity *rw_mutex_entity = (OS_LNX_Entity *)mutex_rw.u64[0]; - struct timespec endt_timespec; - endt_timespec.tv_sec = endt_us/Million(1); - endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); - B32 result = 0; - for(;;) - { - pthread_mutex_lock(&cv_entity->cv.rwlock_mutex_handle); - int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &cv_entity->cv.rwlock_mutex_handle, &endt_timespec); - if(wait_result != ETIMEDOUT) - { - pthread_rwlock_rdlock(&rw_mutex_entity->rwmutex_handle); - pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); - result = 1; - break; - } - pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); - if(wait_result == ETIMEDOUT) - { - break; - } - } - return result; -} - -internal B32 -os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) -{ - // TODO(rjf): because pthread does not supply cv/rw natively, I had to hack - // this together, but this would probably just be a lot better if we just - // implemented the primitives ourselves with e.g. futexes - // - if(os_handle_match(cv, os_handle_zero())) { return 0; } - if(os_handle_match(mutex_rw, os_handle_zero())) { return 0; } - OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; - OS_LNX_Entity *rw_mutex_entity = (OS_LNX_Entity *)mutex_rw.u64[0]; - struct timespec endt_timespec; - endt_timespec.tv_sec = endt_us/Million(1); - endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); - B32 result = 0; - for(;;) - { - pthread_mutex_lock(&cv_entity->cv.rwlock_mutex_handle); - int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &cv_entity->cv.rwlock_mutex_handle, &endt_timespec); - if(wait_result != ETIMEDOUT) - { - pthread_rwlock_wrlock(&rw_mutex_entity->rwmutex_handle); - pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); - result = 1; - break; - } - pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); - if(wait_result == ETIMEDOUT) - { - break; - } - } - return result; -} - -internal void -os_condition_variable_signal(OS_Handle cv) -{ - if(os_handle_match(cv, os_handle_zero())) { return; } - OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; - pthread_cond_signal(&cv_entity->cv.cond_handle); -} - -internal void -os_condition_variable_broadcast(OS_Handle cv) -{ - if(os_handle_match(cv, os_handle_zero())) { return; } - OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; - pthread_cond_broadcast(&cv_entity->cv.cond_handle); -} - -//- rjf: cross-process semaphores - -internal OS_Handle -os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) -{ - OS_Handle result = {0}; - if (name.size > 0) { - // TODO: we need to allocate shared memory to store sem_t - NotImplemented; - } else { - sem_t *s = mmap(0, sizeof(*s), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - AssertAlways(s != MAP_FAILED); - int err = sem_init(s, 0, initial_count); - if (err == 0) { - result.u64[0] = (U64)s; - } - } - return result; -} - -internal void -os_semaphore_release(OS_Handle semaphore) -{ - int err = munmap((void*)semaphore.u64[0], sizeof(sem_t)); - AssertAlways(err == 0); -} - -internal OS_Handle -os_semaphore_open(String8 name) -{ - NotImplemented; -} - -internal void -os_semaphore_close(OS_Handle semaphore) -{ - NotImplemented; -} - -internal B32 -os_semaphore_take(OS_Handle semaphore, U64 endt_us) -{ - AssertAlways(endt_us == max_U64); - for (;;) { - int err = sem_wait((sem_t*)semaphore.u64[0]); - if (err == 0) { - break; - } else { - if (errno == EAGAIN) { - continue; - } - } - InvalidPath; - break; - } - return 1; -} - -internal void -os_semaphore_drop(OS_Handle semaphore) -{ - for (;;) { - int err = sem_post((sem_t*)semaphore.u64[0]); - if (err == 0) { - break; - } else { - if (errno == EAGAIN) { - continue; - } - } - InvalidPath; - break; - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal OS_Handle -os_library_open(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - char *path_cstr = (char *)push_str8_copy(scratch.arena, path).str; - void *so = dlopen(path_cstr, RTLD_LAZY|RTLD_LOCAL); - OS_Handle lib = { (U64)so }; - scratch_end(scratch); - return lib; -} - -internal VoidProc* -os_library_load_proc(OS_Handle lib, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - void *so = (void *)lib.u64; - char *name_cstr = (char *)push_str8_copy(scratch.arena, name).str; - VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); - scratch_end(scratch); - return proc; -} - -internal void -os_library_close(OS_Handle lib) -{ - void *so = (void *)lib.u64; - dlclose(so); -} - -//////////////////////////////// -//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) - -internal void -os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr) -{ - // rjf: push handler to chain - OS_LNX_SafeCallChain chain = {0}; - SLLStackPush(os_lnx_safe_call_chain, &chain); - chain.fail_handler = fail_handler; - chain.ptr = ptr; - - // rjf: set up sig handler info - struct sigaction new_act = {0}; - new_act.sa_handler = os_lnx_safe_call_sig_handler; - int signals_to_handle[] = - { - SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, - }; - struct sigaction og_act[ArrayCount(signals_to_handle)] = {0}; - - // rjf: attach handler info for all signals - for(U32 i = 0; i < ArrayCount(signals_to_handle); i += 1) - { - sigaction(signals_to_handle[i], &new_act, &og_act[i]); - } - - // rjf: call function - func(ptr); - - // rjf: reset handler info for all signals - for(U32 i = 0; i < ArrayCount(signals_to_handle); i += 1) - { - sigaction(signals_to_handle[i], &og_act[i], 0); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks GUIDs (Implemented Per-OS) - -internal Guid -os_make_guid(void) -{ - Guid guid = {0}; - getrandom(guid.v, sizeof(guid.v), 0); - guid.data3 &= 0x0fff; - guid.data3 |= (4 << 12); - guid.data4[0] &= 0x3f; - guid.data4[0] |= 0x80; - return guid; -} - -//////////////////////////////// -//~ rjf: @os_hooks Entry Points (Implemented Per-OS) - -int -main(int argc, char **argv) -{ - //- rjf: set up OS layer - { - //- rjf: get statically-allocated system/process info - { - OS_SystemInfo *info = &os_lnx_state.system_info; - info->logical_processor_count = (U32)get_nprocs(); - info->page_size = (U64)getpagesize(); - info->large_page_size = MB(2); - info->allocation_granularity = info->page_size; - } - { - OS_ProcessInfo *info = &os_lnx_state.process_info; - info->pid = (U32)getpid(); - } - - //- rjf: set up thread context - local_persist TCTX tctx; - tctx_init_and_equip(&tctx); - - //- rjf: set up dynamically allocated state - os_lnx_state.arena = arena_alloc(); - os_lnx_state.entity_arena = arena_alloc(); - pthread_mutex_init(&os_lnx_state.entity_mutex, 0); - - //- rjf: grab dynamically allocated system info - { - Temp scratch = scratch_begin(0, 0); - OS_SystemInfo *info = &os_lnx_state.system_info; - - // rjf: get machine name - B32 got_final_result = 0; - U8 *buffer = 0; - int size = 0; - for(S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1) - { - scratch_end(scratch); - buffer = push_array_no_zero(scratch.arena, U8, cap); - size = gethostname((char*)buffer, cap); - if(size < cap) - { - got_final_result = 1; - break; - } - } - - // rjf: save name to info - if(got_final_result && size > 0) - { - info->machine_name.size = size; - info->machine_name.str = push_array_no_zero(os_lnx_state.arena, U8, info->machine_name.size + 1); - MemoryCopy(info->machine_name.str, buffer, info->machine_name.size); - info->machine_name.str[info->machine_name.size] = 0; - } - - scratch_end(scratch); - } - - //- rjf: grab dynamically allocated process info - { - Temp scratch = scratch_begin(0, 0); - OS_ProcessInfo *info = &os_lnx_state.process_info; - - // rjf: grab binary path - { - // rjf: get self string - B32 got_final_result = 0; - U8 *buffer = 0; - int size = 0; - for(S64 cap = PATH_MAX, r = 0; r < 4; cap *= 2, r += 1) - { - scratch_end(scratch); - buffer = push_array_no_zero(scratch.arena, U8, cap); - size = readlink("/proc/self/exe", (char*)buffer, cap); - if(size < cap) - { - got_final_result = 1; - break; - } - } - - // rjf: save - if(got_final_result && size > 0) - { - String8 full_name = str8(buffer, size); - String8 name_chopped = str8_chop_last_slash(full_name); - info->binary_path = push_str8_copy(os_lnx_state.arena, name_chopped); - } - } - - // rjf: grab initial directory - { - info->initial_path = os_get_current_path(os_lnx_state.arena); - } - - // rjf: grab home directory - { - char *home = getenv("HOME"); - info->user_program_data_path = str8_cstring(home); - } - - scratch_end(scratch); - } - } - - //- rjf: call into "real" entry point - main_thread_base_entry_point(argc, argv); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ dan: Helpers + +internal DateTime +os_lnx_date_time_from_tm(tm in, U32 msec) +{ + DateTime dt = {0}; + dt.sec = in.tm_sec; + dt.min = in.tm_min; + dt.hour = in.tm_hour; + dt.day = in.tm_mday-1; + dt.mon = in.tm_mon; + dt.year = in.tm_year+1900; + dt.msec = msec; + return dt; +} + +internal tm +os_lnx_tm_from_date_time(DateTime dt) +{ + tm result = {0}; + result.tm_sec = dt.sec; + result.tm_min = dt.min; + result.tm_hour= dt.hour; + result.tm_mday= dt.day+1; + result.tm_mon = dt.mon; + result.tm_year= dt.year-1900; + return result; +} + +internal timespec +os_lnx_timespec_from_date_time(DateTime dt) +{ + tm tm_val = os_lnx_tm_from_date_time(dt); + time_t seconds = timegm(&tm_val); + timespec result = {0}; + result.tv_sec = seconds; + return result; +} + +internal DenseTime +os_lnx_dense_time_from_timespec(timespec in) +{ + DenseTime result = 0; + // Check for zero timespec before converting + if (in.tv_sec != 0 || in.tv_nsec != 0) + { + struct tm tm_time = {0}; + // Use gmtime_r for thread safety + time_t seconds = in.tv_sec; // Copy to time_t + if (gmtime_r(&seconds, &tm_time) != NULL) // Pass pointer to time_t + { + // Note: st_mtime/st_ctime etc. are usually UTC + DateTime date_time = os_lnx_date_time_from_tm(tm_time, in.tv_nsec / Million(1)); + result = dense_time_from_date_time(date_time); + } + } + return result; +} + +internal DenseTime +os_lnx_dense_time_from_statx_timestamp(struct statx_timestamp in) +{ + DenseTime result = 0; + // Check for zero timestamp before converting + if (in.tv_sec != 0 || in.tv_nsec != 0) + { + struct tm tm_time = {0}; + // Use gmtime_r for thread safety + time_t seconds = in.tv_sec; // Copy to time_t + if (gmtime_r(&seconds, &tm_time) != NULL) // Pass pointer to time_t + { + // Convert nanoseconds to milliseconds for DateTime + DateTime date_time = os_lnx_date_time_from_tm(tm_time, in.tv_nsec / Million(1)); + result = dense_time_from_date_time(date_time); + } + } + return result; +} + +internal FileProperties +os_lnx_file_properties_from_stat(struct stat *s) +{ + FileProperties props = {0}; + props.size = s->st_size; + // NOTE(rjf): Fallback path for creation time. `os_properties_from_file*` functions + // will attempt `statx` first. This function handles `stat` results. +#if defined(st_birthtim) // Check if st_birthtim is available (more specific than st_ctim) + props.created = os_lnx_dense_time_from_timespec(s->st_birthtim); +#else // Fallback to st_ctim if st_birthtim is not available + props.created = os_lnx_dense_time_from_timespec(s->st_ctim); +#endif + props.modified = os_lnx_dense_time_from_timespec(s->st_mtim); + if(S_ISDIR(s->st_mode)) // Use S_ISDIR macro for clarity + { + props.flags |= FilePropertyFlag_IsFolder; + } + return props; +} + +internal int +os_lnx_futex(atomic_uint *uaddr, int futex_op, unsigned int val, const struct timespec *timeout, unsigned int val3) +{ + // NOTE(rjf): `syscall` needs non-const pointer for timeout for some reason. + struct timespec *timeout_nonconst = (struct timespec*)timeout; + return syscall(SYS_futex, uaddr, futex_op, val, timeout_nonconst, NULL, val3); +} + +internal int +os_lnx_futex_wait(atomic_uint *uaddr, unsigned int expected_val, const struct timespec *timeout) +{ + // NOTE(rjf): timeout can be NULL for infinite wait. FUTEX_WAIT_PRIVATE is used for intra-process sync. + return os_lnx_futex(uaddr, FUTEX_WAIT_PRIVATE, expected_val, timeout, 0); +} + +internal int +os_lnx_futex_wake(atomic_uint *uaddr, int num_to_wake) +{ + // NOTE(rjf): FUTEX_WAKE_PRIVATE is used for intra-process sync. + return os_lnx_futex(uaddr, FUTEX_WAKE_PRIVATE, num_to_wake, NULL, 0); +} + +//////////////////////////////// +//~ dan: Entities + +internal OS_LNX_Entity * +os_lnx_entity_alloc(OS_LNX_EntityKind kind) +{ + OS_LNX_Entity *entity = 0; + DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), + pthread_mutex_unlock(&os_lnx_state.entity_mutex)) + { + entity = os_lnx_state.entity_free; + if(entity) + { + SLLStackPop(os_lnx_state.entity_free); + } + else + { + entity = push_array_no_zero(os_lnx_state.entity_arena, OS_LNX_Entity, 1); + } + } + MemoryZeroStruct(entity); + entity->kind = kind; + + // Initialize based on kind + switch(kind) + { + case OS_LNX_EntityKind_Mutex: + atomic_init(&entity->mutex.futex, 0); // 0: unlocked + atomic_init(&entity->mutex.owner_tid, 0); + atomic_init(&entity->mutex.recursion_depth, 0); + break; + case OS_LNX_EntityKind_RWMutex: + atomic_init(&entity->rw_mutex.futex, 0); + break; + case OS_LNX_EntityKind_ConditionVariable: + atomic_init(&entity->cv.futex, 0); // Initialize sequence number/generation count + break; + case OS_LNX_EntityKind_SafeCallChain: + // Initialization happens in os_safe_call + break; + // Other kinds (Thread, Process, Semaphore) are initialized elsewhere or don't need specific init here + default: break; + } + + return entity; +} + +internal void +os_lnx_entity_release(OS_LNX_Entity *entity) +{ + // NOTE(rjf): Additional cleanup per-kind can go here if needed, e.g., free name strings for semaphores. + // For SafeCallChain, cleanup (handler restoration) happens within os_safe_call itself. + DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), + pthread_mutex_unlock(&os_lnx_state.entity_mutex)) + { + SLLStackPush(os_lnx_state.entity_free, entity); + } +} + +//////////////////////////////// +//~ dan: Thread Entry Point + +internal void * +os_lnx_thread_entry_point(void *ptr) +{ + OS_LNX_Entity *entity = (OS_LNX_Entity *)ptr; + OS_ThreadFunctionType *func = entity->thread.func; + void *thread_ptr = entity->thread.ptr; + TCTX tctx_; + tctx_init_and_equip(&tctx_); + func(thread_ptr); + tctx_release(); + return 0; +} + +//////////////////////////////// +//~ dan: @os_hooks System/Process Info (Implemented Per-OS) + +internal OS_SystemInfo * +os_get_system_info(void) +{ + return &os_lnx_state.system_info; +} + +internal OS_ProcessInfo * +os_get_process_info(void) +{ + return &os_lnx_state.process_info; +} + +internal String8 +os_get_current_path(Arena *arena) +{ + char *cwdir = getcwd(0, 0); + String8 string = push_str8_copy(arena, str8_cstring(cwdir)); + free(cwdir); + return string; +} + +internal U32 +os_get_process_start_time_unix(void) +{ + Temp scratch = scratch_begin(0,0); + U64 start_time = 0; + pid_t pid = getpid(); + String8 path = push_str8f(scratch.arena, "/proc/%u", pid); + struct stat st; + int err = stat((char*)path.str, &st); + if(err == 0) + { + start_time = st.st_mtime; + } + scratch_end(scratch); + return (U32)start_time; +} + +//////////////////////////////// +//~ dan: @os_hooks Memory Allocation (Implemented Per-OS) + +//- rjf: basic + +internal void * +os_reserve(U64 size) +{ + void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + if(result == MAP_FAILED) + { + result = 0; + } + return result; +} + +internal B32 +os_commit(void *ptr, U64 size) +{ + int result = mprotect(ptr, size, PROT_READ|PROT_WRITE); + if (result == -1) { + perror("mprotect commit"); + // Consider returning 0 on failure, matching Win32 VirtualAlloc failure. + // For now, maintain original behavior but log error. + // return 0; + } + return 1; // Original behavior: always return success +} + +internal void +os_decommit(void *ptr, U64 size) +{ + int madvise_result = madvise(ptr, size, MADV_DONTNEED); + if (madvise_result == -1) { + perror("madvise decommit"); + } + int mprotect_result = mprotect(ptr, size, PROT_NONE); + if (mprotect_result == -1) { + perror("mprotect decommit"); + } +} + +internal void +os_release(void *ptr, U64 size) +{ + int result = munmap(ptr, size); + if (result == -1) { + perror("munmap release"); + } +} + +//- rjf: large pages + +internal void * +os_reserve_large(U64 size) +{ + void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0); + if(result == MAP_FAILED) + { + result = 0; + } + return result; +} + +internal B32 +os_commit_large(void *ptr, U64 size) +{ + int result = mprotect(ptr, size, PROT_READ|PROT_WRITE); + if (result == -1) { + perror("mprotect commit_large"); + return 0; + } + return 1; +} + +//////////////////////////////// +//~ dan: @os_hooks Thread Info (Implemented Per-OS) + +internal U32 +os_tid(void) +{ + U32 result = gettid(); + return result; +} + +internal void +os_set_thread_name(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String8 name_copy = push_str8_copy(scratch.arena, name); + pthread_t current_thread = pthread_self(); + int err = pthread_setname_np(current_thread, (char *)name_copy.str); + if (err != 0) { + // Optional: Log error, but it's often non-critical + // fprintf(stderr, "Warning: Failed to set thread name: %s\n", strerror(err)); + } + scratch_end(scratch); +} + +//////////////////////////////// +//~ dan: @os_hooks Aborting (Implemented Per-OS) + +internal void +os_abort(S32 exit_code) +{ + exit(exit_code); +} + +//////////////////////////////// +//~ dan: @os_hooks File System (Implemented Per-OS) + +//- rjf: files + +internal OS_Handle +os_file_open(OS_AccessFlags flags, String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + int lnx_flags = 0; + if(flags & OS_AccessFlag_Read && flags & OS_AccessFlag_Write) + { + lnx_flags = O_RDWR; + } + else if(flags & OS_AccessFlag_Write) + { + lnx_flags = O_WRONLY; + } + else if(flags & OS_AccessFlag_Read) + { + lnx_flags = O_RDONLY; + } + if(flags & OS_AccessFlag_Append) + { + lnx_flags |= O_APPEND; + } + if(flags & (OS_AccessFlag_Write|OS_AccessFlag_Append)) + { + lnx_flags |= O_CREAT; + } + int fd = open((char *)path_copy.str, lnx_flags, 0755); + OS_Handle handle = {0}; + if(fd != -1) + { + handle.u64[0] = fd; + // Set FD_CLOEXEC based on inheritance flag + if (!(flags & OS_AccessFlag_Inherited)) + { + int fd_flags = fcntl(fd, F_GETFD); + if (fd_flags != -1) + { + fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC); + } + else + { + // Handle fcntl error (optional: log? close fd?) + close(fd); + fd = -1; + handle = os_handle_zero(); + } + } + } + scratch_end(scratch); + return handle; +} + +internal void +os_file_close(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { return; } + int fd = (int)file.u64[0]; + close(fd); +} + +internal U64 +os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + int fd = (int)file.u64[0]; + U64 total_num_bytes_to_read = dim_1u64(rng); + U64 total_num_bytes_read = 0; + U64 total_num_bytes_left_to_read = total_num_bytes_to_read; + for(;total_num_bytes_left_to_read > 0;) + { + int read_result = pread(fd, (U8 *)out_data + total_num_bytes_read, total_num_bytes_left_to_read, rng.min + total_num_bytes_read); + if(read_result >= 0) + { + total_num_bytes_read += read_result; + total_num_bytes_left_to_read -= read_result; + } + else if(errno != EINTR) + { + break; + } + } + return total_num_bytes_read; +} + +internal U64 +os_file_write(OS_Handle file, Rng1U64 rng, void *data) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + int fd = (int)file.u64[0]; + U64 total_num_bytes_to_write = dim_1u64(rng); + U64 total_num_bytes_written = 0; + U64 total_num_bytes_left_to_write = total_num_bytes_to_write; + for(;total_num_bytes_left_to_write > 0;) + { + int write_result = pwrite(fd, (U8 *)data + total_num_bytes_written, total_num_bytes_left_to_write, rng.min + total_num_bytes_written); + if(write_result >= 0) + { + total_num_bytes_written += write_result; + total_num_bytes_left_to_write -= write_result; + } + else if(errno != EINTR) + { + break; + } + } + return total_num_bytes_written; +} + +internal B32 +os_file_set_times(OS_Handle file, DateTime date_time) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + int fd = (int)file.u64[0]; + timespec time = os_lnx_timespec_from_date_time(date_time); + timespec times[2] = {time, time}; + int futimens_result = futimens(fd, times); + B32 good = (futimens_result != -1); + return good; +} + +internal FileProperties +os_properties_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { return (FileProperties){0}; } + int fd = (int)file.u64[0]; + FileProperties props = {0}; + +#if defined(__NR_statx) // Check if statx syscall number is defined + struct statx stx = {0}; + // Try statx first to get birth time if available + if (syscall(__NR_statx, fd, "", AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, STATX_BTIME | STATX_MTIME | STATX_SIZE | STATX_TYPE, &stx) == 0) + { + // Populate common properties + props.size = stx.stx_size; + props.modified = os_lnx_dense_time_from_statx_timestamp(stx.stx_mtime); + if(S_ISDIR(stx.stx_mode)) { props.flags |= FilePropertyFlag_IsFolder; } + + // Use birth time if available + if (stx.stx_mask & STATX_BTIME) { + props.created = os_lnx_dense_time_from_statx_timestamp(stx.stx_btime); + } else { + // statx succeeded but didn't return birth time, fall back to fstat/st_ctim/st_birthtim logic + struct stat fd_stat = {0}; + if (fstat(fd, &fd_stat) == 0) { + props = os_lnx_file_properties_from_stat(&fd_stat); // Re-use stat logic for fallback create time + // Keep size, modified, flags from statx as they are likely more accurate or available + props.size = stx.stx_size; + props.modified = os_lnx_dense_time_from_statx_timestamp(stx.stx_mtime); + props.flags = 0; // Reset flags and re-set based on statx mode + if(S_ISDIR(stx.stx_mode)) { props.flags |= FilePropertyFlag_IsFolder; } + } + // If fstat fails here, props remain partially filled from statx, created time is zero. + } + return props; // Return props obtained from statx (potentially with fallback create time) + } + // If statx failed, fall through to fstat below +#endif // __NR_statx + + // Fallback to fstat if statx is not available or failed + struct stat fd_stat = {0}; + int fstat_result = fstat(fd, &fd_stat); + if(fstat_result != -1) + { + props = os_lnx_file_properties_from_stat(&fd_stat); + } + return props; +} + +internal OS_FileID +os_id_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { return (OS_FileID){0}; } + int fd = (int)file.u64[0]; + struct stat fd_stat = {0}; + int fstat_result = fstat(fd, &fd_stat); + OS_FileID id = {0}; + if(fstat_result != -1) + { + id.v[0] = fd_stat.st_dev; + id.v[1] = fd_stat.st_ino; + } + return id; +} + +internal B32 +os_delete_file_at_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 result = 0; + String8 path_copy = push_str8_copy(scratch.arena, path); + if(remove((char*)path_copy.str) != -1) + { + result = 1; + } + scratch_end(scratch); + return result; +} + +internal B32 +os_copy_file_path(String8 dst, String8 src) +{ + B32 result = 0; + OS_Handle src_h = os_file_open(OS_AccessFlag_Read, src); + OS_Handle dst_h = os_file_open(OS_AccessFlag_Write, dst); + if(!os_handle_match(src_h, os_handle_zero()) && + !os_handle_match(dst_h, os_handle_zero())) + { + int src_fd = (int)src_h.u64[0]; + int dst_fd = (int)dst_h.u64[0]; + FileProperties src_props = os_properties_from_file(src_h); + U64 size = src_props.size; + U64 total_bytes_copied = 0; + U64 bytes_left_to_copy = size; + B32 copy_error = 0; + for(;bytes_left_to_copy > 0;) + { + off_t sendfile_off = total_bytes_copied; + // Use ssize_t for sendfile result as it can return -1 on error + ssize_t send_result = sendfile(dst_fd, src_fd, &sendfile_off, bytes_left_to_copy); + if(send_result <= 0) + { + // Error or end of file reached prematurely + copy_error = 1; + break; + } + U64 bytes_copied = (U64)send_result; + bytes_left_to_copy -= bytes_copied; + total_bytes_copied += bytes_copied; + } + + // Check if the entire file was copied without errors + if (!copy_error && total_bytes_copied == size) + { + result = 1; + // Set the destination file times to match the source modification time + DateTime modified_dt = date_time_from_dense_time(src_props.modified); + os_file_set_times(dst_h, modified_dt); + } + } + os_file_close(src_h); + os_file_close(dst_h); + return result; +} + +internal String8 +os_full_path_from_path(Arena *arena, String8 path) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 path_copy = push_str8_copy(scratch.arena, path); + char buffer[PATH_MAX] = {0}; + realpath((char *)path_copy.str, buffer); + String8 result = push_str8_copy(arena, str8_cstring(buffer)); + scratch_end(scratch); + return result; +} + +internal B32 +os_file_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + int access_result = access((char *)path_copy.str, F_OK); + B32 result = 0; + if(access_result == 0) + { + result = 1; + } + scratch_end(scratch); + return result; +} + +internal B32 +os_folder_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 exists = 0; + String8 path_copy = push_str8_copy(scratch.arena, path); + DIR *handle = opendir((char*)path_copy.str); + if(handle) + { + closedir(handle); + exists = 1; + } + scratch_end(scratch); + return exists; +} + +internal FileProperties +os_properties_from_file_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + FileProperties props = {0}; + +#if defined(__NR_statx) // Check if statx syscall number is defined + struct statx stx = {0}; + // Try statx first to get birth time if available + // Use AT_SYMLINK_NOFOLLOW to match stat() behavior unless symlinks should be followed + if (syscall(__NR_statx, AT_FDCWD, (char *)path_copy.str, AT_SYMLINK_NOFOLLOW, STATX_BTIME | STATX_MTIME | STATX_SIZE | STATX_TYPE, &stx) == 0) + { + // Populate common properties + props.size = stx.stx_size; + props.modified = os_lnx_dense_time_from_statx_timestamp(stx.stx_mtime); + if(S_ISDIR(stx.stx_mode)) { props.flags |= FilePropertyFlag_IsFolder; } + + // Use birth time if available + if (stx.stx_mask & STATX_BTIME) { + props.created = os_lnx_dense_time_from_statx_timestamp(stx.stx_btime); + } else { + // statx succeeded but didn't return birth time, fall back to stat/st_ctim/st_birthtim logic + struct stat f_stat = {0}; + if (stat((char *)path_copy.str, &f_stat) == 0) { + props = os_lnx_file_properties_from_stat(&f_stat); // Re-use stat logic for fallback create time + // Keep size, modified, flags from statx + props.size = stx.stx_size; + props.modified = os_lnx_dense_time_from_statx_timestamp(stx.stx_mtime); + props.flags = 0; // Reset flags and re-set based on statx mode + if(S_ISDIR(stx.stx_mode)) { props.flags |= FilePropertyFlag_IsFolder; } + } + // If stat fails here, props remain partially filled from statx, created time is zero. + } + scratch_end(scratch); + return props; // Return props obtained from statx (potentially with fallback create time) + } + // If statx failed, fall through to stat below +#endif // __NR_statx + + // Fallback to stat if statx is not available or failed + struct stat f_stat = {0}; + int stat_result = stat((char *)path_copy.str, &f_stat); + if(stat_result != -1) + { + props = os_lnx_file_properties_from_stat(&f_stat); + } + scratch_end(scratch); + return props; +} + +//- rjf: file maps + +internal OS_Handle +os_file_map_open(OS_AccessFlags flags, OS_Handle file) +{ + OS_Handle map = file; + return map; +} + +internal void +os_file_map_close(OS_Handle map) +{ + // NOTE(rjf): nothing to do; `map` handles are the same as `file` handles in + // the linux implementation (on Windows they require separate handles) +} + +internal void * +os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) +{ + if(os_handle_match(map, os_handle_zero())) { return 0; } + int fd = (int)map.u64[0]; + int prot_flags = 0; + if(flags & OS_AccessFlag_Write) { prot_flags |= PROT_WRITE; } + if(flags & OS_AccessFlag_Read) { prot_flags |= PROT_READ; } + int map_flags = MAP_PRIVATE; + void *base = mmap(0, dim_1u64(range), prot_flags, map_flags, fd, range.min); + if(base == MAP_FAILED) + { + base = 0; + } + return base; +} + +internal void +os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range) +{ + munmap(ptr, dim_1u64(range)); + int result = munmap(ptr, dim_1u64(range)); + if (result == -1) { + perror("munmap file_map_view_close"); + } +} + +//- rjf: directory iteration + +internal OS_FileIter * +os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) +{ + OS_FileIter *base_iter = push_array(arena, OS_FileIter, 1); + base_iter->flags = flags; + OS_LNX_FileIter *iter = (OS_LNX_FileIter *)base_iter->memory; + MemoryZeroStruct(iter); // Initialize struct + + if(path.size == 0) + { + // Handle volume iteration (mount points on Linux) + iter->is_volume_iter = 1; + iter->mount_file_stream = setmntent(_PATH_MOUNTED, "r"); // Readonly access to mount table + if (iter->mount_file_stream != NULL) + { + String8List mount_points_list = {0}; + struct mntent *mount_entry; + String8 skip_fs_types[] = { + str8_lit("proc"), str8_lit("sysfs"), str8_lit("devtmpfs"), str8_lit("tmpfs"), + str8_lit("cgroup"), str8_lit("cgroup2"), str8_lit("debugfs"), str8_lit("devpts"), + str8_lit("securityfs"), str8_lit("pstore"), str8_lit("autofs"), str8_lit("mqueue"), + str8_lit("hugetlbfs"), str8_lit("binfmt_misc"), str8_lit("fusectl"), + str8_lit("tracefs"), str8_lit("configfs"), str8_lit("fuse.gvfsd-fuse"), + // Add other pseudo/virtual/network FS types to skip as needed + }; + + while ((mount_entry = getmntent(iter->mount_file_stream)) != NULL) + { + // Filter out pseudo-filesystems and unwanted types + B32 skip = 0; + String8 fs_type = str8_cstring(mount_entry->mnt_type); + for (U64 i = 0; i < ArrayCount(skip_fs_types); ++i) { + if (str8_match(fs_type, skip_fs_types[i], 0)) { + skip = 1; + break; + } + } + if (skip) { + continue; + } + + // TODO(rjf): Consider filtering based on mnt_opts (e.g., skip "noauto")? + + String8 mount_dir = str8_cstring(mount_entry->mnt_dir); + // TODO(rjf): Check for duplicates if the same device is mounted multiple times? + // For now, assume unique mount points reported by getmntent are sufficient. + str8_list_push(arena, &mount_points_list, mount_dir); + } + iter->volume_mount_points = str8_array_from_list(arena, &mount_points_list); + iter->volume_iter_idx = 0; + // We keep mount_file_stream open until os_file_iter_end + } + else + { + // Failed to open mount table, log error? Volume iteration won't work. + iter->volume_mount_points.count = 0; // Ensure iteration ends immediately + } + // If setmntent fails, volume_mount_points will be empty, and iteration will end immediately. + } + else + { + // Handle regular directory iteration + iter->is_volume_iter = 0; + String8 path_copy = push_str8_copy(arena, path); + iter->dir = opendir((char *)path_copy.str); + iter->path = path_copy; + // iter->mount_file_stream will be NULL + } + + return base_iter; +} + +internal B32 +os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) +{ + B32 good = 0; + OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; + + if (lnx_iter->is_volume_iter) + { + // Volume (Mount Point) Iteration - Unchanged + if (lnx_iter->volume_iter_idx < lnx_iter->volume_mount_points.count) + { + MemoryZeroStruct(info_out); + info_out->name = lnx_iter->volume_mount_points.v[lnx_iter->volume_iter_idx]; + info_out->props.flags |= FilePropertyFlag_IsFolder; + // Props remain minimal for volume iteration + lnx_iter->volume_iter_idx++; + good = 1; + } + } + else + { + // Regular Directory Iteration + if (lnx_iter->dir == 0) // Check if opendir failed in begin + { + iter->flags |= OS_FileIterFlag_Done; + return 0; + } + + for (;;) // Loop until a valid, unfiltered entry is found or the directory ends + { + // 1. Read next directory entry + errno = 0; // Reset errno before readdir + lnx_iter->dp = readdir(lnx_iter->dir); + + if (lnx_iter->dp == 0) // End of directory or error + { + if (errno != 0) { + // An error occurred during readdir, treat as end of iteration for now. + // A more robust implementation might log the error here via errno. + // Optional: Log error? + } + good = 0; // Mark as not found this iteration + iter->flags |= OS_FileIterFlag_Done; // Mark iteration as finished + break; // Exit loop + } + + // 2. Filter "." and ".." + char *d_name = lnx_iter->dp->d_name; + if (d_name[0] == '.') { + if (d_name[1] == 0 || (d_name[1] == '.' && d_name[2] == 0)) { + continue; // Skip "." and ".." + } + // 3. Filter hidden files if requested + if (iter->flags & OS_FileIterFlag_SkipHiddenFiles) { + continue; // Skip hidden files (e.g., ".bashrc") + } + } + + // 4. Always use stat for definitive type and properties + Temp scratch = scratch_begin(&arena, 1); + String8 full_path = {0}; + if (lnx_iter->path.size == 1 && lnx_iter->path.str[0] == '/') { // Handle root case + full_path = push_str8f(scratch.arena, "/%s", d_name); + } else { + full_path = push_str8f(scratch.arena, "%S/%s", lnx_iter->path, d_name); + } + struct stat st = {0}; + // Use stat() to follow symlinks, matching FindFirstFile behavior. + int stat_result = stat((char *)full_path.str, &st); + scratch_end(scratch); + + // 5. Handle stat result and apply filters + if (stat_result != 0) { + // Stat failed (e.g., broken link, permissions). Skip this entry. + // Optional: Log errno here? For now, just skip. + continue; + } + + // Stat succeeded, check filters based on actual type + B32 is_dir = S_ISDIR(st.st_mode); + B32 filtered = 0; + if (is_dir && (iter->flags & OS_FileIterFlag_SkipFolders)) { + filtered = 1; + } else if (!is_dir && (iter->flags & OS_FileIterFlag_SkipFiles)) { + filtered = 1; + } + + if (!filtered) { + // 6. Populate output and break loop + info_out->name = push_str8_copy(arena, str8_cstring(d_name)); + info_out->props = os_lnx_file_properties_from_stat(&st); + good = 1; // Found a valid entry + break; // Exit loop + } + // If filtered, continue the loop to find the next entry + } + } + + // Return true only if a valid item was found *this call* + return good; +} + +internal void +os_file_iter_end(OS_FileIter *iter) +{ + OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; + if (lnx_iter->is_volume_iter) + { + if (lnx_iter->mount_file_stream != 0) + { + endmntent(lnx_iter->mount_file_stream); + lnx_iter->mount_file_stream = 0; + } + } + else + { + if (lnx_iter->dir != 0) + { + closedir(lnx_iter->dir); + lnx_iter->dir = 0; + } + } +} + +//- rjf: directory creation + +internal B32 +os_make_directory(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 result = 0; + String8 path_copy = push_str8_copy(scratch.arena, path); + if(mkdir((char*)path_copy.str, 0755) != -1) + { + result = 1; + } + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ dan: @os_hooks Shared Memory (Implemented Per-OS) + +internal OS_Handle +os_shared_memory_alloc(U64 size, String8 name) +{ + OS_Handle result = os_handle_zero(); // Initialize to zero handle + Temp scratch = scratch_begin(0, 0); + String8 name_copy = push_str8_copy(scratch.arena, name); + + // Use O_CREAT | O_RDWR to create if not exists, or open if it does. + // Use appropriate permissions (e.g., 0666). + int id = shm_open((char *)name_copy.str, O_CREAT | O_RDWR, 0666); + + if (id != -1) // Check if shm_open succeeded + { + // Truncate the file to the desired size. + if (ftruncate(id, size) == 0) // Check if ftruncate succeeded + { + result.u64[0] = (U64)id; // Success path + } + else + { + perror("ftruncate shared memory"); + close(id); // Close the descriptor on ftruncate failure + // Optional: shm_unlink the name if we created it and failed to size it? + // Might be too aggressive if another process already had it open. + // For now, just close and return zero handle. + } + } + else + { + perror("shm_open shared memory"); + // shm_open failed, result remains zero handle + } + + scratch_end(scratch); + return result; +} + +internal OS_Handle +os_shared_memory_open(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String8 name_copy = push_str8_copy(scratch.arena, name); + int id = shm_open((char *)name_copy.str, O_RDWR, 0); + OS_Handle result = {(U64)id}; + scratch_end(scratch); + return result; +} + +internal void +os_shared_memory_close(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())){return;} + int id = (int)handle.u64[0]; + close(id); +} + +internal void * +os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) +{ + if(os_handle_match(handle, os_handle_zero())){return 0;} + int id = (int)handle.u64[0]; + void *base = mmap(0, dim_1u64(range), PROT_READ|PROT_WRITE, MAP_SHARED, id, range.min); + if(base == MAP_FAILED) + { + base = 0; + } + return base; +} + +internal void +os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range) +{ + if(os_handle_match(handle, os_handle_zero())){return;} + munmap(ptr, dim_1u64(range)); + int result = munmap(ptr, dim_1u64(range)); + if (result == -1) { + perror("munmap shared_memory_view_close"); + } +} + +//////////////////////////////// +//~ dan: @os_hooks Time (Implemented Per-OS) + +internal U64 +os_now_microseconds(void) +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); + return result; +} + +internal U32 +os_now_unix(void) +{ + time_t t = time(0); + return (U32)t; +} + +internal DateTime +os_now_universal_time(void) +{ + time_t t = 0; + time(&t); + struct tm universal_tm = {0}; + gmtime_r(&t, &universal_tm); + DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); + return result; +} + +internal DateTime +os_universal_time_from_local(DateTime *date_time) +{ + // local DateTime -> universal time_t + tm local_tm = os_lnx_tm_from_date_time(*date_time); + local_tm.tm_isdst = -1; + time_t universal_t = mktime(&local_tm); + + // universal time_t -> DateTime + tm universal_tm = {0}; + gmtime_r(&universal_t, &universal_tm); + DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); + return result; +} + +internal DateTime +os_local_time_from_universal(DateTime *date_time) +{ + // universal DateTime -> local time_t + tm universal_tm = os_lnx_tm_from_date_time(*date_time); + universal_tm.tm_isdst = -1; + time_t universal_t = timegm(&universal_tm); + tm local_tm = {0}; + localtime_r(&universal_t, &local_tm); + + // local tm -> DateTime + DateTime result = os_lnx_date_time_from_tm(local_tm, 0); + return result; +} + +internal void +os_sleep_milliseconds(U32 msec) +{ + usleep(msec*Thousand(1)); +} + +//////////////////////////////// +//~ dan: @os_hooks Child Processes (Implemented Per-OS) + +internal OS_Handle +os_process_launch(OS_ProcessLaunchParams *params) +{ + OS_Handle result = {0}; + Temp scratch = scratch_begin(0, 0); + + pid_t pid = fork(); + + if (pid == -1) + { + // Fork failed + perror("fork"); + } + else if (pid == 0) + { + // Child process + + // Redirect stdout if requested + if (!os_handle_match(params->stdout_file, os_handle_zero())) + { + int fd = (int)params->stdout_file.u64[0]; + if (dup2(fd, STDOUT_FILENO) == -1) { perror("dup2 stdout"); _exit(1); } + // Close the original fd in child, not strictly necessary as exec replaces fds, + // but good practice if exec fails. If Inherited flag was set, this might + // close the parent's handle too if not careful about handle inheritance flags + // during file open. Assuming non-inherited handles for simplicity here. + // close(fd); // Let's skip closing for now, relies on correct OS_AccessFlag_Inherited usage + } + + // Redirect stderr if requested + if (!os_handle_match(params->stderr_file, os_handle_zero())) + { + int fd = (int)params->stderr_file.u64[0]; + if (dup2(fd, STDERR_FILENO) == -1) { perror("dup2 stderr"); _exit(1); } + // close(fd); + } + + // Redirect stdin if requested + if (!os_handle_match(params->stdin_file, os_handle_zero())) + { + int fd = (int)params->stdin_file.u64[0]; + if (dup2(fd, STDIN_FILENO) == -1) { perror("dup2 stdin"); _exit(1); } + // close(fd); + } + + // Change directory if requested + if (params->path.size > 0) + { + String8 path_copy = push_str8_copy(scratch.arena, params->path); + if (chdir((char *)path_copy.str) == -1) + { + perror("chdir"); + _exit(1); + } + } + + // Prepare command line arguments + char **argv = push_array(scratch.arena, char *, params->cmd_line.node_count + 1); + U64 argv_idx = 0; + for(String8Node *n = params->cmd_line.first; n != 0; n = n->next) + { + String8 arg_copy = push_str8_copy(scratch.arena, n->string); + argv[argv_idx++] = (char *)arg_copy.str; + } + argv[argv_idx] = NULL; + + // Prepare environment + char **envp = NULL; + if (params->inherit_env == 0 && params->env.node_count > 0) + { + // Use only specified environment variables + envp = push_array(scratch.arena, char *, params->env.node_count + 1); + U64 envp_idx = 0; + for (String8Node *n = params->env.first; n != 0; n = n->next) + { + String8 env_copy = push_str8_copy(scratch.arena, n->string); + envp[envp_idx++] = (char *)env_copy.str; + } + envp[envp_idx] = NULL; + } + else if (params->inherit_env != 0 && params->env.node_count > 0) + { + // Inherit existing environment and append/overwrite specified ones + extern char **environ; + String8List merged_env_list = {0}; + + // Pass 1: Add inherited variables if they are not overridden by custom ones. + if (environ) + { + for (char **existing_env = environ; *existing_env; ++existing_env) + { + String8 existing_var = str8_cstring(*existing_env); + U64 eq_pos_existing = str8_find_needle(existing_var, 0, str8_lit("="), 0); + String8 existing_key = str8_prefix(existing_var, eq_pos_existing); + B32 overwritten = 0; + + // Check if this key exists in the custom environment variables + for (String8Node *n = params->env.first; n != 0; n = n->next) + { + String8 custom_var = n->string; + U64 eq_pos_custom = str8_find_needle(custom_var, 0, str8_lit("="), 0); + String8 custom_key = str8_prefix(custom_var, eq_pos_custom); + if (str8_match(existing_key, custom_key, 0)) { + overwritten = 1; + break; + } + } + + if (!overwritten) + { + // Add the inherited variable to the list + str8_list_push(scratch.arena, &merged_env_list, existing_var); + } + } + } + + // Pass 2: Add all custom variables (handles appending new and overwriting existing) + for (String8Node *n = params->env.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &merged_env_list, n->string); + } + + // Convert the list to the final envp array + envp = push_array(scratch.arena, char *, merged_env_list.node_count + 1); + U64 envp_idx = 0; + for (String8Node *n = merged_env_list.first; n != 0; n = n->next) + { + // Need to copy the strings again as they might point to transient memory (environ) + String8 env_copy = push_str8_copy(scratch.arena, n->string); + envp[envp_idx++] = (char *)env_copy.str; + } + envp[envp_idx] = NULL; // Null-terminate the list + } + else if (params->inherit_env == 0 && params->env.node_count == 0) + { + // Use minimal empty environment + envp = push_array(scratch.arena, char *, 1); + envp[0] = NULL; + } + else // inherit_env == 1 and params->env.node_count == 0 + { + // Inherit existing environment (default behavior of execvp) + extern char **environ; + envp = environ; + } + + // Execute the command using execvpe which allows specifying environment + execvpe(argv[0], argv, envp); + + // execvpe only returns on error + perror("execvpe"); + _exit(127); // Indicate exec failed + } + else + { + // Parent process + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Process); + if (entity) + { + entity->process.pid = pid; + result.u64[0] = (U64)entity; + } + else + { + // Failed to allocate entity, kill the child? Or let it become a zombie? + // Let's just return zero handle for now. + kill(pid, SIGKILL); // Or SIGTERM + waitpid(pid, NULL, 0); // Reap the killed child + } + } + + scratch_end(scratch); + return result; +} + +internal B32 +os_process_join(OS_Handle handle, U64 endt_us) +{ + if (os_handle_match(handle, os_handle_zero())) { return 0; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; + if (entity == 0 || entity->kind != OS_LNX_EntityKind_Process) { return 0; } + + pid_t pid = entity->process.pid; + B32 joined = 0; + + for (;;) + { + int status; + pid_t result = waitpid(pid, &status, WNOHANG); + + if (result == pid) + { + // Process exited or was signalled + joined = 1; + break; + } + else if (result == 0) + { + // Process still running + U64 now_us = os_now_microseconds(); + if (now_us >= endt_us) + { + // Timeout reached + break; + } + + // Calculate sleep time + U64 remaining_us = endt_us - now_us; + U32 sleep_ms = (U32)(remaining_us / 1000); + // Prevent sleeping for too long if timeout is very close, minimum sleep 1ms + sleep_ms = Max(1, Min(sleep_ms, 100)); + os_sleep_milliseconds(sleep_ms); + } + else + { + // Error in waitpid (e.g., process doesn't exist, permissions issue) + perror("waitpid"); + break; + } + } + + if (joined) + { + os_lnx_entity_release(entity); + } + + return joined; +} + +internal void +os_process_detach(OS_Handle handle) +{ + if (os_handle_match(handle, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; + if (entity != 0 && entity->kind == OS_LNX_EntityKind_Process) + { + // We just release our tracking entity. The OS will handle the actual + // process becoming an orphan or being re-parented to init (pid 1). + os_lnx_entity_release(entity); + } +} + +//////////////////////////////// +//~ dan: @os_hooks Threads (Implemented Per-OS) + +internal OS_Handle +os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Thread); + entity->thread.func = func; + entity->thread.ptr = ptr; + { + int pthread_result = pthread_create(&entity->thread.handle, 0, os_lnx_thread_entry_point, entity); + if(pthread_result == -1) + { + os_lnx_entity_release(entity); + entity = 0; + } + } + OS_Handle handle = {(U64)entity}; + return handle; +} + +internal B32 +os_thread_join(OS_Handle handle, U64 endt_us) +{ + if(os_handle_match(handle, os_handle_zero())) { return 0; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; + B32 result = 0; + int join_result = 0; + + if (endt_us == max_U64) + { + // Infinite wait + join_result = pthread_join(entity->thread.handle, 0); + } + else + { + // Timed wait using absolute deadline + struct timespec ts_deadline; + ts_deadline.tv_sec = endt_us / Million(1); + ts_deadline.tv_nsec = (endt_us % Million(1)) * Thousand(1); + + // NOTE: Requires _GNU_SOURCE, which is defined in os_core_linux.h + join_result = pthread_timedjoin_np(entity->thread.handle, 0, &ts_deadline); + } + + // Join succeeded if return code is 0. ETIMEDOUT indicates timeout. + result = (join_result == 0); + + // Release the entity regardless of join success, similar to Win32 closing the handle. + // The caller should not attempt to join again. + os_lnx_entity_release(entity); + return result; +} + +internal void +os_thread_detach(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; + os_lnx_entity_release(entity); +} + +//////////////////////////////// +//~ dan: @os_hooks Synchronization Primitives (Implemented Per-OS) + +//- rjf: mutexes + +internal OS_Handle +os_mutex_alloc(void) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Mutex); + // Initialization is now done in os_lnx_entity_alloc + if(entity == 0) // Check if allocation failed + { + return os_handle_zero(); + } + OS_Handle handle = {(U64)entity}; + return handle; +} + +internal void +os_mutex_release(OS_Handle mutex) +{ + if(os_handle_match(mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + // No explicit destruction needed for atomics + os_lnx_entity_release(entity); +} + +internal void +os_mutex_take(OS_Handle mutex) +{ + if(os_handle_match(mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + U32 tid = os_tid(); + + // Check for recursion + if (atomic_load_explicit(&entity->mutex.owner_tid, memory_order_relaxed) == tid) { + atomic_fetch_add_explicit(&entity->mutex.recursion_depth, 1, memory_order_relaxed); + return; + } + + // Try to acquire the lock uncontended + unsigned int zero = 0; + if (atomic_compare_exchange_strong_explicit(&entity->mutex.futex, &zero, 1, memory_order_acquire, memory_order_relaxed)) + { + // Acquired uncontended + atomic_store_explicit(&entity->mutex.owner_tid, tid, memory_order_relaxed); + atomic_store_explicit(&entity->mutex.recursion_depth, 1, memory_order_relaxed); + return; + } + + // Lock is contended, prepare to wait + do { + // Mark as contended if it's currently locked uncontended (state 1) + unsigned int one = 1; + if (atomic_compare_exchange_strong_explicit(&entity->mutex.futex, &one, 2, memory_order_relaxed, memory_order_relaxed)) + { + // Successfully marked as contended, now wait + os_lnx_futex_wait(&entity->mutex.futex, 2, NULL); // Wait indefinitely if it's 2 (contended) + } + + // After waking up or if it was already contended (state 2), try to acquire again. + // We need to transition from 0 (unlocked) to 2 (contended lock) because other waiters might exist. + zero = 0; + } while (!atomic_compare_exchange_strong_explicit(&entity->mutex.futex, &zero, 2, memory_order_acquire, memory_order_relaxed)); + + // Acquired contended lock + atomic_store_explicit(&entity->mutex.owner_tid, tid, memory_order_relaxed); + atomic_store_explicit(&entity->mutex.recursion_depth, 1, memory_order_relaxed); +} + +internal void +os_mutex_drop(OS_Handle mutex) +{ + if(os_handle_match(mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + U32 tid = os_tid(); + + // Check ownership (optional but good practice) + Assert(atomic_load_explicit(&entity->mutex.owner_tid, memory_order_relaxed) == tid); + + // Decrement recursion depth + unsigned int rec_depth = atomic_fetch_sub_explicit(&entity->mutex.recursion_depth, 1, memory_order_relaxed); + + // If recursion depth > 1 before decrement, we're done + if (rec_depth > 1) { + return; + } + + // Reset owner TID as we are fully unlocking + atomic_store_explicit(&entity->mutex.owner_tid, 0, memory_order_relaxed); + + // Release the lock + // Transition from 2 (contended) or 1 (uncontended) to 0 (unlocked) + if (atomic_exchange_explicit(&entity->mutex.futex, 0, memory_order_release) == 2) + { + // If it was contended (state 2), wake one waiting thread + os_lnx_futex_wake(&entity->mutex.futex, 1); + } +} + +//- rjf: reader/writer mutexes + +internal OS_Handle +os_rw_mutex_alloc(void) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_RWMutex); + // Initialization is now done in os_lnx_entity_alloc + if(entity == 0) // Check if allocation failed + { + return os_handle_zero(); + } + OS_Handle handle = {(U64)entity}; + return handle; +} + +internal void +os_rw_mutex_release(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + // No explicit destruction needed for atomics + os_lnx_entity_release(entity); +} + +internal void +os_rw_mutex_take_r(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + atomic_uint *futex = &entity->rw_mutex.futex; + + unsigned int current_state = atomic_load_explicit(futex, memory_order_relaxed); + while (1) { + // Check if a writer holds the lock or writers are waiting (writer preference) + if (current_state & (RW_MUTEX_WRITE_HELD_MASK | RW_MUTEX_WRITE_WAIT_MASK)) { + // Mark that readers are waiting + unsigned int expected = current_state; + unsigned int desired = current_state | RW_MUTEX_READ_WAIT_MASK; + // Attempt to set the wait flag, weak is fine as we'll re-check and wait anyway + atomic_compare_exchange_weak_explicit(futex, &expected, desired, memory_order_relaxed, memory_order_relaxed); + // Use the potentially updated state (expected) for waiting + current_state = expected | RW_MUTEX_READ_WAIT_MASK; // Assume wait flag is now set for the wait condition + + // Wait if still necessary (writer held or waiting) + os_lnx_futex_wait(futex, current_state, NULL); + + // Reload state after wake and retry loop + current_state = atomic_load_explicit(futex, memory_order_relaxed); + continue; + } + + // Attempt to increment reader count + unsigned int new_state = current_state + 1; + // Check for reader count overflow (highly unlikely with 16 bits) + if ((new_state & RW_MUTEX_READ_MASK) == 0) { + current_state = atomic_load_explicit(futex, memory_order_relaxed); // Reload state on overflow attempt + continue; + } + + // Try acquiring the read lock using weak CAS + if (atomic_compare_exchange_weak_explicit(futex, ¤t_state, new_state, memory_order_acquire, memory_order_relaxed)) { + // Successfully acquired read lock + break; + } + // CAS failed, loop continues with updated current_state (automatically loaded by failed CAS) + } +} + +internal void +os_rw_mutex_drop_r(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + atomic_uint *futex = &entity->rw_mutex.futex; + + // Decrement reader count and get the state *before* decrementing + unsigned int prev_state = atomic_fetch_sub_explicit(futex, 1, memory_order_release); + + unsigned int readers_before = prev_state & RW_MUTEX_READ_MASK; + unsigned int writers_waiting = prev_state & RW_MUTEX_WRITE_WAIT_MASK; + + // If this was the last reader AND writers are waiting + if (readers_before == 1 && writers_waiting) { + // Wake one potential writer. The woken writer will handle clearing the wait flag if it acquires the lock. + os_lnx_futex_wake(futex, 1); + } +} + +internal void +os_rw_mutex_take_w(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + atomic_uint *futex = &entity->rw_mutex.futex; + + unsigned int current_state = atomic_load_explicit(futex, memory_order_relaxed); + while (1) { + if (current_state == 0) { // Lock is free + // Try to acquire uncontended using weak CAS + if (atomic_compare_exchange_weak_explicit(futex, ¤t_state, RW_MUTEX_WRITE_HELD_MASK, memory_order_acquire, memory_order_relaxed)) { + return; // Acquired write lock + } + // CAS failed, loop continues with updated current_state + continue; + } + + // Lock is not free (readers exist, writer holds, or waiters exist) + unsigned int expected = current_state; + unsigned int desired = current_state | RW_MUTEX_WRITE_WAIT_MASK; + + // Mark that a writer is waiting, if not already marked + if (!(current_state & RW_MUTEX_WRITE_WAIT_MASK)) { + atomic_compare_exchange_weak_explicit(futex, &expected, desired, memory_order_relaxed, memory_order_relaxed); + // Use the potentially updated state for the wait check + current_state = expected | RW_MUTEX_WRITE_WAIT_MASK; + } else { + current_state = expected; // Use the state loaded or from failed CAS + } + + // Wait if the lock is held by readers or another writer + if (current_state & (RW_MUTEX_READ_MASK | RW_MUTEX_WRITE_HELD_MASK)) { + os_lnx_futex_wait(futex, current_state, NULL); // Wait on the current state + // Reload state after wake and retry loop + current_state = atomic_load_explicit(futex, memory_order_relaxed); + } else { + // Lock is not held (only waiter flags might be set). Try to acquire. + expected = current_state & ~RW_MUTEX_WRITE_HELD_MASK; // Expect lock not held + desired = (expected | RW_MUTEX_WRITE_HELD_MASK) & ~RW_MUTEX_WRITE_WAIT_MASK; // Set HELD, clear our WAIT bit + + if (atomic_compare_exchange_weak_explicit(futex, &expected, desired, memory_order_acquire, memory_order_relaxed)) { + // Acquired write lock + return; + } + // CAS failed, retry loop with updated current_state (expected) + current_state = expected; + } + } +} + +internal void +os_rw_mutex_drop_w(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + atomic_uint *futex = &entity->rw_mutex.futex; + + // Atomically clear the write-held flag, get the state *before* clearing. + unsigned int prev_state = atomic_fetch_and_explicit(futex, ~RW_MUTEX_WRITE_HELD_MASK, memory_order_release); + + // Check wait flags from the previous state to decide who to wake. + if (prev_state & RW_MUTEX_WRITE_WAIT_MASK) { + // Writers were waiting. Wake one. + os_lnx_futex_wake(futex, 1); + } else if (prev_state & RW_MUTEX_READ_WAIT_MASK) { + // No writers waiting, but readers were. Try to clear the read wait flag and wake all readers. + unsigned int current_state_after_drop = atomic_load_explicit(futex, memory_order_relaxed); + while(current_state_after_drop & RW_MUTEX_READ_WAIT_MASK) { + unsigned int desired = current_state_after_drop & ~RW_MUTEX_READ_WAIT_MASK; + if(atomic_compare_exchange_weak_explicit(futex, ¤t_state_after_drop, desired, memory_order_relaxed, memory_order_relaxed)) { + break; // Flag cleared + } + // retry if CAS failed, current_state_after_drop is updated by CAS + } + os_lnx_futex_wake(futex, INT_MAX); // Wake all potentially waiting readers + } +} + +//- rjf: condition variables + +internal OS_Handle +os_condition_variable_alloc(void) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_ConditionVariable); + // Initialization is now done in os_lnx_entity_alloc + if(entity == 0) // Check if allocation failed + { + return os_handle_zero(); + } + OS_Handle handle = {(U64)entity}; + return handle; +} + +internal void +os_condition_variable_release(OS_Handle cv) +{ + if(os_handle_match(cv, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)cv.u64[0]; + // No explicit destruction needed for atomics + os_lnx_entity_release(entity); +} + +internal B32 +os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us) +{ + if(os_handle_match(cv, os_handle_zero())) { return 0; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + OS_LNX_Entity *mutex_entity = (OS_LNX_Entity *)mutex.u64[0]; + struct timespec endt_timespec; + endt_timespec.tv_sec = endt_us/Million(1); + endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); + + // 1. Read the current sequence number + unsigned int current_seq = atomic_load_explicit(&cv_entity->cv.futex, memory_order_relaxed); + + // 2. Unlock the associated *external* mutex (must be done by caller before wait) + // IMPORTANT: The API expects the mutex passed here is the one protecting the condition. + // The original code's use of rwlock_mutex_handle inside the CV entity was incorrect. + os_mutex_drop(mutex); // Release the *external* mutex + + // 3. Wait on the futex, checking if the sequence number has changed + int wait_result = os_lnx_futex_wait(&cv_entity->cv.futex, current_seq, &endt_timespec); + + // 4. Re-lock the external mutex (must be done by caller *after* wait returns) + os_mutex_take(mutex); + + // 5. Determine result + B32 result = (wait_result != -1 || errno != ETIMEDOUT); + // Futex wait returns 0 on success (woken), -1 on error. Check errno for timeout. + return result; +} + +internal B32 +os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + if(os_handle_match(cv, os_handle_zero()) || os_handle_match(mutex_rw, os_handle_zero())) { return 0; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + OS_LNX_Entity *rw_mutex_entity = (OS_LNX_Entity *)mutex_rw.u64[0]; // Assuming mutex_rw is the RW mutex handle + + if (cv_entity == 0 || cv_entity->kind != OS_LNX_EntityKind_ConditionVariable || + rw_mutex_entity == 0 || rw_mutex_entity->kind != OS_LNX_EntityKind_RWMutex) + { + // Invalid handle types + return 0; + } + + struct timespec *timeout_ts_ptr = 0; // Default to infinite wait + struct timespec timeout_ts; + if (endt_us != max_U64) + { + // Check for immediate timeout + U64 now_us = os_now_microseconds(); + if (now_us >= endt_us) { + errno = ETIMEDOUT; // Set errno for timeout + return 0; // Timeout already expired + } + // Use monotonic clock for absolute timeout + timeout_ts.tv_sec = endt_us / Million(1); + timeout_ts.tv_nsec = (endt_us % Million(1)) * 1000; + timeout_ts_ptr = &timeout_ts; + } + + // Similar logic to normal CV wait, but using the RW lock + unsigned int current_seq = atomic_load_explicit(&cv_entity->cv.futex, memory_order_relaxed); + + // Unlock the *external* RW lock (Read mode) + os_rw_mutex_drop_r(mutex_rw); + + // Wait on the futex using the sequence number and timeout + int wait_result = os_lnx_futex_wait(&cv_entity->cv.futex, current_seq, timeout_ts_ptr); + + // Re-lock the *external* RW lock (Read mode) + os_rw_mutex_take_r(mutex_rw); + + // Check result: 0 means woken up, -1 means error. Check errno for timeout. + B32 result = (wait_result == 0); + if (wait_result == -1 && errno == ETIMEDOUT) { + result = 0; // Explicitly return false on timeout + } else if (wait_result == -1) { + // Some other error occurred during wait + perror("os_lnx_futex_wait in os_condition_variable_wait_r"); + result = 0; // Consider other errors as failure + } + + return result; +} + +internal B32 +os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + if(os_handle_match(cv, os_handle_zero()) || os_handle_match(mutex_rw, os_handle_zero())) { return 0; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + OS_LNX_Entity *rw_mutex_entity = (OS_LNX_Entity *)mutex_rw.u64[0]; // Assuming mutex_rw is the RW mutex handle + + if (cv_entity == 0 || cv_entity->kind != OS_LNX_EntityKind_ConditionVariable || + rw_mutex_entity == 0 || rw_mutex_entity->kind != OS_LNX_EntityKind_RWMutex) + { + // Invalid handle types + return 0; + } + + struct timespec *timeout_ts_ptr = 0; // Default to infinite wait + struct timespec timeout_ts; + if (endt_us != max_U64) + { + // Check for immediate timeout + U64 now_us = os_now_microseconds(); + if (now_us >= endt_us) { + errno = ETIMEDOUT; // Set errno for timeout + return 0; // Timeout already expired + } + // Use monotonic clock for absolute timeout + timeout_ts.tv_sec = endt_us / Million(1); + timeout_ts.tv_nsec = (endt_us % Million(1)) * 1000; + timeout_ts_ptr = &timeout_ts; + } + + // Similar logic to normal CV wait, but using the RW lock + unsigned int current_seq = atomic_load_explicit(&cv_entity->cv.futex, memory_order_relaxed); + + // Unlock the *external* RW lock (Write mode) + os_rw_mutex_drop_w(mutex_rw); + + // Wait on the futex using the sequence number and timeout + int wait_result = os_lnx_futex_wait(&cv_entity->cv.futex, current_seq, timeout_ts_ptr); + + // Re-lock the *external* RW lock (Write mode) + os_rw_mutex_take_w(mutex_rw); + + // Check result: 0 means woken up, -1 means error. Check errno for timeout. + B32 result = (wait_result == 0); + if (wait_result == -1 && errno == ETIMEDOUT) { + result = 0; // Explicitly return false on timeout + } else if (wait_result == -1) { + // Some other error occurred during wait + perror("os_lnx_futex_wait in os_condition_variable_wait_w"); + result = 0; // Consider other errors as failure + } + + return result; +} + +internal void +os_condition_variable_signal(OS_Handle cv) +{ + if(os_handle_match(cv, os_handle_zero())) { return; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + // Increment sequence number to mark a change + atomic_fetch_add_explicit(&cv_entity->cv.futex, 1, memory_order_relaxed); + // Wake one waiting thread + os_lnx_futex_wake(&cv_entity->cv.futex, 1); +} + +internal void +os_condition_variable_broadcast(OS_Handle cv) +{ + if(os_handle_match(cv, os_handle_zero())) { return; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + // Increment sequence number + atomic_fetch_add_explicit(&cv_entity->cv.futex, 1, memory_order_relaxed); + // Wake all waiting threads + os_lnx_futex_wake(&cv_entity->cv.futex, INT_MAX); // INT_MAX wakes all +} + +//- rjf: cross-process semaphores + +internal OS_Handle +os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) +{ + // NOTE(rjf): max_count ignored on linux - POSIX semaphores don't have a max count concept + // like Windows semaphores do. We clamp initial_count to SEM_VALUE_MAX. + if (initial_count > SEM_VALUE_MAX) + { + initial_count = SEM_VALUE_MAX; + } + + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Semaphore); + if (!entity) + { + return os_handle_zero(); + } + + if (name.size > 0) + { + // Named Semaphore + entity->semaphore.is_named = 1; + Temp scratch = scratch_begin(0, 0); // Use scratch for temporary name manipulation + + // POSIX requires named semaphores to start with '/' + String8 name_copy = name; + if (name.str[0] != '/') + { + name_copy = push_str8f(scratch.arena, "/%S", name); + } + + // Store the final name (potentially prefixed) in the entity arena + entity->semaphore.name = push_str8_copy(os_lnx_state.entity_arena, name_copy); + + // O_EXCL ensures we create a new one or fail if it exists + entity->semaphore.named_handle = sem_open((char *)entity->semaphore.name.str, O_CREAT | O_EXCL | O_RDWR, 0666, initial_count); + scratch_end(scratch); + + if (entity->semaphore.named_handle == SEM_FAILED) + { + perror("sem_open (alloc)"); + // Arena free for name happens when entity is released + os_lnx_entity_release(entity); + return os_handle_zero(); + } + } + else + { + // Unnamed Semaphore (process-local) + entity->semaphore.is_named = 0; + entity->semaphore.name = str8_zero(); + entity->semaphore.named_handle = SEM_FAILED; // Explicitly mark named handle as invalid + + // Initialize the embedded unnamed semaphore (pshared = 0 for process-local) + int err = sem_init(&entity->semaphore.unnamed_handle, 0, initial_count); + if (err != 0) + { + perror("sem_init"); + os_lnx_entity_release(entity); + return os_handle_zero(); + } + } + + OS_Handle result = {(U64)entity}; + return result; +} + +internal void +os_semaphore_release(OS_Handle semaphore) +{ + if (os_handle_match(semaphore, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)semaphore.u64[0]; + if (entity == 0 || entity->kind != OS_LNX_EntityKind_Semaphore) { return; } + + if (entity->semaphore.is_named) + { + if (entity->semaphore.named_handle != SEM_FAILED) + { + sem_close(entity->semaphore.named_handle); + // NOTE(improvement): Removed sem_unlink. Release should only close the handle + // for named semaphores, similar to Win32 CloseHandle, allowing other + // openers to continue using it. Unlinking should be a separate concern + // if needed (e.g., a dedicated destroy function or manual cleanup). + // Name string is freed when entity_arena is managed + } + } + else + { + // Destroy the embedded unnamed semaphore + sem_destroy(&entity->semaphore.unnamed_handle); + } + + os_lnx_entity_release(entity); +} + +internal OS_Handle +os_semaphore_open(String8 name) +{ + if (name.size == 0) { return os_handle_zero(); } // Named semaphores must have a name + + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Semaphore); + if (!entity) + { + return os_handle_zero(); + } + + entity->semaphore.is_named = 1; + Temp scratch = scratch_begin(0, 0); + + String8 name_copy = name; + if (name.str[0] != '/') + { + name_copy = push_str8f(scratch.arena, "/%S", name); + } + entity->semaphore.name = push_str8_copy(os_lnx_state.entity_arena, name_copy); + + // Open existing named semaphore + entity->semaphore.named_handle = sem_open((char *)entity->semaphore.name.str, O_RDWR); + scratch_end(scratch); + + if (entity->semaphore.named_handle == SEM_FAILED) + { + perror("sem_open (open)"); + // Arena free for name happens when entity is released + os_lnx_entity_release(entity); + return os_handle_zero(); + } + + OS_Handle result = {(U64)entity}; + return result; +} + +internal void +os_semaphore_close(OS_Handle semaphore) +{ + // This function behaves similarly to release for named semaphores, + // but doesn't unlink the name or destroy unnamed semaphores. + // It just closes the handle/connection for this process. + if (os_handle_match(semaphore, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)semaphore.u64[0]; + if (entity == 0 || entity->kind != OS_LNX_EntityKind_Semaphore) { return; } + + if (entity->semaphore.is_named && entity->semaphore.named_handle != SEM_FAILED) + { + sem_close(entity->semaphore.named_handle); + // Do not unlink here, just close the process's handle to it. + } + // For unnamed semaphores, there's no "close" separate from destroy/release. + + // Release the entity tracking structure itself. + os_lnx_entity_release(entity); +} + +internal B32 +os_semaphore_take(OS_Handle semaphore, U64 endt_us) +{ + if (os_handle_match(semaphore, os_handle_zero())) { return 0; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)semaphore.u64[0]; + if (entity == 0 || entity->kind != OS_LNX_EntityKind_Semaphore) { return 0; } + + sem_t *sem_handle = entity->semaphore.is_named ? entity->semaphore.named_handle : &entity->semaphore.unnamed_handle; + if (entity->semaphore.is_named && sem_handle == SEM_FAILED) { return 0; } // Check named handle validity + + int err = 0; + + if (endt_us == max_U64) + { + // Infinite wait + while ((err = sem_wait(sem_handle)) == -1 && errno == EINTR) + { + continue; // Restart if interrupted by handler + } + } + else + { + // Timed wait using monotonic clock + struct timespec ts_deadline; + U64 now_us = os_now_microseconds(); + + if (now_us >= endt_us) { + // Timeout already expired, try once non-blockingly + err = sem_trywait(sem_handle); + if (err == -1 && errno == EAGAIN) { // EAGAIN means would block + errno = ETIMEDOUT; // Report as timeout + } + } else { + // Calculate absolute deadline based on CLOCK_MONOTONIC + ts_deadline.tv_sec = endt_us / Million(1); + ts_deadline.tv_nsec = (endt_us % Million(1)) * 1000; + + // Call sem_clockwait with the absolute monotonic deadline + // Requires _POSIX_TIMEOUTS and _POSIX_MONOTONIC_CLOCK, enabled by _GNU_SOURCE + while ((err = sem_clockwait(sem_handle, CLOCK_MONOTONIC, &ts_deadline)) == -1 && errno == EINTR) + { + continue; // Restart if interrupted by handler + } + } + } + + if (err == 0) + { + return 1; // Success + } + else + { + // ETIMEDOUT is expected on timeout, other errors are reported. + if (errno != ETIMEDOUT) { + // Updated error message context + perror("sem_wait/sem_trywait/sem_clockwait"); + } + return 0; // Timeout or other error + } +} + +internal void +os_semaphore_drop(OS_Handle semaphore) +{ + if (os_handle_match(semaphore, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)semaphore.u64[0]; + if (entity == 0 || entity->kind != OS_LNX_EntityKind_Semaphore) { return; } + + sem_t *sem_handle = entity->semaphore.is_named ? entity->semaphore.named_handle : &entity->semaphore.unnamed_handle; + if (entity->semaphore.is_named && sem_handle == SEM_FAILED) { return; } // Check named handle validity + + int err = 0; + + // sem_post is generally non-blocking unless SEM_VALUE_MAX is reached (on some systems) + while ((err = sem_post(sem_handle)) == -1 && errno == EINTR) + { + continue; // Restart if interrupted by handler + } + + if (err != 0) { + // SEM_OVF is the most likely error if max value is exceeded, + // but other errors are possible. + perror("sem_post"); + } +} + +//////////////////////////////// +//~ dan: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) + +internal OS_Handle +os_library_open(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + char *path_cstr = (char *)push_str8_copy(scratch.arena, path).str; + void *so = dlopen(path_cstr, RTLD_LAZY|RTLD_LOCAL); + OS_Handle lib = { (U64)so }; + scratch_end(scratch); + return lib; +} + +internal VoidProc* +os_library_load_proc(OS_Handle lib, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + void *so = (void *)lib.u64; + char *name_cstr = (char *)push_str8_copy(scratch.arena, name).str; + VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); + scratch_end(scratch); + return proc; +} + +internal void +os_library_close(OS_Handle lib) +{ + void *so = (void *)lib.u64; + dlclose(so); +} + +//////////////////////////////// +//~ dan: @os_hooks Safe Calls (Implemented Per-OS) + +internal void +os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr) +{ + // Allocate an entity for this safe call frame + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_SafeCallChain); + if (!entity) { + // Allocation failed, cannot proceed safely. Maybe abort? + os_abort(-1); + return; + } + + // Store parameters and link to previous chain node + entity->safe_call_chain.fail_handler = fail_handler; + entity->safe_call_chain.ptr = ptr; + entity->safe_call_chain.caller_chain_node = os_lnx_safe_call_chain; + entity->safe_call_chain.num_signals_handled = 0; + + // Push this frame onto the thread-local chain + os_lnx_safe_call_chain = entity; + + // Save current signal mask + sigset_t original_mask; + pthread_sigmask(SIG_BLOCK, NULL, &original_mask); + + // Set up signal handlers for critical errors + struct sigaction new_action = {0}; + new_action.sa_handler = os_lnx_safe_call_sig_handler; // The handler now just jumps + sigfillset(&new_action.sa_mask); // Block all signals within the handler + new_action.sa_flags = SA_NODEFER; // Don't block the signal itself in the handler + + int signals_to_handle[] = { + SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGABRT, SIGTRAP + }; + const int num_signals = ArrayCount(signals_to_handle); + + for(int i = 0; i < num_signals; ++i) + { + int sig = signals_to_handle[i]; + if (sigaction(sig, &new_action, &entity->safe_call_chain.original_actions[i]) == 0) + { + entity->safe_call_chain.signals_handled[entity->safe_call_chain.num_signals_handled] = sig; + entity->safe_call_chain.num_signals_handled++; + } + // TODO(rjf): Consider logging failure to set a handler? + } + + // Use sigsetjmp to save the context. Returns 0 on initial call. + int jmp_result = sigsetjmp(entity->safe_call_chain.jmp_buf, 1); // Save signal mask + + if (jmp_result == 0) + { + // Initial call: execute the user function + func(ptr); + } + else + { + // Jumped back from signal handler (jmp_result is the signal number) + // Call the fail handler *after* unwinding from the signal context. + if(entity->safe_call_chain.fail_handler != 0) + { + entity->safe_call_chain.fail_handler(entity->safe_call_chain.ptr); + } + else + { + // Default behavior if no fail handler: abort? + fprintf(stderr, "Unhandled fatal signal %d in os_safe_call, aborting.\n", jmp_result); + os_abort(jmp_result); // Exit with signal number + } + } + + // Cleanup: Restore original signal handlers + for(int i = 0; i < entity->safe_call_chain.num_signals_handled; ++i) + { + int sig = entity->safe_call_chain.signals_handled[i]; + sigaction(sig, &entity->safe_call_chain.original_actions[i], NULL); + } + + // Restore original signal mask + pthread_sigmask(SIG_SETMASK, &original_mask, NULL); + + // Pop this frame from the thread-local chain + os_lnx_safe_call_chain = entity->safe_call_chain.caller_chain_node; + + // Release the entity for this frame + os_lnx_entity_release(entity); +} + +//////////////////////////////// +//~ dan: @os_hooks GUIDs (Implemented Per-OS) + +internal Guid +os_make_guid(void) +{ + Guid guid = {0}; + getrandom(guid.v, sizeof(guid.v), 0); + guid.data3 &= 0x0fff; + guid.data3 |= (4 << 12); + guid.data4[0] &= 0x3f; + guid.data4[0] |= 0x80; + return guid; +} + +//////////////////////////////// +//~ dan: Signal Handler for Safe Calls + +internal void +os_lnx_safe_call_sig_handler(int sig) +{ + // This handler should be minimal and async-signal-safe. + // It jumps back to the corresponding os_safe_call frame. + if(os_lnx_safe_call_chain != 0) + { + // Restore the original handler for this signal *before* longjmping + // to prevent potential re-entry issues if the signal occurs again + // immediately after the jump. Find the original action for `sig`. + OS_LNX_Entity *current_frame = os_lnx_safe_call_chain; + for(int i = 0; i < current_frame->safe_call_chain.num_signals_handled; ++i) + { + if(current_frame->safe_call_chain.signals_handled[i] == sig) + { + sigaction(sig, ¤t_frame->safe_call_chain.original_actions[i], NULL); + break; // Found and restored + } + } + // Now jump back. Pass the signal number as the return value. + siglongjmp(current_frame->safe_call_chain.jmp_buf, sig); + } + else + { + // Should not happen if os_safe_call is used correctly. + // If it does, we have no context to jump back to. Abort. + fprintf(stderr, "Fatal signal %d outside of os_safe_call context! Aborting.\n", sig); + // Reset to default handler and re-raise? Or just exit? + signal(sig, SIG_DFL); + raise(sig); + _exit(sig); // Force exit if raise doesn't terminate + } +} + +//////////////////////////////// +//~ dan: @os_hooks Entry Points (Implemented Per-OS) + +int +main(int argc, char **argv) +{ + //- rjf: set up OS layer + { + //- rjf: get statically-allocated system/process info + { + OS_SystemInfo *info = &os_lnx_state.system_info; + info->logical_processor_count = (U32)get_nprocs(); + info->page_size = (U64)getpagesize(); + info->large_page_size = MB(2); + info->allocation_granularity = info->page_size; + } + { + OS_ProcessInfo *info = &os_lnx_state.process_info; + info->pid = (U32)getpid(); + } + + //- rjf: set up thread context + local_persist TCTX tctx; + tctx_init_and_equip(&tctx); + + //- rjf: set up dynamically allocated state + os_lnx_state.arena = arena_alloc(); + os_lnx_state.entity_arena = arena_alloc(); + pthread_mutex_init(&os_lnx_state.entity_mutex, 0); + + //- rjf: grab dynamically allocated system info + { + Temp scratch = scratch_begin(0, 0); + OS_SystemInfo *info = &os_lnx_state.system_info; + + // get machine name + B32 got_final_result = 0; + U8 *buffer = 0; + int size = 0; + for(S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1) + { + scratch_end(scratch); + buffer = push_array_no_zero(scratch.arena, U8, cap); + size = gethostname((char*)buffer, cap); + if(size < cap) + { + got_final_result = 1; + break; + } + } + + // save name to info + if(got_final_result && size > 0) + { + info->machine_name.size = size; + info->machine_name.str = push_array_no_zero(os_lnx_state.arena, U8, info->machine_name.size + 1); + MemoryCopy(info->machine_name.str, buffer, info->machine_name.size); + info->machine_name.str[info->machine_name.size] = 0; + } + + scratch_end(scratch); + } + + //- rjf: grab dynamically allocated process info + { + Temp scratch = scratch_begin(0, 0); + OS_ProcessInfo *info = &os_lnx_state.process_info; + + // grab binary path + { + // get self string + B32 got_final_result = 0; + U8 *buffer = 0; + int size = 0; + for(S64 cap = PATH_MAX, r = 0; r < 4; cap *= 2, r += 1) + { + scratch_end(scratch); + buffer = push_array_no_zero(scratch.arena, U8, cap); + size = readlink("/proc/self/exe", (char*)buffer, cap); + if(size < cap) + { + got_final_result = 1; + break; + } + } + + // save + if(got_final_result && size > 0) + { + String8 full_name = str8(buffer, size); + String8 name_chopped = str8_chop_last_slash(full_name); + info->binary_path = push_str8_copy(os_lnx_state.arena, name_chopped); + } + } + + // grab initial directory + { + info->initial_path = os_get_current_path(os_lnx_state.arena); + } + + // grab home directory + { + char *home = getenv("HOME"); + info->user_program_data_path = str8_cstring(home); + } + + // grab environment variables + { + extern char **environ; + if (environ != 0) + { + for (char **env_ptr = environ; *env_ptr != 0; env_ptr += 1) + { + String8 env_var = str8_cstring(*env_ptr); + str8_list_push(os_lnx_state.arena, &info->environment, env_var); + } + } + } + + scratch_end(scratch); + } + } + + //- rjf: call into "real" entry point + main_thread_base_entry_point(argc, argv); +} + diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index e7d2ba5ac..40fd43808 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -1,137 +1,212 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_CORE_LINUX_H -#define OS_CORE_LINUX_H - -//////////////////////////////// -//~ rjf: Includes - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -pid_t gettid(void); -int pthread_setname_np(pthread_t thread, const char *name); -int pthread_getname_np(pthread_t thread, char *name, size_t size); - -typedef struct tm tm; -typedef struct timespec timespec; - -//////////////////////////////// -//~ rjf: File Iterator - -typedef struct OS_LNX_FileIter OS_LNX_FileIter; -struct OS_LNX_FileIter -{ - DIR *dir; - struct dirent *dp; - String8 path; -}; -StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(OS_LNX_FileIter), os_lnx_file_iter_size_check); - -//////////////////////////////// -//~ rjf: Safe Call Handler Chain - -typedef struct OS_LNX_SafeCallChain OS_LNX_SafeCallChain; -struct OS_LNX_SafeCallChain -{ - OS_LNX_SafeCallChain *next; - OS_ThreadFunctionType *fail_handler; - void *ptr; -}; - -//////////////////////////////// -//~ rjf: Entities - -typedef enum OS_LNX_EntityKind -{ - OS_LNX_EntityKind_Thread, - OS_LNX_EntityKind_Mutex, - OS_LNX_EntityKind_RWMutex, - OS_LNX_EntityKind_ConditionVariable, -} -OS_LNX_EntityKind; - -typedef struct OS_LNX_Entity OS_LNX_Entity; -struct OS_LNX_Entity -{ - OS_LNX_Entity *next; - OS_LNX_EntityKind kind; - union - { - struct - { - pthread_t handle; - OS_ThreadFunctionType *func; - void *ptr; - } thread; - pthread_mutex_t mutex_handle; - pthread_rwlock_t rwmutex_handle; - struct - { - pthread_cond_t cond_handle; - pthread_mutex_t rwlock_mutex_handle; - } cv; - }; -}; - -//////////////////////////////// -//~ rjf: State - -typedef struct OS_LNX_State OS_LNX_State; -struct OS_LNX_State -{ - Arena *arena; - OS_SystemInfo system_info; - OS_ProcessInfo process_info; - pthread_mutex_t entity_mutex; - Arena *entity_arena; - OS_LNX_Entity *entity_free; -}; - -//////////////////////////////// -//~ rjf: Globals - -global OS_LNX_State os_lnx_state = {0}; -thread_static OS_LNX_SafeCallChain *os_lnx_safe_call_chain = 0; - -//////////////////////////////// -//~ rjf: Helpers - -internal DateTime os_lnx_date_time_from_tm(tm in, U32 msec); -internal tm os_lnx_tm_from_date_time(DateTime dt); -internal timespec os_lnx_timespec_from_date_time(DateTime dt); -internal DenseTime os_lnx_dense_time_from_timespec(timespec in); -internal FileProperties os_lnx_file_properties_from_stat(struct stat *s); -internal void os_lnx_safe_call_sig_handler(int x); - -//////////////////////////////// -//~ rjf: Entities - -internal OS_LNX_Entity *os_lnx_entity_alloc(OS_LNX_EntityKind kind); -internal void os_lnx_entity_release(OS_LNX_Entity *entity); - -//////////////////////////////// -//~ rjf: Thread Entry Point - -internal void *os_lnx_thread_entry_point(void *ptr); - -#endif // OS_CORE_LINUX_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_CORE_LINUX_H +#define OS_CORE_LINUX_H + +//////////////////////////////// +//~ dan: Includes + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Explicit declarations for GNU extensions that might not be exposed +// by headers even when _GNU_SOURCE is defined. +// TODO this feels really stupid and not addressing the root cause of the problem: +extern char **environ; +int execvpe(const char *file, char *const argv[], char *const envp[]); +// From +int pthread_timedjoin_np(pthread_t thread, void **retval, const struct timespec *abstime); +// From +// Use _GNU_SOURCE as the guard since sem_clockwait is often tied to it +// and the specific POSIX macros might not be reliable everywhere. +int sem_clockwait(sem_t *sem, clockid_t clock_id, const struct timespec *abstime); + +#ifndef AT_EMPTY_PATH +#define AT_EMPTY_PATH 0x1000 +#endif + +pid_t gettid(void); +int pthread_setname_np(pthread_t thread, const char *name); +int pthread_getname_np(pthread_t thread, char *name, size_t size); + +typedef struct tm tm; +typedef struct timespec timespec; + +//////////////////////////////// +//~ dan: File Iterator + +typedef struct OS_LNX_FileIter OS_LNX_FileIter; +struct OS_LNX_FileIter +{ + DIR *dir; + struct dirent *dp; + String8 path; + B32 is_volume_iter; + FILE *mount_file_stream; + String8Array volume_mount_points; + U64 volume_iter_idx; +}; +StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(OS_LNX_FileIter), os_lnx_file_iter_size_check); + +//////////////////////////////// +//~ dan: Safe Call Handler Chain + +typedef struct OS_LNX_SafeCallChain OS_LNX_SafeCallChain; +struct OS_LNX_SafeCallChain +{ + OS_LNX_SafeCallChain *next; + OS_ThreadFunctionType *fail_handler; + void *ptr; +}; + +//////////////////////////////// +//~ dan: Entities + +typedef enum OS_LNX_EntityKind +{ + OS_LNX_EntityKind_Thread, + OS_LNX_EntityKind_Mutex, + OS_LNX_EntityKind_RWMutex, + OS_LNX_EntityKind_ConditionVariable, + OS_LNX_EntityKind_Process, + OS_LNX_EntityKind_Semaphore, + OS_LNX_EntityKind_SafeCallChain, +} +OS_LNX_EntityKind; + +typedef struct OS_LNX_Entity OS_LNX_Entity; +struct OS_LNX_Entity +{ + OS_LNX_Entity *next; + OS_LNX_EntityKind kind; + union + { + struct + { + pthread_t handle; + OS_ThreadFunctionType *func; + void *ptr; + } thread; + struct // OS_LNX_EntityKind_Mutex + { + atomic_uint futex; // 0: unlocked, 1: locked uncontended, 2: locked contended + atomic_uint owner_tid; + atomic_uint recursion_depth; + } mutex; + struct // OS_LNX_EntityKind_RWMutex + { + atomic_uint futex; // Stores counts: lower 16 bits readers, upper 16 bits writers/waiters + // Special state for write lock held. +#define RW_MUTEX_READ_MASK 0x0000FFFFu // Lower 16 bits for reader count +#define RW_MUTEX_WRITE_MASK 0xFFFF0000u // Upper 16 bits used for writer/waiter state +#define RW_MUTEX_WRITE_HELD_MASK 0x80000000u // Top bit indicates writer holds the lock +#define RW_MUTEX_WRITE_WAIT_MASK 0x40000000u // Next bit indicates writers are waiting +#define RW_MUTEX_READ_WAIT_MASK 0x20000000u // Next bit indicates readers are waiting + } rw_mutex; + struct // OS_LNX_EntityKind_ConditionVariable + { + atomic_uint futex; // Sequence number or generation count for wakeups + // CVs need an associated mutex, handled externally by the API user + } cv; + struct + { + pid_t pid; + } process; + struct + { + sem_t *named_handle; // Handle for named semaphores from sem_open + sem_t unnamed_handle; // Embedded handle for unnamed semaphores + String8 name; // OS-internal name (e.g., with leading slash prepended) for named semaphores + B32 is_named; + } semaphore; + struct // OS_LNX_EntityKind_SafeCallChain + { + OS_ThreadFunctionType *fail_handler; + void *ptr; + sigjmp_buf jmp_buf; + struct sigaction original_actions[6]; // Max signals we handle + int signals_handled[6]; + int num_signals_handled; + OS_LNX_Entity *caller_chain_node; // Link to caller's node in the TLS chain + } safe_call_chain; + }; +}; + +//////////////////////////////// +//~ dan: State + +typedef struct OS_LNX_State OS_LNX_State; +struct OS_LNX_State +{ + Arena *arena; + OS_SystemInfo system_info; + OS_ProcessInfo process_info; + pthread_mutex_t entity_mutex; + Arena *entity_arena; + OS_LNX_Entity *entity_free; +}; + +//////////////////////////////// +//~ dan: Globals + +global OS_LNX_State os_lnx_state = {0}; +thread_static OS_LNX_Entity *os_lnx_safe_call_chain = 0; + +//////////////////////////////// +//~ dan: Helpers + +internal DateTime os_lnx_date_time_from_tm(tm in, U32 msec); +internal tm os_lnx_tm_from_date_time(DateTime dt); +internal timespec os_lnx_timespec_from_date_time(DateTime dt); +internal DenseTime os_lnx_dense_time_from_timespec(timespec in); +internal DenseTime os_lnx_dense_time_from_statx_timestamp(struct statx_timestamp in); +internal FileProperties os_lnx_file_properties_from_stat(struct stat *s); +internal void os_lnx_safe_call_sig_handler(int sig); + +//////////////////////////////// +//~ dan: Entities + +internal OS_LNX_Entity *os_lnx_entity_alloc(OS_LNX_EntityKind kind); +internal void os_lnx_entity_release(OS_LNX_Entity *entity); + +//////////////////////////////// +//~ dan: Futex Helpers + +internal int os_lnx_futex(atomic_uint *uaddr, int futex_op, unsigned int val, const struct timespec *timeout, unsigned int val3); +internal int os_lnx_futex_wait(atomic_uint *uaddr, unsigned int expected_val, const struct timespec *timeout); +internal int os_lnx_futex_wake(atomic_uint *uaddr, int num_to_wake); + +//////////////////////////////// +//~ dan: Thread Entry Point + +internal void *os_lnx_thread_entry_point(void *ptr); + +#endif // OS_CORE_LINUX_H diff --git a/src/os/core/win32/os_core_win32.c b/src/os/core/win32/os_core_win32.c index 6a0e02816..3cf517ef6 100644 --- a/src/os/core/win32/os_core_win32.c +++ b/src/os/core/win32/os_core_win32.c @@ -1,1739 +1,1739 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Modern Windows SDK Functions -// -// (We must dynamically link to them, since they can be missing in older SDKs) - -typedef HRESULT W32_SetThreadDescription_Type(HANDLE hThread, PCWSTR lpThreadDescription); -global W32_SetThreadDescription_Type *w32_SetThreadDescription_func = 0; - -//////////////////////////////// -//~ rjf: File Info Conversion Helpers - -internal FilePropertyFlags -os_w32_file_property_flags_from_dwFileAttributes(DWORD dwFileAttributes) -{ - FilePropertyFlags flags = 0; - if(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - flags |= FilePropertyFlag_IsFolder; - } - return flags; -} - -internal void -os_w32_file_properties_from_attribute_data(FileProperties *properties, WIN32_FILE_ATTRIBUTE_DATA *attributes) -{ - properties->size = Compose64Bit(attributes->nFileSizeHigh, attributes->nFileSizeLow); - os_w32_dense_time_from_file_time(&properties->created, &attributes->ftCreationTime); - os_w32_dense_time_from_file_time(&properties->modified, &attributes->ftLastWriteTime); - properties->flags = os_w32_file_property_flags_from_dwFileAttributes(attributes->dwFileAttributes); -} - -//////////////////////////////// -//~ rjf: Time Conversion Helpers - -internal void -os_w32_date_time_from_system_time(DateTime *out, SYSTEMTIME *in) -{ - out->year = in->wYear; - out->mon = in->wMonth - 1; - out->wday = in->wDayOfWeek; - out->day = in->wDay; - out->hour = in->wHour; - out->min = in->wMinute; - out->sec = in->wSecond; - out->msec = in->wMilliseconds; -} - -internal void -os_w32_system_time_from_date_time(SYSTEMTIME *out, DateTime *in) -{ - out->wYear = (WORD)(in->year); - out->wMonth = in->mon + 1; - out->wDay = in->day; - out->wHour = in->hour; - out->wMinute = in->min; - out->wSecond = in->sec; - out->wMilliseconds = in->msec; -} - -internal void -os_w32_dense_time_from_file_time(DenseTime *out, FILETIME *in) -{ - SYSTEMTIME systime = {0}; - FileTimeToSystemTime(in, &systime); - DateTime date_time = {0}; - os_w32_date_time_from_system_time(&date_time, &systime); - *out = dense_time_from_date_time(date_time); -} - -internal U32 -os_w32_sleep_ms_from_endt_us(U64 endt_us) -{ - U32 sleep_ms = 0; - if(endt_us == max_U64) - { - sleep_ms = INFINITE; - } - else - { - U64 begint = os_now_microseconds(); - if(begint < endt_us) - { - U64 sleep_us = endt_us - begint; - sleep_ms = (U32)((sleep_us + 999)/1000); - } - } - return sleep_ms; -} - -internal U32 -os_w32_unix_time_from_file_time(FILETIME file_time) -{ - U64 win32_time = ((U64)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime; - U64 unix_time64 = ((win32_time - 0x19DB1DED53E8000ULL) / 10000000); - - Assert(unix_time64 <= max_U32); - U32 unix_time32 = (U32)unix_time64; - - return unix_time32; -} - -//////////////////////////////// -//~ rjf: Entity Functions - -internal OS_W32_Entity * -os_w32_entity_alloc(OS_W32_EntityKind kind) -{ - OS_W32_Entity *result = 0; - EnterCriticalSection(&os_w32_state.entity_mutex); - { - result = os_w32_state.entity_free; - if(result) - { - SLLStackPop(os_w32_state.entity_free); - } - else - { - result = push_array_no_zero(os_w32_state.entity_arena, OS_W32_Entity, 1); - } - MemoryZeroStruct(result); - } - LeaveCriticalSection(&os_w32_state.entity_mutex); - result->kind = kind; - return result; -} - -internal void -os_w32_entity_release(OS_W32_Entity *entity) -{ - entity->kind = OS_W32_EntityKind_Null; - EnterCriticalSection(&os_w32_state.entity_mutex); - SLLStackPush(os_w32_state.entity_free, entity); - LeaveCriticalSection(&os_w32_state.entity_mutex); -} - -//////////////////////////////// -//~ rjf: Thread Entry Point - -internal DWORD -os_w32_thread_entry_point(void *ptr) -{ - OS_W32_Entity *entity = (OS_W32_Entity *)ptr; - OS_ThreadFunctionType *func = entity->thread.func; - void *thread_ptr = entity->thread.ptr; - TCTX tctx_; - tctx_init_and_equip(&tctx_); - func(thread_ptr); - tctx_release(); - return 0; -} - -//////////////////////////////// -//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) - -internal OS_SystemInfo * -os_get_system_info(void) -{ - return &os_w32_state.system_info; -} - -internal OS_ProcessInfo * -os_get_process_info(void) -{ - return &os_w32_state.process_info; -} - -internal String8 -os_get_current_path(Arena *arena) -{ - Temp scratch = scratch_begin(&arena, 1); - DWORD length = GetCurrentDirectoryW(0, 0); - U16 *memory = push_array_no_zero(scratch.arena, U16, length + 1); - length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory); - String8 name = str8_from_16(arena, str16(memory, length)); - scratch_end(scratch); - return name; -} - -internal U32 -os_get_process_start_time_unix(void) -{ - HANDLE handle = GetCurrentProcess(); - FILETIME start_time = {0}; - FILETIME exit_time; - FILETIME kernel_time; - FILETIME user_time; - if(GetProcessTimes(handle, &start_time, &exit_time, &kernel_time, &user_time)) - { - return os_w32_unix_time_from_file_time(start_time); - } - return 0; -} - -//////////////////////////////// -//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) - -//- rjf: basic - -internal void * -os_reserve(U64 size) -{ - void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); - return result; -} - -internal B32 -os_commit(void *ptr, U64 size) -{ - B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); - return result; -} - -internal void -os_decommit(void *ptr, U64 size) -{ - VirtualFree(ptr, size, MEM_DECOMMIT); -} - -internal void -os_release(void *ptr, U64 size) -{ - // NOTE(rjf): size not used - not necessary on Windows, but necessary for other OSes. - VirtualFree(ptr, 0, MEM_RELEASE); -} - -//- rjf: large pages - -internal void * -os_reserve_large(U64 size) -{ - // we commit on reserve because windows - void *result = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_LARGE_PAGES, PAGE_READWRITE); - return result; -} - -internal B32 -os_commit_large(void *ptr, U64 size) -{ - return 1; -} - -//////////////////////////////// -//~ rjf: @os_hooks Thread Info (Implemented Per-OS) - -internal U32 -os_tid(void) -{ - DWORD id = GetCurrentThreadId(); - return (U32)id; -} - -internal void -os_set_thread_name(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - - // rjf: windows 10 style - if(w32_SetThreadDescription_func) - { - String16 name16 = str16_from_8(scratch.arena, name); - HRESULT hr = w32_SetThreadDescription_func(GetCurrentThread(), (WCHAR*)name16.str); - } - - // rjf: raise-exception style - { - String8 name_copy = push_str8_copy(scratch.arena, name); -#pragma pack(push,8) - typedef struct THREADNAME_INFO THREADNAME_INFO; - struct THREADNAME_INFO - { - U32 dwType; // Must be 0x1000. - char *szName; // Pointer to name (in user addr space). - U32 dwThreadID; // Thread ID (-1=caller thread). - U32 dwFlags; // Reserved for future use, must be zero. - }; -#pragma pack(pop) - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = (char *)name_copy.str; - info.dwThreadID = os_tid(); - info.dwFlags = 0; -#pragma warning(push) -#pragma warning(disable: 6320 6322) - __try - { - RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - } -#pragma warning(pop) - } - - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: @os_hooks Aborting (Implemented Per-OS) - -internal void -os_abort(S32 exit_code) -{ - ExitProcess(exit_code); -} - -//////////////////////////////// -//~ rjf: @os_hooks File System (Implemented Per-OS) - -//- rjf: files - -internal OS_Handle -os_file_open(OS_AccessFlags flags, String8 path) -{ - OS_Handle result = {0}; - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - DWORD access_flags = 0; - DWORD share_mode = 0; - DWORD creation_disposition = OPEN_EXISTING; - SECURITY_ATTRIBUTES security_attributes = {sizeof(security_attributes), 0, 0}; - if(flags & OS_AccessFlag_Read) {access_flags |= GENERIC_READ;} - if(flags & OS_AccessFlag_Write) {access_flags |= GENERIC_WRITE;} - if(flags & OS_AccessFlag_Execute) {access_flags |= GENERIC_EXECUTE;} - if(flags & OS_AccessFlag_ShareRead) {share_mode |= FILE_SHARE_READ;} - if(flags & OS_AccessFlag_ShareWrite) {share_mode |= FILE_SHARE_WRITE|FILE_SHARE_DELETE;} - if(flags & OS_AccessFlag_Write) {creation_disposition = CREATE_ALWAYS;} - if(flags & OS_AccessFlag_Append) {creation_disposition = OPEN_ALWAYS; access_flags |= FILE_APPEND_DATA; } - if(flags & OS_AccessFlag_Inherited) - { - security_attributes.bInheritHandle = 1; - } - HANDLE file = CreateFileW((WCHAR *)path16.str, access_flags, share_mode, &security_attributes, creation_disposition, FILE_ATTRIBUTE_NORMAL, 0); - if(file != INVALID_HANDLE_VALUE) - { - result.u64[0] = (U64)file; - } - scratch_end(scratch); - return result; -} - -internal void -os_file_close(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { return; } - HANDLE handle = (HANDLE)file.u64[0]; - BOOL result = CloseHandle(handle); - (void)result; -} - -internal U64 -os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - HANDLE handle = (HANDLE)file.u64[0]; - - // rjf: clamp range by file size - U64 size = 0; - GetFileSizeEx(handle, (LARGE_INTEGER *)&size); - Rng1U64 rng_clamped = r1u64(ClampTop(rng.min, size), ClampTop(rng.max, size)); - U64 total_read_size = 0; - - // rjf: read loop - { - U64 to_read = dim_1u64(rng_clamped); - for(U64 off = rng.min; total_read_size < to_read;) - { - U64 amt64 = to_read - total_read_size; - U32 amt32 = u32_from_u64_saturate(amt64); - DWORD read_size = 0; - OVERLAPPED overlapped = {0}; - overlapped.Offset = (off&0x00000000ffffffffull); - overlapped.OffsetHigh = (off&0xffffffff00000000ull) >> 32; - ReadFile(handle, (U8 *)out_data + total_read_size, amt32, &read_size, &overlapped); - off += read_size; - total_read_size += read_size; - if(read_size != amt32) - { - break; - } - } - } - - return total_read_size; -} - -internal U64 -os_file_write(OS_Handle file, Rng1U64 rng, void *data) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - HANDLE win_handle = (HANDLE)file.u64[0]; - U64 src_off = 0; - U64 dst_off = rng.min; - U64 total_write_size = dim_1u64(rng); - for(;;) - { - void *bytes_src = (U8 *)data + src_off; - U64 bytes_left = total_write_size - src_off; - DWORD write_size = Min(MB(1), bytes_left); - DWORD bytes_written = 0; - OVERLAPPED overlapped = {0}; - overlapped.Offset = (dst_off&0x00000000ffffffffull); - overlapped.OffsetHigh = (dst_off&0xffffffff00000000ull) >> 32; - BOOL success = WriteFile(win_handle, bytes_src, write_size, &bytes_written, &overlapped); - if(success == 0) - { - break; - } - src_off += bytes_written; - dst_off += bytes_written; - if(bytes_left == 0) - { - break; - } - } - return src_off; -} - -internal B32 -os_file_set_time(OS_Handle file, DateTime time) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - B32 result = 0; - HANDLE handle = (HANDLE)file.u64[0]; - SYSTEMTIME system_time = {0}; - os_w32_system_time_from_date_time(&system_time, &time); - FILETIME file_time = {0}; - result = (SystemTimeToFileTime(&system_time, &file_time) && - SetFileTime(handle, &file_time, &file_time, &file_time)); - return result; -} - -internal FileProperties -os_properties_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { FileProperties r = {0}; return r; } - FileProperties props = {0}; - HANDLE handle = (HANDLE)file.u64[0]; - BY_HANDLE_FILE_INFORMATION info; - BOOL info_good = GetFileInformationByHandle(handle, &info); - if(info_good) - { - U32 size_lo = info.nFileSizeLow; - U32 size_hi = info.nFileSizeHigh; - props.size = (U64)size_lo | (((U64)size_hi)<<32); - os_w32_dense_time_from_file_time(&props.modified, &info.ftLastWriteTime); - os_w32_dense_time_from_file_time(&props.created, &info.ftCreationTime); - props.flags = os_w32_file_property_flags_from_dwFileAttributes(info.dwFileAttributes); - } - return props; -} - -internal OS_FileID -os_id_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { OS_FileID r = {0}; return r; } - OS_FileID result = {0}; - HANDLE handle = (HANDLE)file.u64[0]; - BY_HANDLE_FILE_INFORMATION info; - BOOL is_ok = GetFileInformationByHandle(handle, &info); - if(is_ok) - { - result.v[0] = info.dwVolumeSerialNumber; - result.v[1] = info.nFileIndexLow; - result.v[2] = info.nFileIndexHigh; - } - return result; -} - -internal B32 -os_delete_file_at_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - B32 result = DeleteFileW((WCHAR*)path16.str); - scratch_end(scratch); - return result; -} - -internal B32 -os_copy_file_path(String8 dst, String8 src) -{ - Temp scratch = scratch_begin(0, 0); - String16 dst16 = str16_from_8(scratch.arena, dst); - String16 src16 = str16_from_8(scratch.arena, src); - B32 result = CopyFileW((WCHAR*)src16.str, (WCHAR*)dst16.str, 0); - scratch_end(scratch); - return result; -} - -internal String8 -os_full_path_from_path(Arena *arena, String8 path) -{ - Temp scratch = scratch_begin(&arena, 1); - DWORD buffer_size = Max(MAX_PATH, path.size * 2) + 1; - String16 path16 = str16_from_8(scratch.arena, path); - WCHAR *buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size); - DWORD path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL); - if(path16_size > buffer_size) - { - arena_pop(scratch.arena, buffer_size); - buffer_size = path16_size + 1; - buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size); - path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL); - } - String8 full_path = str8_from_16(arena, str16((U16*)buffer, path16_size)); - scratch_end(scratch); - return full_path; -} - -internal B32 -os_file_path_exists(String8 path) -{ - Temp scratch = scratch_begin(0,0); - String16 path16 = str16_from_8(scratch.arena, path); - DWORD attributes = GetFileAttributesW((WCHAR *)path16.str); - B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && !!(~attributes & FILE_ATTRIBUTE_DIRECTORY); - scratch_end(scratch); - return exists; -} - -internal B32 -os_folder_path_exists(String8 path) -{ - Temp scratch = scratch_begin(0,0); - String16 path16 = str16_from_8(scratch.arena, path); - DWORD attributes = GetFileAttributesW((WCHAR *)path16.str); - B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY); - scratch_end(scratch); - return exists; -} - -internal FileProperties -os_properties_from_file_path(String8 path) -{ - WIN32_FIND_DATAW find_data = {0}; - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - HANDLE handle = FindFirstFileW((WCHAR *)path16.str, &find_data); - FileProperties props = {0}; - if(handle != INVALID_HANDLE_VALUE) - { - props.size = Compose64Bit(find_data.nFileSizeHigh, find_data.nFileSizeLow); - os_w32_dense_time_from_file_time(&props.created, &find_data.ftCreationTime); - os_w32_dense_time_from_file_time(&props.modified, &find_data.ftLastWriteTime); - props.flags = os_w32_file_property_flags_from_dwFileAttributes(find_data.dwFileAttributes); - } - FindClose(handle); - scratch_end(scratch); - return props; -} - -//- rjf: file maps - -internal OS_Handle -os_file_map_open(OS_AccessFlags flags, OS_Handle file) -{ - OS_Handle map = {0}; - { - HANDLE file_handle = (HANDLE)file.u64[0]; - DWORD protect_flags = 0; - { - switch(flags) - { - default:{}break; - case OS_AccessFlag_Read: - {protect_flags = PAGE_READONLY;}break; - case OS_AccessFlag_Write: - case OS_AccessFlag_Read|OS_AccessFlag_Write: - {protect_flags = PAGE_READWRITE;}break; - case OS_AccessFlag_Execute: - case OS_AccessFlag_Read|OS_AccessFlag_Execute: - {protect_flags = PAGE_EXECUTE_READ;}break; - case OS_AccessFlag_Execute|OS_AccessFlag_Write|OS_AccessFlag_Read: - case OS_AccessFlag_Execute|OS_AccessFlag_Write: - {protect_flags = PAGE_EXECUTE_READWRITE;}break; - } - } - HANDLE map_handle = CreateFileMappingA(file_handle, 0, protect_flags, 0, 0, 0); - map.u64[0] = (U64)map_handle; - } - return map; -} - -internal void -os_file_map_close(OS_Handle map) -{ - HANDLE handle = (HANDLE)map.u64[0]; - BOOL result = CloseHandle(handle); - (void)result; -} - -internal void * -os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) -{ - HANDLE handle = (HANDLE)map.u64[0]; - U32 off_lo = (U32)((range.min&0x00000000ffffffffull)>>0); - U32 off_hi = (U32)((range.min&0xffffffff00000000ull)>>32); - U64 size = dim_1u64(range); - DWORD access_flags = 0; - { - switch(flags) - { - default:{}break; - case OS_AccessFlag_Read: - { - access_flags = FILE_MAP_READ; - }break; - case OS_AccessFlag_Write: - { - access_flags = FILE_MAP_WRITE; - }break; - case OS_AccessFlag_Read|OS_AccessFlag_Write: - { - access_flags = FILE_MAP_ALL_ACCESS; - }break; - case OS_AccessFlag_Execute: - case OS_AccessFlag_Read|OS_AccessFlag_Execute: - case OS_AccessFlag_Write|OS_AccessFlag_Execute: - case OS_AccessFlag_Read|OS_AccessFlag_Write|OS_AccessFlag_Execute: - { - access_flags = FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE; - }break; - } - } - void *result = MapViewOfFile(handle, access_flags, off_hi, off_lo, size); - return result; -} - -internal void -os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range) -{ - BOOL result = UnmapViewOfFile(ptr); - (void)result; -} - -//- rjf: directory iteration - -internal OS_FileIter * -os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) -{ - Temp scratch = scratch_begin(&arena, 1); - String8 path_with_wildcard = push_str8_cat(scratch.arena, path, str8_lit("\\*")); - String16 path16 = str16_from_8(scratch.arena, path_with_wildcard); - OS_FileIter *iter = push_array(arena, OS_FileIter, 1); - iter->flags = flags; - OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; - if(path.size == 0) - { - w32_iter->is_volume_iter = 1; - WCHAR buffer[512] = {0}; - DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer); - String8List drive_strings = {0}; - for(U64 off = 0; off < (U64)length;) - { - String16 next_drive_string_16 = str16_cstring((U16 *)buffer+off); - off += next_drive_string_16.size+1; - String8 next_drive_string = str8_from_16(arena, next_drive_string_16); - next_drive_string = str8_chop_last_slash(next_drive_string); - str8_list_push(scratch.arena, &drive_strings, next_drive_string); - } - w32_iter->drive_strings = str8_array_from_list(arena, &drive_strings); - w32_iter->drive_strings_iter_idx = 0; - } - else - { - w32_iter->handle = FindFirstFileW((WCHAR*)path16.str, &w32_iter->find_data); - } - scratch_end(scratch); - return iter; -} - -internal B32 -os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) -{ - B32 result = 0; - OS_FileIterFlags flags = iter->flags; - OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; - switch(w32_iter->is_volume_iter) - { - //- rjf: file iteration - default: - case 0: - { - if (!(flags & OS_FileIterFlag_Done) && w32_iter->handle != INVALID_HANDLE_VALUE) - { - do - { - // check is usable - B32 usable_file = 1; - - WCHAR *file_name = w32_iter->find_data.cFileName; - DWORD attributes = w32_iter->find_data.dwFileAttributes; - if (file_name[0] == '.'){ - if (flags & OS_FileIterFlag_SkipHiddenFiles){ - usable_file = 0; - } - else if (file_name[1] == 0){ - usable_file = 0; - } - else if (file_name[1] == '.' && file_name[2] == 0){ - usable_file = 0; - } - } - if (attributes & FILE_ATTRIBUTE_DIRECTORY){ - if (flags & OS_FileIterFlag_SkipFolders){ - usable_file = 0; - } - } - else{ - if (flags & OS_FileIterFlag_SkipFiles){ - usable_file = 0; - } - } - - // emit if usable - if (usable_file){ - info_out->name = str8_from_16(arena, str16_cstring((U16*)file_name)); - info_out->props.size = (U64)w32_iter->find_data.nFileSizeLow | (((U64)w32_iter->find_data.nFileSizeHigh)<<32); - os_w32_dense_time_from_file_time(&info_out->props.created, &w32_iter->find_data.ftCreationTime); - os_w32_dense_time_from_file_time(&info_out->props.modified, &w32_iter->find_data.ftLastWriteTime); - info_out->props.flags = os_w32_file_property_flags_from_dwFileAttributes(attributes); - result = 1; - if (!FindNextFileW(w32_iter->handle, &w32_iter->find_data)){ - iter->flags |= OS_FileIterFlag_Done; - } - break; - } - }while(FindNextFileW(w32_iter->handle, &w32_iter->find_data)); - } - }break; - - //- rjf: volume iteration - case 1: - { - result = w32_iter->drive_strings_iter_idx < w32_iter->drive_strings.count; - if(result != 0) - { - MemoryZeroStruct(info_out); - info_out->name = w32_iter->drive_strings.v[w32_iter->drive_strings_iter_idx]; - info_out->props.flags |= FilePropertyFlag_IsFolder; - w32_iter->drive_strings_iter_idx += 1; - } - }break; - } - if(!result) - { - iter->flags |= OS_FileIterFlag_Done; - } - return result; -} - -internal void -os_file_iter_end(OS_FileIter *iter) -{ - OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; - HANDLE zero_handle; - MemoryZeroStruct(&zero_handle); - if(!MemoryMatchStruct(&zero_handle, &w32_iter->handle)) - { - FindClose(w32_iter->handle); - } -} - -//- rjf: directory creation - -internal B32 -os_make_directory(String8 path) -{ - B32 result = 0; - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, path); - WIN32_FILE_ATTRIBUTE_DATA attributes = {0}; - GetFileAttributesExW((WCHAR*)name16.str, GetFileExInfoStandard, &attributes); - if(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - result = 1; - } - else if(CreateDirectoryW((WCHAR*)name16.str, 0)) - { - result = 1; - } - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) - -internal OS_Handle -os_shared_memory_alloc(U64 size, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE file = CreateFileMappingW(INVALID_HANDLE_VALUE, - 0, - PAGE_READWRITE, - (U32)((size & 0xffffffff00000000) >> 32), - (U32)((size & 0x00000000ffffffff)), - (WCHAR *)name16.str); - OS_Handle result = {(U64)file}; - scratch_end(scratch); - return result; -} - -internal OS_Handle -os_shared_memory_open(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE file = OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, (WCHAR *)name16.str); - OS_Handle result = {(U64)file}; - scratch_end(scratch); - return result; -} - -internal void -os_shared_memory_close(OS_Handle handle) -{ - HANDLE file = (HANDLE)(handle.u64[0]); - CloseHandle(file); -} - -internal void * -os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) -{ - HANDLE file = (HANDLE)(handle.u64[0]); - U64 offset = range.min; - U64 size = range.max-range.min; - void *ptr = MapViewOfFile(file, FILE_MAP_ALL_ACCESS, - (U32)((offset & 0xffffffff00000000) >> 32), - (U32)((offset & 0x00000000ffffffff)), - size); - return ptr; -} - -internal void -os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range) -{ - UnmapViewOfFile(ptr); -} - -//////////////////////////////// -//~ rjf: @os_hooks Time (Implemented Per-OS) - -internal U64 -os_now_microseconds(void) -{ - U64 result = 0; - LARGE_INTEGER large_int_counter; - if(QueryPerformanceCounter(&large_int_counter)) - { - result = (large_int_counter.QuadPart*Million(1))/os_w32_state.microsecond_resolution; - } - return result; -} - -internal U32 -os_now_unix(void) -{ - FILETIME file_time; - GetSystemTimeAsFileTime(&file_time); - U32 unix_time = os_w32_unix_time_from_file_time(file_time); - return unix_time; -} - -internal DateTime -os_now_universal_time(void) -{ - SYSTEMTIME systime = {0}; - GetSystemTime(&systime); - DateTime result = {0}; - os_w32_date_time_from_system_time(&result, &systime); - return result; -} - -internal DateTime -os_universal_time_from_local(DateTime *date_time) -{ - SYSTEMTIME systime = {0}; - os_w32_system_time_from_date_time(&systime, date_time); - FILETIME ftime = {0}; - SystemTimeToFileTime(&systime, &ftime); - FILETIME ftime_local = {0}; - LocalFileTimeToFileTime(&ftime, &ftime_local); - FileTimeToSystemTime(&ftime_local, &systime); - DateTime result = {0}; - os_w32_date_time_from_system_time(&result, &systime); - return result; -} - -internal DateTime -os_local_time_from_universal(DateTime *date_time) -{ - SYSTEMTIME systime = {0}; - os_w32_system_time_from_date_time(&systime, date_time); - FILETIME ftime = {0}; - SystemTimeToFileTime(&systime, &ftime); - FILETIME ftime_local = {0}; - FileTimeToLocalFileTime(&ftime, &ftime_local); - FileTimeToSystemTime(&ftime_local, &systime); - DateTime result = {0}; - os_w32_date_time_from_system_time(&result, &systime); - return result; -} - -internal void -os_sleep_milliseconds(U32 msec) -{ - Sleep(msec); -} - -//////////////////////////////// -//~ rjf: @os_hooks Child Processes (Implemented Per-OS) - -internal OS_Handle -os_process_launch(OS_ProcessLaunchParams *params) -{ - OS_Handle result = {0}; - Temp scratch = scratch_begin(0, 0); - - //- rjf: form full command string - String8 cmd = {0}; - { - StringJoin join_params = {0}; - join_params.pre = str8_lit("\""); - join_params.sep = str8_lit("\" \""); - join_params.post = str8_lit("\""); - cmd = str8_list_join(scratch.arena, ¶ms->cmd_line, &join_params); - } - - //- rjf: form environment - B32 use_null_env_arg = 0; - String8 env = {0}; - { - StringJoin join_params2 = {0}; - join_params2.sep = str8_lit("\0"); - join_params2.post = str8_lit("\0"); - String8List all_opts = params->env; - if(params->inherit_env != 0) - { - if(all_opts.node_count != 0) - { - MemoryZeroStruct(&all_opts); - for(String8Node *n = params->env.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - for(String8Node *n = os_w32_state.process_info.environment.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - } - else - { - use_null_env_arg = 1; - } - } - if(use_null_env_arg == 0) - { - env = str8_list_join(scratch.arena, &all_opts, &join_params2); - } - } - - //- rjf: utf-8 -> utf-16 - String16 cmd16 = str16_from_8(scratch.arena, cmd); - String16 dir16 = str16_from_8(scratch.arena, params->path); - String16 env16 = {0}; - if(use_null_env_arg == 0) - { - env16 = str16_from_8(scratch.arena, env); - } - - //- rjf: determine creation flags - DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; - if(params->consoleless) - { - creation_flags |= CREATE_NO_WINDOW; - } - - //- rjf: launch - BOOL inherit_handles = 0; - STARTUPINFOW startup_info = {sizeof(startup_info)}; - if(!os_handle_match(params->stdout_file, os_handle_zero())) - { - HANDLE stdout_handle = (HANDLE)params->stdout_file.u64[0]; - startup_info.hStdOutput = stdout_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - if(!os_handle_match(params->stderr_file, os_handle_zero())) - { - HANDLE stderr_handle = (HANDLE)params->stderr_file.u64[0]; - startup_info.hStdError = stderr_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - if(!os_handle_match(params->stdin_file, os_handle_zero())) - { - HANDLE stdin_handle = (HANDLE)params->stdin_file.u64[0]; - startup_info.hStdInput = stdin_handle; - startup_info.dwFlags |= STARTF_USESTDHANDLES; - inherit_handles = 1; - } - PROCESS_INFORMATION process_info = {0}; - if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, inherit_handles, creation_flags, use_null_env_arg ? 0 : (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) - { - result.u64[0] = (U64)process_info.hProcess; - CloseHandle(process_info.hThread); - } - - scratch_end(scratch); - return result; -} - -internal B32 -os_process_join(OS_Handle handle, U64 endt_us) -{ - HANDLE process = (HANDLE)(handle.u64[0]); - DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - DWORD result = WaitForSingleObject(process, sleep_ms); - return (result == WAIT_OBJECT_0); -} - -internal void -os_process_detach(OS_Handle handle) -{ - HANDLE process = (HANDLE)(handle.u64[0]); - CloseHandle(process); -} - -//////////////////////////////// -//~ rjf: @os_hooks Threads (Implemented Per-OS) - -internal OS_Handle -os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params) -{ - OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Thread); - entity->thread.func = func; - entity->thread.ptr = ptr; - entity->thread.handle = CreateThread(0, 0, os_w32_thread_entry_point, entity, 0, &entity->thread.tid); - OS_Handle result = {IntFromPtr(entity)}; - return result; -} - -internal B32 -os_thread_join(OS_Handle handle, U64 endt_us) -{ - DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - OS_W32_Entity *entity = (OS_W32_Entity *)PtrFromInt(handle.u64[0]); - DWORD wait_result = WAIT_OBJECT_0; - if(entity != 0) - { - wait_result = WaitForSingleObject(entity->thread.handle, sleep_ms); - CloseHandle(entity->thread.handle); - os_w32_entity_release(entity); - } - return (wait_result == WAIT_OBJECT_0); -} - -internal void -os_thread_detach(OS_Handle thread) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(thread.u64[0]); - if(entity != 0) - { - CloseHandle(entity->thread.handle); - os_w32_entity_release(entity); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) - -//- rjf: mutexes - -internal OS_Handle -os_mutex_alloc(void) -{ - OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Mutex); - InitializeCriticalSection(&entity->mutex); - OS_Handle result = {IntFromPtr(entity)}; - return result; -} - -internal void -os_mutex_release(OS_Handle mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); - os_w32_entity_release(entity); -} - -internal void -os_mutex_take(OS_Handle mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); - EnterCriticalSection(&entity->mutex); -} - -internal void -os_mutex_drop(OS_Handle mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); - LeaveCriticalSection(&entity->mutex); -} - -//- rjf: reader/writer mutexes - -internal OS_Handle -os_rw_mutex_alloc(void) -{ - OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_RWMutex); - InitializeSRWLock(&entity->rw_mutex); - OS_Handle result = {IntFromPtr(entity)}; - return result; -} - -internal void -os_rw_mutex_release(OS_Handle rw_mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - os_w32_entity_release(entity); -} - -internal void -os_rw_mutex_take_r(OS_Handle rw_mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - AcquireSRWLockShared(&entity->rw_mutex); -} - -internal void -os_rw_mutex_drop_r(OS_Handle rw_mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - ReleaseSRWLockShared(&entity->rw_mutex); -} - -internal void -os_rw_mutex_take_w(OS_Handle rw_mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - AcquireSRWLockExclusive(&entity->rw_mutex); -} - -internal void -os_rw_mutex_drop_w(OS_Handle rw_mutex) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - ReleaseSRWLockExclusive(&entity->rw_mutex); -} - -//- rjf: condition variables - -internal OS_Handle -os_condition_variable_alloc(void) -{ - OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_ConditionVariable); - InitializeConditionVariable(&entity->cv); - OS_Handle result = {IntFromPtr(entity)}; - return result; -} - -internal void -os_condition_variable_release(OS_Handle cv) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - os_w32_entity_release(entity); -} - -internal B32 -os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us) -{ - U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - BOOL result = 0; - if(sleep_ms > 0) - { - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); - result = SleepConditionVariableCS(&entity->cv, &mutex_entity->mutex, sleep_ms); - } - return result; -} - -internal B32 -os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) -{ - U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - BOOL result = 0; - if(sleep_ms > 0) - { - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex_rw.u64[0]); - result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, - CONDITION_VARIABLE_LOCKMODE_SHARED); - } - return result; -} - -internal B32 -os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) -{ - U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - BOOL result = 0; - if(sleep_ms > 0) - { - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex_rw.u64[0]); - result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, 0); - } - return result; -} - -internal void -os_condition_variable_signal(OS_Handle cv) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - WakeConditionVariable(&entity->cv); -} - -internal void -os_condition_variable_broadcast(OS_Handle cv) -{ - OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); - WakeAllConditionVariable(&entity->cv); -} - -//- rjf: cross-process semaphores - -internal OS_Handle -os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE handle = CreateSemaphoreW(0, initial_count, max_count, (WCHAR *)name16.str); - OS_Handle result = {(U64)handle}; - scratch_end(scratch); - return result; -} - -internal void -os_semaphore_release(OS_Handle semaphore) -{ - HANDLE handle = (HANDLE)semaphore.u64[0]; - CloseHandle(handle); -} - -internal OS_Handle -os_semaphore_open(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE handle = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS , 0, (WCHAR *)name16.str); - OS_Handle result = {(U64)handle}; - scratch_end(scratch); - return result; -} - -internal void -os_semaphore_close(OS_Handle semaphore) -{ - HANDLE handle = (HANDLE)semaphore.u64[0]; - CloseHandle(handle); -} - -internal B32 -os_semaphore_take(OS_Handle semaphore, U64 endt_us) -{ - U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); - HANDLE handle = (HANDLE)semaphore.u64[0]; - DWORD wait_result = WaitForSingleObject(handle, sleep_ms); - B32 result = (wait_result == WAIT_OBJECT_0); - return result; -} - -internal void -os_semaphore_drop(OS_Handle semaphore) -{ - HANDLE handle = (HANDLE)semaphore.u64[0]; - ReleaseSemaphore(handle, 1, 0); -} - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal OS_Handle -os_library_open(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - HMODULE mod = LoadLibraryW((LPCWSTR)path16.str); - OS_Handle result = { (U64)mod }; - scratch_end(scratch); - return result; -} - -internal VoidProc* -os_library_load_proc(OS_Handle lib, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - HMODULE mod = (HMODULE)lib.u64[0]; - name = push_str8_copy(scratch.arena, name); - VoidProc *result = (VoidProc*)GetProcAddress(mod, (LPCSTR)name.str); - scratch_end(scratch); - return result; -} - -internal void -os_library_close(OS_Handle lib) -{ - HMODULE mod = (HMODULE)lib.u64[0]; - FreeLibrary(mod); -} - -//////////////////////////////// -//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) - -internal void -os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr) -{ - __try - { - func(ptr); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - if(fail_handler != 0) - { - fail_handler(ptr); - } - ExitProcess(1); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks GUIDs (Implemented Per-OS) - -internal Guid -os_make_guid(void) -{ - Guid result; MemoryZeroStruct(&result); - UUID uuid; - RPC_STATUS rpc_status = UuidCreate(&uuid); - if(rpc_status == RPC_S_OK) - { - result.data1 = uuid.Data1; - result.data2 = uuid.Data2; - result.data3 = uuid.Data3; - MemoryCopyArray(result.data4, uuid.Data4); - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Entry Points (Implemented Per-OS) - -#include -#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside -#include - -internal B32 win32_g_is_quiet = 0; - -internal HRESULT WINAPI -win32_dialog_callback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LONG_PTR data) -{ - if(msg == TDN_HYPERLINK_CLICKED) - { - ShellExecuteW(NULL, L"open", (LPWSTR)lparam, NULL, NULL, SW_SHOWNORMAL); - } - return S_OK; -} - -internal LONG WINAPI -win32_exception_filter(EXCEPTION_POINTERS* exception_ptrs) -{ - if(win32_g_is_quiet) - { - ExitProcess(1); - } - - static volatile LONG first = 0; - if(InterlockedCompareExchange(&first, 1, 0) != 0) - { - // prevent failures in other threads to popup same message box - // this handler just shows first thread that crashes - // we are terminating afterwards anyway - for (;;) Sleep(1000); - } - - WCHAR buffer[4096] = {0}; - int buflen = 0; - - DWORD exception_code = exception_ptrs->ExceptionRecord->ExceptionCode; - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"A fatal exception (code 0x%x) occurred. The process is terminating.\n", exception_code); - - // load dbghelp dynamically just in case if it is missing - HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); - if(dbghelp) - { - DWORD (WINAPI *dbg_SymSetOptions)(DWORD SymOptions); - BOOL (WINAPI *dbg_SymInitializeW)(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess); - BOOL (WINAPI *dbg_StackWalk64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, - LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, - PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, - PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); - PVOID (WINAPI *dbg_SymFunctionTableAccess64)(HANDLE hProcess, DWORD64 AddrBase); - DWORD64 (WINAPI *dbg_SymGetModuleBase64)(HANDLE hProcess, DWORD64 qwAddr); - BOOL (WINAPI *dbg_SymFromAddrW)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFOW Symbol); - BOOL (WINAPI *dbg_SymGetLineFromAddrW64)(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINEW64 Line); - BOOL (WINAPI *dbg_SymGetModuleInfoW64)(HANDLE hProcess, DWORD64 qwAddr, PIMAGEHLP_MODULEW64 ModuleInfo); - - *(FARPROC*)&dbg_SymSetOptions = GetProcAddress(dbghelp, "SymSetOptions"); - *(FARPROC*)&dbg_SymInitializeW = GetProcAddress(dbghelp, "SymInitializeW"); - *(FARPROC*)&dbg_StackWalk64 = GetProcAddress(dbghelp, "StackWalk64"); - *(FARPROC*)&dbg_SymFunctionTableAccess64 = GetProcAddress(dbghelp, "SymFunctionTableAccess64"); - *(FARPROC*)&dbg_SymGetModuleBase64 = GetProcAddress(dbghelp, "SymGetModuleBase64"); - *(FARPROC*)&dbg_SymFromAddrW = GetProcAddress(dbghelp, "SymFromAddrW"); - *(FARPROC*)&dbg_SymGetLineFromAddrW64 = GetProcAddress(dbghelp, "SymGetLineFromAddrW64"); - *(FARPROC*)&dbg_SymGetModuleInfoW64 = GetProcAddress(dbghelp, "SymGetModuleInfoW64"); - - if(dbg_SymSetOptions && dbg_SymInitializeW && dbg_StackWalk64 && dbg_SymFunctionTableAccess64 && dbg_SymGetModuleBase64 && dbg_SymFromAddrW && dbg_SymGetLineFromAddrW64 && dbg_SymGetModuleInfoW64) - { - HANDLE process = GetCurrentProcess(); - HANDLE thread = GetCurrentThread(); - CONTEXT* context = exception_ptrs->ContextRecord; - - WCHAR module_path[MAX_PATH]; - GetModuleFileNameW(NULL, module_path, ArrayCount(module_path)); - PathRemoveFileSpecW(module_path); - - dbg_SymSetOptions(SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); - if(dbg_SymInitializeW(process, module_path, TRUE)) - { - // check that raddbg.pdb file is good - B32 raddbg_pdb_valid = 0; - { - IMAGEHLP_MODULEW64 module = {0}; - module.SizeOfStruct = sizeof(module); - if(dbg_SymGetModuleInfoW64(process, (DWORD64)&win32_exception_filter, &module)) - { - raddbg_pdb_valid = (module.SymType == SymPdb); - } - } - - if(!raddbg_pdb_valid) - { - buflen += wnsprintfW(buffer + buflen, sizeof(buffer) - buflen, - L"\nThe PDB debug information file for this executable is not valid or was not found. Please rebuild binary to get the call stack.\n"); - } - else - { - STACKFRAME64 frame = {0}; - DWORD image_type; -#if defined(_M_AMD64) - image_type = IMAGE_FILE_MACHINE_AMD64; - frame.AddrPC.Offset = context->Rip; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context->Rbp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context->Rsp; - frame.AddrStack.Mode = AddrModeFlat; -#elif defined(_M_ARM64) - image_type = IMAGE_FILE_MACHINE_ARM64; - frame.AddrPC.Offset = context->Pc; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context->Fp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context->Sp; - frame.AddrStack.Mode = AddrModeFlat; -#else -# error Arch not supported! -#endif - - for(U32 idx=0; ;idx++) - { - const U32 max_frames = 32; - if(idx == max_frames) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"..."); - break; - } - - if(!dbg_StackWalk64(image_type, process, thread, &frame, context, 0, dbg_SymFunctionTableAccess64, dbg_SymGetModuleBase64, 0)) - { - break; - } - - U64 address = frame.AddrPC.Offset; - if(address == 0) - { - break; - } - - if(idx==0) - { -#if BUILD_CONSOLE_INTERFACE - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nCreate a new issue with this report at %S.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); -#else - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, - L"\nPress Ctrl+C to copy this text to clipboard, then create a new issue at\n" - L"%S\n\n", BUILD_ISSUES_LINK_STRING_LITERAL, BUILD_ISSUES_LINK_STRING_LITERAL); -#endif - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"Call stack:\n"); - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"%u. [0x%I64x]", idx + 1, address); - - struct { - SYMBOL_INFOW info; - WCHAR name[MAX_SYM_NAME]; - } symbol = {0}; - - symbol.info.SizeOfStruct = sizeof(symbol.info); - symbol.info.MaxNameLen = MAX_SYM_NAME; - - DWORD64 displacement = 0; - if(dbg_SymFromAddrW(process, address, &displacement, &symbol.info)) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s +%u", symbol.info.Name, (DWORD)displacement); - - IMAGEHLP_LINEW64 line = {0}; - line.SizeOfStruct = sizeof(line); - - DWORD line_displacement = 0; - if(dbg_SymGetLineFromAddrW64(process, address, &line_displacement, &line)) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L", %s line %u", PathFindFileNameW(line.FileName), line.LineNumber); - } - } - else - { - IMAGEHLP_MODULEW64 module = {0}; - module.SizeOfStruct = sizeof(module); - if(dbg_SymGetModuleInfoW64(process, address, &module)) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s", module.ModuleName); - } - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\n"); - } - } - } - } - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nVersion: %S%S", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); - -#if BUILD_CONSOLE_INTERFACE - fwprintf(stderr, L"\n--- Fatal Exception ---\n"); - fwprintf(stderr, L"%s\n\n", buffer); -#else - TASKDIALOGCONFIG dialog = {0}; - dialog.cbSize = sizeof(dialog); - dialog.dwFlags = TDF_SIZE_TO_CONTENT | TDF_ENABLE_HYPERLINKS | TDF_ALLOW_DIALOG_CANCELLATION; - dialog.pszMainIcon = TD_ERROR_ICON; - dialog.dwCommonButtons = TDCBF_CLOSE_BUTTON; - dialog.pszWindowTitle = L"Fatal Exception"; - dialog.pszContent = buffer; - dialog.pfCallback = &win32_dialog_callback; - TaskDialogIndirect(&dialog, 0, 0, 0); -#endif - - ExitProcess(1); -} - -#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside -#define OS_WINDOWS 1 - -internal void -w32_entry_point_caller(int argc, WCHAR **wargv) -{ - SetUnhandledExceptionFilter(&win32_exception_filter); - - //- rjf: dynamically load windows functions which are not guaranteed - // in all SDKs - { - HMODULE module = LoadLibraryA("kernel32.dll"); - w32_SetThreadDescription_func = (W32_SetThreadDescription_Type *)GetProcAddress(module, "SetThreadDescription"); - FreeLibrary(module); - } - - //- rjf: try to allow large pages if we can - B32 large_pages_allowed = 0; - { - HANDLE token; - if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) - { - LUID luid; - if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) - { - TOKEN_PRIVILEGES priv; - priv.PrivilegeCount = 1; - priv.Privileges[0].Luid = luid; - priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - large_pages_allowed = !!AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0); - } - CloseHandle(token); - } - } - - //- rjf: get system info - SYSTEM_INFO sysinfo = {0}; - GetSystemInfo(&sysinfo); - - //- rjf: set up non-dynamically-alloc'd state - // - // (we need to set up some basics before this layer can supply - // memory allocation primitives) - { - os_w32_state.microsecond_resolution = 1; - LARGE_INTEGER large_int_resolution; - if(QueryPerformanceFrequency(&large_int_resolution)) - { - os_w32_state.microsecond_resolution = large_int_resolution.QuadPart; - } - } - { - OS_SystemInfo *info = &os_w32_state.system_info; - info->logical_processor_count = (U64)sysinfo.dwNumberOfProcessors; - info->page_size = sysinfo.dwPageSize; - info->large_page_size = GetLargePageMinimum(); - info->allocation_granularity = sysinfo.dwAllocationGranularity; - } - { - OS_ProcessInfo *info = &os_w32_state.process_info; - info->large_pages_allowed = large_pages_allowed; - info->pid = GetCurrentProcessId(); - } - - //- rjf: extract arguments - Arena *args_arena = arena_alloc(.reserve_size = MB(1), .commit_size = KB(32)); - char **argv = push_array(args_arena, char *, argc); - for(int i = 0; i < argc; i += 1) - { - String16 arg16 = str16_cstring((U16 *)wargv[i]); - String8 arg8 = str8_from_16(args_arena, arg16); - if(str8_match(arg8, str8_lit("--quiet"), StringMatchFlag_CaseInsensitive) || - str8_match(arg8, str8_lit("-quiet"), StringMatchFlag_CaseInsensitive)) - { - win32_g_is_quiet = 1; - } - if(str8_match(arg8, str8_lit("--large_pages"), StringMatchFlag_CaseInsensitive) || - str8_match(arg8, str8_lit("-large_pages"), StringMatchFlag_CaseInsensitive)) - { - arena_default_flags = ArenaFlag_LargePages; - arena_default_reserve_size = Max(MB(64), os_w32_state.system_info.large_page_size); - arena_default_commit_size = arena_default_reserve_size; - } - argv[i] = (char *)arg8.str; - } - - //- rjf: set up thread context - local_persist TCTX tctx; - tctx_init_and_equip(&tctx); - - //- rjf: set up dynamically-alloc'd state - Arena *arena = arena_alloc(); - { - os_w32_state.arena = arena; - { - OS_SystemInfo *info = &os_w32_state.system_info; - U8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0}; - DWORD size = MAX_COMPUTERNAME_LENGTH + 1; - if(GetComputerNameA((char*)buffer, &size)) - { - info->machine_name = push_str8_copy(arena, str8(buffer, size)); - } - } - } - { - OS_ProcessInfo *info = &os_w32_state.process_info; - { - Temp scratch = scratch_begin(0, 0); - DWORD size = KB(32); - U16 *buffer = push_array_no_zero(scratch.arena, U16, size); - DWORD length = GetModuleFileNameW(0, (WCHAR*)buffer, size); - String8 name8 = str8_from_16(scratch.arena, str16(buffer, length)); - String8 name_chopped = str8_chop_last_slash(name8); - info->binary_path = push_str8_copy(arena, name_chopped); - scratch_end(scratch); - } - info->initial_path = os_get_current_path(arena); - { - Temp scratch = scratch_begin(0, 0); - U64 size = KB(32); - U16 *buffer = push_array_no_zero(scratch.arena, U16, size); - if(SUCCEEDED(SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, (WCHAR*)buffer))) - { - info->user_program_data_path = str8_from_16(arena, str16_cstring(buffer)); - } - scratch_end(scratch); - } - { - WCHAR *this_proc_env = GetEnvironmentStringsW(); - U64 start_idx = 0; - for(U64 idx = 0;; idx += 1) - { - if(this_proc_env[idx] == 0) - { - if(start_idx == idx) - { - break; - } - else - { - String16 string16 = str16((U16 *)this_proc_env + start_idx, idx - start_idx); - String8 string = str8_from_16(arena, string16); - str8_list_push(arena, &info->environment, string); - start_idx = idx+1; - } - } - } - } - } - - //- rjf: set up entity storage - InitializeCriticalSection(&os_w32_state.entity_mutex); - os_w32_state.entity_arena = arena_alloc(); - - //- rjf: call into "real" entry point - main_thread_base_entry_point(argc, argv); -} - -#if BUILD_CONSOLE_INTERFACE -int wmain(int argc, WCHAR **argv) -{ - w32_entry_point_caller(argc, argv); - return 0; -} -#else -int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) -{ - w32_entry_point_caller(__argc, __wargv); - return 0; -} -#endif +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ dan: Modern Windows SDK Functions +// +// (We must dynamically link to them, since they can be missing in older SDKs) + +typedef HRESULT W32_SetThreadDescription_Type(HANDLE hThread, PCWSTR lpThreadDescription); +global W32_SetThreadDescription_Type *w32_SetThreadDescription_func = 0; + +//////////////////////////////// +//~ dan: File Info Conversion Helpers + +internal FilePropertyFlags +os_w32_file_property_flags_from_dwFileAttributes(DWORD dwFileAttributes) +{ + FilePropertyFlags flags = 0; + if(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + flags |= FilePropertyFlag_IsFolder; + } + return flags; +} + +internal void +os_w32_file_properties_from_attribute_data(FileProperties *properties, WIN32_FILE_ATTRIBUTE_DATA *attributes) +{ + properties->size = Compose64Bit(attributes->nFileSizeHigh, attributes->nFileSizeLow); + os_w32_dense_time_from_file_time(&properties->created, &attributes->ftCreationTime); + os_w32_dense_time_from_file_time(&properties->modified, &attributes->ftLastWriteTime); + properties->flags = os_w32_file_property_flags_from_dwFileAttributes(attributes->dwFileAttributes); +} + +//////////////////////////////// +//~ dan: Time Conversion Helpers + +internal void +os_w32_date_time_from_system_time(DateTime *out, SYSTEMTIME *in) +{ + out->year = in->wYear; + out->mon = in->wMonth - 1; + out->wday = in->wDayOfWeek; + out->day = in->wDay; + out->hour = in->wHour; + out->min = in->wMinute; + out->sec = in->wSecond; + out->msec = in->wMilliseconds; +} + +internal void +os_w32_system_time_from_date_time(SYSTEMTIME *out, DateTime *in) +{ + out->wYear = (WORD)(in->year); + out->wMonth = in->mon + 1; + out->wDay = in->day; + out->wHour = in->hour; + out->wMinute = in->min; + out->wSecond = in->sec; + out->wMilliseconds = in->msec; +} + +internal void +os_w32_dense_time_from_file_time(DenseTime *out, FILETIME *in) +{ + SYSTEMTIME systime = {0}; + FileTimeToSystemTime(in, &systime); + DateTime date_time = {0}; + os_w32_date_time_from_system_time(&date_time, &systime); + *out = dense_time_from_date_time(date_time); +} + +internal U32 +os_w32_sleep_ms_from_endt_us(U64 endt_us) +{ + U32 sleep_ms = 0; + if(endt_us == max_U64) + { + sleep_ms = INFINITE; + } + else + { + U64 begint = os_now_microseconds(); + if(begint < endt_us) + { + U64 sleep_us = endt_us - begint; + sleep_ms = (U32)((sleep_us + 999)/1000); + } + } + return sleep_ms; +} + +internal U32 +os_w32_unix_time_from_file_time(FILETIME file_time) +{ + U64 win32_time = ((U64)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime; + U64 unix_time64 = ((win32_time - 0x19DB1DED53E8000ULL) / 10000000); + + Assert(unix_time64 <= max_U32); + U32 unix_time32 = (U32)unix_time64; + + return unix_time32; +} + +//////////////////////////////// +//~ dan: Entity Functions + +internal OS_W32_Entity * +os_w32_entity_alloc(OS_W32_EntityKind kind) +{ + OS_W32_Entity *result = 0; + EnterCriticalSection(&os_w32_state.entity_mutex); + { + result = os_w32_state.entity_free; + if(result) + { + SLLStackPop(os_w32_state.entity_free); + } + else + { + result = push_array_no_zero(os_w32_state.entity_arena, OS_W32_Entity, 1); + } + MemoryZeroStruct(result); + } + LeaveCriticalSection(&os_w32_state.entity_mutex); + result->kind = kind; + return result; +} + +internal void +os_w32_entity_release(OS_W32_Entity *entity) +{ + entity->kind = OS_W32_EntityKind_Null; + EnterCriticalSection(&os_w32_state.entity_mutex); + SLLStackPush(os_w32_state.entity_free, entity); + LeaveCriticalSection(&os_w32_state.entity_mutex); +} + +//////////////////////////////// +//~ dan: Thread Entry Point + +internal DWORD +os_w32_thread_entry_point(void *ptr) +{ + OS_W32_Entity *entity = (OS_W32_Entity *)ptr; + OS_ThreadFunctionType *func = entity->thread.func; + void *thread_ptr = entity->thread.ptr; + TCTX tctx_; + tctx_init_and_equip(&tctx_); + func(thread_ptr); + tctx_release(); + return 0; +} + +//////////////////////////////// +//~ dan: @os_hooks System/Process Info (Implemented Per-OS) + +internal OS_SystemInfo * +os_get_system_info(void) +{ + return &os_w32_state.system_info; +} + +internal OS_ProcessInfo * +os_get_process_info(void) +{ + return &os_w32_state.process_info; +} + +internal String8 +os_get_current_path(Arena *arena) +{ + Temp scratch = scratch_begin(&arena, 1); + DWORD length = GetCurrentDirectoryW(0, 0); + U16 *memory = push_array_no_zero(scratch.arena, U16, length + 1); + length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory); + String8 name = str8_from_16(arena, str16(memory, length)); + scratch_end(scratch); + return name; +} + +internal U32 +os_get_process_start_time_unix(void) +{ + HANDLE handle = GetCurrentProcess(); + FILETIME start_time = {0}; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + if(GetProcessTimes(handle, &start_time, &exit_time, &kernel_time, &user_time)) + { + return os_w32_unix_time_from_file_time(start_time); + } + return 0; +} + +//////////////////////////////// +//~ dan: @os_hooks Memory Allocation (Implemented Per-OS) + +//- rjf: basic + +internal void * +os_reserve(U64 size) +{ + void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); + return result; +} + +internal B32 +os_commit(void *ptr, U64 size) +{ + B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); + return result; +} + +internal void +os_decommit(void *ptr, U64 size) +{ + VirtualFree(ptr, size, MEM_DECOMMIT); +} + +internal void +os_release(void *ptr, U64 size) +{ + // NOTE(rjf): size not used - not necessary on Windows, but necessary for other OSes. + VirtualFree(ptr, 0, MEM_RELEASE); +} + +//- rjf: large pages + +internal void * +os_reserve_large(U64 size) +{ + // we commit on reserve because windows + void *result = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_LARGE_PAGES, PAGE_READWRITE); + return result; +} + +internal B32 +os_commit_large(void *ptr, U64 size) +{ + return 1; +} + +//////////////////////////////// +//~ dan: @os_hooks Thread Info (Implemented Per-OS) + +internal U32 +os_tid(void) +{ + DWORD id = GetCurrentThreadId(); + return (U32)id; +} + +internal void +os_set_thread_name(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + + // rjf: windows 10 style + if(w32_SetThreadDescription_func) + { + String16 name16 = str16_from_8(scratch.arena, name); + HRESULT hr = w32_SetThreadDescription_func(GetCurrentThread(), (WCHAR*)name16.str); + } + + // rjf: raise-exception style + { + String8 name_copy = push_str8_copy(scratch.arena, name); +#pragma pack(push,8) + typedef struct THREADNAME_INFO THREADNAME_INFO; + struct THREADNAME_INFO + { + U32 dwType; // Must be 0x1000. + char *szName; // Pointer to name (in user addr space). + U32 dwThreadID; // Thread ID (-1=caller thread). + U32 dwFlags; // Reserved for future use, must be zero. + }; +#pragma pack(pop) + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = (char *)name_copy.str; + info.dwThreadID = os_tid(); + info.dwFlags = 0; +#pragma warning(push) +#pragma warning(disable: 6320 6322) + __try + { + RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + } +#pragma warning(pop) + } + + scratch_end(scratch); +} + +//////////////////////////////// +//~ dan: @os_hooks Aborting (Implemented Per-OS) + +internal void +os_abort(S32 exit_code) +{ + ExitProcess(exit_code); +} + +//////////////////////////////// +//~ dan: @os_hooks File System (Implemented Per-OS) + +//- rjf: files + +internal OS_Handle +os_file_open(OS_AccessFlags flags, String8 path) +{ + OS_Handle result = {0}; + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + DWORD access_flags = 0; + DWORD share_mode = 0; + DWORD creation_disposition = OPEN_EXISTING; + SECURITY_ATTRIBUTES security_attributes = {sizeof(security_attributes), 0, 0}; + if(flags & OS_AccessFlag_Read) {access_flags |= GENERIC_READ;} + if(flags & OS_AccessFlag_Write) {access_flags |= GENERIC_WRITE;} + if(flags & OS_AccessFlag_Execute) {access_flags |= GENERIC_EXECUTE;} + if(flags & OS_AccessFlag_ShareRead) {share_mode |= FILE_SHARE_READ;} + if(flags & OS_AccessFlag_ShareWrite) {share_mode |= FILE_SHARE_WRITE|FILE_SHARE_DELETE;} + if(flags & OS_AccessFlag_Write) {creation_disposition = CREATE_ALWAYS;} + if(flags & OS_AccessFlag_Append) {creation_disposition = OPEN_ALWAYS; access_flags |= FILE_APPEND_DATA; } + if(flags & OS_AccessFlag_Inherited) + { + security_attributes.bInheritHandle = 1; + } + HANDLE file = CreateFileW((WCHAR *)path16.str, access_flags, share_mode, &security_attributes, creation_disposition, FILE_ATTRIBUTE_NORMAL, 0); + if(file != INVALID_HANDLE_VALUE) + { + result.u64[0] = (U64)file; + } + scratch_end(scratch); + return result; +} + +internal void +os_file_close(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { return; } + HANDLE handle = (HANDLE)file.u64[0]; + BOOL result = CloseHandle(handle); + (void)result; +} + +internal U64 +os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + HANDLE handle = (HANDLE)file.u64[0]; + + // rjf: clamp range by file size + U64 size = 0; + GetFileSizeEx(handle, (LARGE_INTEGER *)&size); + Rng1U64 rng_clamped = r1u64(ClampTop(rng.min, size), ClampTop(rng.max, size)); + U64 total_read_size = 0; + + // rjf: read loop + { + U64 to_read = dim_1u64(rng_clamped); + for(U64 off = rng.min; total_read_size < to_read;) + { + U64 amt64 = to_read - total_read_size; + U32 amt32 = u32_from_u64_saturate(amt64); + DWORD read_size = 0; + OVERLAPPED overlapped = {0}; + overlapped.Offset = (off&0x00000000ffffffffull); + overlapped.OffsetHigh = (off&0xffffffff00000000ull) >> 32; + ReadFile(handle, (U8 *)out_data + total_read_size, amt32, &read_size, &overlapped); + off += read_size; + total_read_size += read_size; + if(read_size != amt32) + { + break; + } + } + } + + return total_read_size; +} + +internal U64 +os_file_write(OS_Handle file, Rng1U64 rng, void *data) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + HANDLE win_handle = (HANDLE)file.u64[0]; + U64 src_off = 0; + U64 dst_off = rng.min; + U64 total_write_size = dim_1u64(rng); + for(;;) + { + void *bytes_src = (U8 *)data + src_off; + U64 bytes_left = total_write_size - src_off; + DWORD write_size = Min(MB(1), bytes_left); + DWORD bytes_written = 0; + OVERLAPPED overlapped = {0}; + overlapped.Offset = (dst_off&0x00000000ffffffffull); + overlapped.OffsetHigh = (dst_off&0xffffffff00000000ull) >> 32; + BOOL success = WriteFile(win_handle, bytes_src, write_size, &bytes_written, &overlapped); + if(success == 0) + { + break; + } + src_off += bytes_written; + dst_off += bytes_written; + if(bytes_left == 0) + { + break; + } + } + return src_off; +} + +internal B32 +os_file_set_time(OS_Handle file, DateTime time) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + B32 result = 0; + HANDLE handle = (HANDLE)file.u64[0]; + SYSTEMTIME system_time = {0}; + os_w32_system_time_from_date_time(&system_time, &time); + FILETIME file_time = {0}; + result = (SystemTimeToFileTime(&system_time, &file_time) && + SetFileTime(handle, &file_time, &file_time, &file_time)); + return result; +} + +internal FileProperties +os_properties_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { FileProperties r = {0}; return r; } + FileProperties props = {0}; + HANDLE handle = (HANDLE)file.u64[0]; + BY_HANDLE_FILE_INFORMATION info; + BOOL info_good = GetFileInformationByHandle(handle, &info); + if(info_good) + { + U32 size_lo = info.nFileSizeLow; + U32 size_hi = info.nFileSizeHigh; + props.size = (U64)size_lo | (((U64)size_hi)<<32); + os_w32_dense_time_from_file_time(&props.modified, &info.ftLastWriteTime); + os_w32_dense_time_from_file_time(&props.created, &info.ftCreationTime); + props.flags = os_w32_file_property_flags_from_dwFileAttributes(info.dwFileAttributes); + } + return props; +} + +internal OS_FileID +os_id_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { OS_FileID r = {0}; return r; } + OS_FileID result = {0}; + HANDLE handle = (HANDLE)file.u64[0]; + BY_HANDLE_FILE_INFORMATION info; + BOOL is_ok = GetFileInformationByHandle(handle, &info); + if(is_ok) + { + result.v[0] = info.dwVolumeSerialNumber; + result.v[1] = info.nFileIndexLow; + result.v[2] = info.nFileIndexHigh; + } + return result; +} + +internal B32 +os_delete_file_at_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + B32 result = DeleteFileW((WCHAR*)path16.str); + scratch_end(scratch); + return result; +} + +internal B32 +os_copy_file_path(String8 dst, String8 src) +{ + Temp scratch = scratch_begin(0, 0); + String16 dst16 = str16_from_8(scratch.arena, dst); + String16 src16 = str16_from_8(scratch.arena, src); + B32 result = CopyFileW((WCHAR*)src16.str, (WCHAR*)dst16.str, 0); + scratch_end(scratch); + return result; +} + +internal String8 +os_full_path_from_path(Arena *arena, String8 path) +{ + Temp scratch = scratch_begin(&arena, 1); + DWORD buffer_size = Max(MAX_PATH, path.size * 2) + 1; + String16 path16 = str16_from_8(scratch.arena, path); + WCHAR *buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size); + DWORD path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL); + if(path16_size > buffer_size) + { + arena_pop(scratch.arena, buffer_size); + buffer_size = path16_size + 1; + buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size); + path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL); + } + String8 full_path = str8_from_16(arena, str16((U16*)buffer, path16_size)); + scratch_end(scratch); + return full_path; +} + +internal B32 +os_file_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0,0); + String16 path16 = str16_from_8(scratch.arena, path); + DWORD attributes = GetFileAttributesW((WCHAR *)path16.str); + B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && !!(~attributes & FILE_ATTRIBUTE_DIRECTORY); + scratch_end(scratch); + return exists; +} + +internal B32 +os_folder_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0,0); + String16 path16 = str16_from_8(scratch.arena, path); + DWORD attributes = GetFileAttributesW((WCHAR *)path16.str); + B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY); + scratch_end(scratch); + return exists; +} + +internal FileProperties +os_properties_from_file_path(String8 path) +{ + WIN32_FIND_DATAW find_data = {0}; + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + HANDLE handle = FindFirstFileW((WCHAR *)path16.str, &find_data); + FileProperties props = {0}; + if(handle != INVALID_HANDLE_VALUE) + { + props.size = Compose64Bit(find_data.nFileSizeHigh, find_data.nFileSizeLow); + os_w32_dense_time_from_file_time(&props.created, &find_data.ftCreationTime); + os_w32_dense_time_from_file_time(&props.modified, &find_data.ftLastWriteTime); + props.flags = os_w32_file_property_flags_from_dwFileAttributes(find_data.dwFileAttributes); + } + FindClose(handle); + scratch_end(scratch); + return props; +} + +//- rjf: file maps + +internal OS_Handle +os_file_map_open(OS_AccessFlags flags, OS_Handle file) +{ + OS_Handle map = {0}; + { + HANDLE file_handle = (HANDLE)file.u64[0]; + DWORD protect_flags = 0; + { + switch(flags) + { + default:{}break; + case OS_AccessFlag_Read: + {protect_flags = PAGE_READONLY;}break; + case OS_AccessFlag_Write: + case OS_AccessFlag_Read|OS_AccessFlag_Write: + {protect_flags = PAGE_READWRITE;}break; + case OS_AccessFlag_Execute: + case OS_AccessFlag_Read|OS_AccessFlag_Execute: + {protect_flags = PAGE_EXECUTE_READ;}break; + case OS_AccessFlag_Execute|OS_AccessFlag_Write|OS_AccessFlag_Read: + case OS_AccessFlag_Execute|OS_AccessFlag_Write: + {protect_flags = PAGE_EXECUTE_READWRITE;}break; + } + } + HANDLE map_handle = CreateFileMappingA(file_handle, 0, protect_flags, 0, 0, 0); + map.u64[0] = (U64)map_handle; + } + return map; +} + +internal void +os_file_map_close(OS_Handle map) +{ + HANDLE handle = (HANDLE)map.u64[0]; + BOOL result = CloseHandle(handle); + (void)result; +} + +internal void * +os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) +{ + HANDLE handle = (HANDLE)map.u64[0]; + U32 off_lo = (U32)((range.min&0x00000000ffffffffull)>>0); + U32 off_hi = (U32)((range.min&0xffffffff00000000ull)>>32); + U64 size = dim_1u64(range); + DWORD access_flags = 0; + { + switch(flags) + { + default:{}break; + case OS_AccessFlag_Read: + { + access_flags = FILE_MAP_READ; + }break; + case OS_AccessFlag_Write: + { + access_flags = FILE_MAP_WRITE; + }break; + case OS_AccessFlag_Read|OS_AccessFlag_Write: + { + access_flags = FILE_MAP_ALL_ACCESS; + }break; + case OS_AccessFlag_Execute: + case OS_AccessFlag_Read|OS_AccessFlag_Execute: + case OS_AccessFlag_Write|OS_AccessFlag_Execute: + case OS_AccessFlag_Read|OS_AccessFlag_Write|OS_AccessFlag_Execute: + { + access_flags = FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE; + }break; + } + } + void *result = MapViewOfFile(handle, access_flags, off_hi, off_lo, size); + return result; +} + +internal void +os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range) +{ + BOOL result = UnmapViewOfFile(ptr); + (void)result; +} + +//- rjf: directory iteration + +internal OS_FileIter * +os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 path_with_wildcard = push_str8_cat(scratch.arena, path, str8_lit("\\*")); + String16 path16 = str16_from_8(scratch.arena, path_with_wildcard); + OS_FileIter *iter = push_array(arena, OS_FileIter, 1); + iter->flags = flags; + OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; + if(path.size == 0) + { + w32_iter->is_volume_iter = 1; + WCHAR buffer[512] = {0}; + DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer); + String8List drive_strings = {0}; + for(U64 off = 0; off < (U64)length;) + { + String16 next_drive_string_16 = str16_cstring((U16 *)buffer+off); + off += next_drive_string_16.size+1; + String8 next_drive_string = str8_from_16(arena, next_drive_string_16); + next_drive_string = str8_chop_last_slash(next_drive_string); + str8_list_push(scratch.arena, &drive_strings, next_drive_string); + } + w32_iter->drive_strings = str8_array_from_list(arena, &drive_strings); + w32_iter->drive_strings_iter_idx = 0; + } + else + { + w32_iter->handle = FindFirstFileW((WCHAR*)path16.str, &w32_iter->find_data); + } + scratch_end(scratch); + return iter; +} + +internal B32 +os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) +{ + B32 result = 0; + OS_FileIterFlags flags = iter->flags; + OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; + switch(w32_iter->is_volume_iter) + { + //- rjf: file iteration + default: + case 0: + { + if (!(flags & OS_FileIterFlag_Done) && w32_iter->handle != INVALID_HANDLE_VALUE) + { + do + { + // check is usable + B32 usable_file = 1; + + WCHAR *file_name = w32_iter->find_data.cFileName; + DWORD attributes = w32_iter->find_data.dwFileAttributes; + if (file_name[0] == '.'){ + if (flags & OS_FileIterFlag_SkipHiddenFiles){ + usable_file = 0; + } + else if (file_name[1] == 0){ + usable_file = 0; + } + else if (file_name[1] == '.' && file_name[2] == 0){ + usable_file = 0; + } + } + if (attributes & FILE_ATTRIBUTE_DIRECTORY){ + if (flags & OS_FileIterFlag_SkipFolders){ + usable_file = 0; + } + } + else{ + if (flags & OS_FileIterFlag_SkipFiles){ + usable_file = 0; + } + } + + // emit if usable + if (usable_file){ + info_out->name = str8_from_16(arena, str16_cstring((U16*)file_name)); + info_out->props.size = (U64)w32_iter->find_data.nFileSizeLow | (((U64)w32_iter->find_data.nFileSizeHigh)<<32); + os_w32_dense_time_from_file_time(&info_out->props.created, &w32_iter->find_data.ftCreationTime); + os_w32_dense_time_from_file_time(&info_out->props.modified, &w32_iter->find_data.ftLastWriteTime); + info_out->props.flags = os_w32_file_property_flags_from_dwFileAttributes(attributes); + result = 1; + if (!FindNextFileW(w32_iter->handle, &w32_iter->find_data)){ + iter->flags |= OS_FileIterFlag_Done; + } + break; + } + }while(FindNextFileW(w32_iter->handle, &w32_iter->find_data)); + } + }break; + + //- rjf: volume iteration + case 1: + { + result = w32_iter->drive_strings_iter_idx < w32_iter->drive_strings.count; + if(result != 0) + { + MemoryZeroStruct(info_out); + info_out->name = w32_iter->drive_strings.v[w32_iter->drive_strings_iter_idx]; + info_out->props.flags |= FilePropertyFlag_IsFolder; + w32_iter->drive_strings_iter_idx += 1; + } + }break; + } + if(!result) + { + iter->flags |= OS_FileIterFlag_Done; + } + return result; +} + +internal void +os_file_iter_end(OS_FileIter *iter) +{ + OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; + HANDLE zero_handle; + MemoryZeroStruct(&zero_handle); + if(!MemoryMatchStruct(&zero_handle, &w32_iter->handle)) + { + FindClose(w32_iter->handle); + } +} + +//- rjf: directory creation + +internal B32 +os_make_directory(String8 path) +{ + B32 result = 0; + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, path); + WIN32_FILE_ATTRIBUTE_DATA attributes = {0}; + GetFileAttributesExW((WCHAR*)name16.str, GetFileExInfoStandard, &attributes); + if(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + result = 1; + } + else if(CreateDirectoryW((WCHAR*)name16.str, 0)) + { + result = 1; + } + scratch_end(scratch); + return(result); +} + +//////////////////////////////// +//~ dan: @os_hooks Shared Memory (Implemented Per-OS) + +internal OS_Handle +os_shared_memory_alloc(U64 size, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE file = CreateFileMappingW(INVALID_HANDLE_VALUE, + 0, + PAGE_READWRITE, + (U32)((size & 0xffffffff00000000) >> 32), + (U32)((size & 0x00000000ffffffff)), + (WCHAR *)name16.str); + OS_Handle result = {(U64)file}; + scratch_end(scratch); + return result; +} + +internal OS_Handle +os_shared_memory_open(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE file = OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, (WCHAR *)name16.str); + OS_Handle result = {(U64)file}; + scratch_end(scratch); + return result; +} + +internal void +os_shared_memory_close(OS_Handle handle) +{ + HANDLE file = (HANDLE)(handle.u64[0]); + CloseHandle(file); +} + +internal void * +os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) +{ + HANDLE file = (HANDLE)(handle.u64[0]); + U64 offset = range.min; + U64 size = range.max-range.min; + void *ptr = MapViewOfFile(file, FILE_MAP_ALL_ACCESS, + (U32)((offset & 0xffffffff00000000) >> 32), + (U32)((offset & 0x00000000ffffffff)), + size); + return ptr; +} + +internal void +os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range) +{ + UnmapViewOfFile(ptr); +} + +//////////////////////////////// +//~ dan: @os_hooks Time (Implemented Per-OS) + +internal U64 +os_now_microseconds(void) +{ + U64 result = 0; + LARGE_INTEGER large_int_counter; + if(QueryPerformanceCounter(&large_int_counter)) + { + result = (large_int_counter.QuadPart*Million(1))/os_w32_state.microsecond_resolution; + } + return result; +} + +internal U32 +os_now_unix(void) +{ + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + U32 unix_time = os_w32_unix_time_from_file_time(file_time); + return unix_time; +} + +internal DateTime +os_now_universal_time(void) +{ + SYSTEMTIME systime = {0}; + GetSystemTime(&systime); + DateTime result = {0}; + os_w32_date_time_from_system_time(&result, &systime); + return result; +} + +internal DateTime +os_universal_time_from_local(DateTime *date_time) +{ + SYSTEMTIME systime = {0}; + os_w32_system_time_from_date_time(&systime, date_time); + FILETIME ftime = {0}; + SystemTimeToFileTime(&systime, &ftime); + FILETIME ftime_local = {0}; + LocalFileTimeToFileTime(&ftime, &ftime_local); + FileTimeToSystemTime(&ftime_local, &systime); + DateTime result = {0}; + os_w32_date_time_from_system_time(&result, &systime); + return result; +} + +internal DateTime +os_local_time_from_universal(DateTime *date_time) +{ + SYSTEMTIME systime = {0}; + os_w32_system_time_from_date_time(&systime, date_time); + FILETIME ftime = {0}; + SystemTimeToFileTime(&systime, &ftime); + FILETIME ftime_local = {0}; + FileTimeToLocalFileTime(&ftime, &ftime_local); + FileTimeToSystemTime(&ftime_local, &systime); + DateTime result = {0}; + os_w32_date_time_from_system_time(&result, &systime); + return result; +} + +internal void +os_sleep_milliseconds(U32 msec) +{ + Sleep(msec); +} + +//////////////////////////////// +//~ dan: @os_hooks Child Processes (Implemented Per-OS) + +internal OS_Handle +os_process_launch(OS_ProcessLaunchParams *params) +{ + OS_Handle result = {0}; + Temp scratch = scratch_begin(0, 0); + + //- rjf: form full command string + String8 cmd = {0}; + { + StringJoin join_params = {0}; + join_params.pre = str8_lit("\""); + join_params.sep = str8_lit("\" \""); + join_params.post = str8_lit("\""); + cmd = str8_list_join(scratch.arena, ¶ms->cmd_line, &join_params); + } + + //- rjf: form environment + B32 use_null_env_arg = 0; + String8 env = {0}; + { + StringJoin join_params2 = {0}; + join_params2.sep = str8_lit("\0"); + join_params2.post = str8_lit("\0"); + String8List all_opts = params->env; + if(params->inherit_env != 0) + { + if(all_opts.node_count != 0) + { + MemoryZeroStruct(&all_opts); + for(String8Node *n = params->env.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + for(String8Node *n = os_w32_state.process_info.environment.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + } + else + { + use_null_env_arg = 1; + } + } + if(use_null_env_arg == 0) + { + env = str8_list_join(scratch.arena, &all_opts, &join_params2); + } + } + + //- rjf: utf-8 -> utf-16 + String16 cmd16 = str16_from_8(scratch.arena, cmd); + String16 dir16 = str16_from_8(scratch.arena, params->path); + String16 env16 = {0}; + if(use_null_env_arg == 0) + { + env16 = str16_from_8(scratch.arena, env); + } + + //- rjf: determine creation flags + DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; + if(params->consoleless) + { + creation_flags |= CREATE_NO_WINDOW; + } + + //- rjf: launch + BOOL inherit_handles = 0; + STARTUPINFOW startup_info = {sizeof(startup_info)}; + if(!os_handle_match(params->stdout_file, os_handle_zero())) + { + HANDLE stdout_handle = (HANDLE)params->stdout_file.u64[0]; + startup_info.hStdOutput = stdout_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + if(!os_handle_match(params->stderr_file, os_handle_zero())) + { + HANDLE stderr_handle = (HANDLE)params->stderr_file.u64[0]; + startup_info.hStdError = stderr_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + if(!os_handle_match(params->stdin_file, os_handle_zero())) + { + HANDLE stdin_handle = (HANDLE)params->stdin_file.u64[0]; + startup_info.hStdInput = stdin_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + PROCESS_INFORMATION process_info = {0}; + if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, inherit_handles, creation_flags, use_null_env_arg ? 0 : (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) + { + result.u64[0] = (U64)process_info.hProcess; + CloseHandle(process_info.hThread); + } + + scratch_end(scratch); + return result; +} + +internal B32 +os_process_join(OS_Handle handle, U64 endt_us) +{ + HANDLE process = (HANDLE)(handle.u64[0]); + DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + DWORD result = WaitForSingleObject(process, sleep_ms); + return (result == WAIT_OBJECT_0); +} + +internal void +os_process_detach(OS_Handle handle) +{ + HANDLE process = (HANDLE)(handle.u64[0]); + CloseHandle(process); +} + +//////////////////////////////// +//~ dan: @os_hooks Threads (Implemented Per-OS) + +internal OS_Handle +os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Thread); + entity->thread.func = func; + entity->thread.ptr = ptr; + entity->thread.handle = CreateThread(0, 0, os_w32_thread_entry_point, entity, 0, &entity->thread.tid); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal B32 +os_thread_join(OS_Handle handle, U64 endt_us) +{ + DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + OS_W32_Entity *entity = (OS_W32_Entity *)PtrFromInt(handle.u64[0]); + DWORD wait_result = WAIT_OBJECT_0; + if(entity != 0) + { + wait_result = WaitForSingleObject(entity->thread.handle, sleep_ms); + CloseHandle(entity->thread.handle); + os_w32_entity_release(entity); + } + return (wait_result == WAIT_OBJECT_0); +} + +internal void +os_thread_detach(OS_Handle thread) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(thread.u64[0]); + if(entity != 0) + { + CloseHandle(entity->thread.handle); + os_w32_entity_release(entity); + } +} + +//////////////////////////////// +//~ dan: @os_hooks Synchronization Primitives (Implemented Per-OS) + +//- rjf: mutexes + +internal OS_Handle +os_mutex_alloc(void) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Mutex); + InitializeCriticalSection(&entity->mutex); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal void +os_mutex_release(OS_Handle mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + os_w32_entity_release(entity); +} + +internal void +os_mutex_take(OS_Handle mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + EnterCriticalSection(&entity->mutex); +} + +internal void +os_mutex_drop(OS_Handle mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + LeaveCriticalSection(&entity->mutex); +} + +//- rjf: reader/writer mutexes + +internal OS_Handle +os_rw_mutex_alloc(void) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_RWMutex); + InitializeSRWLock(&entity->rw_mutex); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal void +os_rw_mutex_release(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + os_w32_entity_release(entity); +} + +internal void +os_rw_mutex_take_r(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + AcquireSRWLockShared(&entity->rw_mutex); +} + +internal void +os_rw_mutex_drop_r(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + ReleaseSRWLockShared(&entity->rw_mutex); +} + +internal void +os_rw_mutex_take_w(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + AcquireSRWLockExclusive(&entity->rw_mutex); +} + +internal void +os_rw_mutex_drop_w(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + ReleaseSRWLockExclusive(&entity->rw_mutex); +} + +//- rjf: condition variables + +internal OS_Handle +os_condition_variable_alloc(void) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_ConditionVariable); + InitializeConditionVariable(&entity->cv); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal void +os_condition_variable_release(OS_Handle cv) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + os_w32_entity_release(entity); +} + +internal B32 +os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + BOOL result = 0; + if(sleep_ms > 0) + { + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + result = SleepConditionVariableCS(&entity->cv, &mutex_entity->mutex, sleep_ms); + } + return result; +} + +internal B32 +os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + BOOL result = 0; + if(sleep_ms > 0) + { + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex_rw.u64[0]); + result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, + CONDITION_VARIABLE_LOCKMODE_SHARED); + } + return result; +} + +internal B32 +os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + BOOL result = 0; + if(sleep_ms > 0) + { + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex_rw.u64[0]); + result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, 0); + } + return result; +} + +internal void +os_condition_variable_signal(OS_Handle cv) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + WakeConditionVariable(&entity->cv); +} + +internal void +os_condition_variable_broadcast(OS_Handle cv) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + WakeAllConditionVariable(&entity->cv); +} + +//- rjf: cross-process semaphores + +internal OS_Handle +os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE handle = CreateSemaphoreW(0, initial_count, max_count, (WCHAR *)name16.str); + OS_Handle result = {(U64)handle}; + scratch_end(scratch); + return result; +} + +internal void +os_semaphore_release(OS_Handle semaphore) +{ + HANDLE handle = (HANDLE)semaphore.u64[0]; + CloseHandle(handle); +} + +internal OS_Handle +os_semaphore_open(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE handle = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS , 0, (WCHAR *)name16.str); + OS_Handle result = {(U64)handle}; + scratch_end(scratch); + return result; +} + +internal void +os_semaphore_close(OS_Handle semaphore) +{ + HANDLE handle = (HANDLE)semaphore.u64[0]; + CloseHandle(handle); +} + +internal B32 +os_semaphore_take(OS_Handle semaphore, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + HANDLE handle = (HANDLE)semaphore.u64[0]; + DWORD wait_result = WaitForSingleObject(handle, sleep_ms); + B32 result = (wait_result == WAIT_OBJECT_0); + return result; +} + +internal void +os_semaphore_drop(OS_Handle semaphore) +{ + HANDLE handle = (HANDLE)semaphore.u64[0]; + ReleaseSemaphore(handle, 1, 0); +} + +//////////////////////////////// +//~ dan: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) + +internal OS_Handle +os_library_open(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + HMODULE mod = LoadLibraryW((LPCWSTR)path16.str); + OS_Handle result = { (U64)mod }; + scratch_end(scratch); + return result; +} + +internal VoidProc* +os_library_load_proc(OS_Handle lib, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + HMODULE mod = (HMODULE)lib.u64[0]; + name = push_str8_copy(scratch.arena, name); + VoidProc *result = (VoidProc*)GetProcAddress(mod, (LPCSTR)name.str); + scratch_end(scratch); + return result; +} + +internal void +os_library_close(OS_Handle lib) +{ + HMODULE mod = (HMODULE)lib.u64[0]; + FreeLibrary(mod); +} + +//////////////////////////////// +//~ dan: @os_hooks Safe Calls (Implemented Per-OS) + +internal void +os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr) +{ + __try + { + func(ptr); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + if(fail_handler != 0) + { + fail_handler(ptr); + } + ExitProcess(1); + } +} + +//////////////////////////////// +//~ dan: @os_hooks GUIDs (Implemented Per-OS) + +internal Guid +os_make_guid(void) +{ + Guid result; MemoryZeroStruct(&result); + UUID uuid; + RPC_STATUS rpc_status = UuidCreate(&uuid); + if(rpc_status == RPC_S_OK) + { + result.data1 = uuid.Data1; + result.data2 = uuid.Data2; + result.data3 = uuid.Data3; + MemoryCopyArray(result.data4, uuid.Data4); + } + return result; +} + +//////////////////////////////// +//~ dan: @os_hooks Entry Points (Implemented Per-OS) + +#include +#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside +#include + +internal B32 win32_g_is_quiet = 0; + +internal HRESULT WINAPI +win32_dialog_callback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LONG_PTR data) +{ + if(msg == TDN_HYPERLINK_CLICKED) + { + ShellExecuteW(NULL, L"open", (LPWSTR)lparam, NULL, NULL, SW_SHOWNORMAL); + } + return S_OK; +} + +internal LONG WINAPI +win32_exception_filter(EXCEPTION_POINTERS* exception_ptrs) +{ + if(win32_g_is_quiet) + { + ExitProcess(1); + } + + static volatile LONG first = 0; + if(InterlockedCompareExchange(&first, 1, 0) != 0) + { + // prevent failures in other threads to popup same message box + // this handler just shows first thread that crashes + // we are terminating afterwards anyway + for (;;) Sleep(1000); + } + + WCHAR buffer[4096] = {0}; + int buflen = 0; + + DWORD exception_code = exception_ptrs->ExceptionRecord->ExceptionCode; + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"A fatal exception (code 0x%x) occurred. The process is terminating.\n", exception_code); + + // load dbghelp dynamically just in case if it is missing + HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); + if(dbghelp) + { + DWORD (WINAPI *dbg_SymSetOptions)(DWORD SymOptions); + BOOL (WINAPI *dbg_SymInitializeW)(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess); + BOOL (WINAPI *dbg_StackWalk64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, + LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); + PVOID (WINAPI *dbg_SymFunctionTableAccess64)(HANDLE hProcess, DWORD64 AddrBase); + DWORD64 (WINAPI *dbg_SymGetModuleBase64)(HANDLE hProcess, DWORD64 qwAddr); + BOOL (WINAPI *dbg_SymFromAddrW)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFOW Symbol); + BOOL (WINAPI *dbg_SymGetLineFromAddrW64)(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINEW64 Line); + BOOL (WINAPI *dbg_SymGetModuleInfoW64)(HANDLE hProcess, DWORD64 qwAddr, PIMAGEHLP_MODULEW64 ModuleInfo); + + *(FARPROC*)&dbg_SymSetOptions = GetProcAddress(dbghelp, "SymSetOptions"); + *(FARPROC*)&dbg_SymInitializeW = GetProcAddress(dbghelp, "SymInitializeW"); + *(FARPROC*)&dbg_StackWalk64 = GetProcAddress(dbghelp, "StackWalk64"); + *(FARPROC*)&dbg_SymFunctionTableAccess64 = GetProcAddress(dbghelp, "SymFunctionTableAccess64"); + *(FARPROC*)&dbg_SymGetModuleBase64 = GetProcAddress(dbghelp, "SymGetModuleBase64"); + *(FARPROC*)&dbg_SymFromAddrW = GetProcAddress(dbghelp, "SymFromAddrW"); + *(FARPROC*)&dbg_SymGetLineFromAddrW64 = GetProcAddress(dbghelp, "SymGetLineFromAddrW64"); + *(FARPROC*)&dbg_SymGetModuleInfoW64 = GetProcAddress(dbghelp, "SymGetModuleInfoW64"); + + if(dbg_SymSetOptions && dbg_SymInitializeW && dbg_StackWalk64 && dbg_SymFunctionTableAccess64 && dbg_SymGetModuleBase64 && dbg_SymFromAddrW && dbg_SymGetLineFromAddrW64 && dbg_SymGetModuleInfoW64) + { + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + CONTEXT* context = exception_ptrs->ContextRecord; + + WCHAR module_path[MAX_PATH]; + GetModuleFileNameW(NULL, module_path, ArrayCount(module_path)); + PathRemoveFileSpecW(module_path); + + dbg_SymSetOptions(SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + if(dbg_SymInitializeW(process, module_path, TRUE)) + { + // check that raddbg.pdb file is good + B32 raddbg_pdb_valid = 0; + { + IMAGEHLP_MODULEW64 module = {0}; + module.SizeOfStruct = sizeof(module); + if(dbg_SymGetModuleInfoW64(process, (DWORD64)&win32_exception_filter, &module)) + { + raddbg_pdb_valid = (module.SymType == SymPdb); + } + } + + if(!raddbg_pdb_valid) + { + buflen += wnsprintfW(buffer + buflen, sizeof(buffer) - buflen, + L"\nThe PDB debug information file for this executable is not valid or was not found. Please rebuild binary to get the call stack.\n"); + } + else + { + STACKFRAME64 frame = {0}; + DWORD image_type; +#if defined(_M_AMD64) + image_type = IMAGE_FILE_MACHINE_AMD64; + frame.AddrPC.Offset = context->Rip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context->Rbp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context->Rsp; + frame.AddrStack.Mode = AddrModeFlat; +#elif defined(_M_ARM64) + image_type = IMAGE_FILE_MACHINE_ARM64; + frame.AddrPC.Offset = context->Pc; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context->Fp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context->Sp; + frame.AddrStack.Mode = AddrModeFlat; +#else +# error Arch not supported! +#endif + + for(U32 idx=0; ;idx++) + { + const U32 max_frames = 32; + if(idx == max_frames) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"..."); + break; + } + + if(!dbg_StackWalk64(image_type, process, thread, &frame, context, 0, dbg_SymFunctionTableAccess64, dbg_SymGetModuleBase64, 0)) + { + break; + } + + U64 address = frame.AddrPC.Offset; + if(address == 0) + { + break; + } + + if(idx==0) + { +#if BUILD_CONSOLE_INTERFACE + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nCreate a new issue with this report at %S.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); +#else + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, + L"\nPress Ctrl+C to copy this text to clipboard, then create a new issue at\n" + L"%S\n\n", BUILD_ISSUES_LINK_STRING_LITERAL, BUILD_ISSUES_LINK_STRING_LITERAL); +#endif + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"Call stack:\n"); + } + + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"%u. [0x%I64x]", idx + 1, address); + + struct { + SYMBOL_INFOW info; + WCHAR name[MAX_SYM_NAME]; + } symbol = {0}; + + symbol.info.SizeOfStruct = sizeof(symbol.info); + symbol.info.MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement = 0; + if(dbg_SymFromAddrW(process, address, &displacement, &symbol.info)) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s +%u", symbol.info.Name, (DWORD)displacement); + + IMAGEHLP_LINEW64 line = {0}; + line.SizeOfStruct = sizeof(line); + + DWORD line_displacement = 0; + if(dbg_SymGetLineFromAddrW64(process, address, &line_displacement, &line)) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L", %s line %u", PathFindFileNameW(line.FileName), line.LineNumber); + } + } + else + { + IMAGEHLP_MODULEW64 module = {0}; + module.SizeOfStruct = sizeof(module); + if(dbg_SymGetModuleInfoW64(process, address, &module)) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s", module.ModuleName); + } + } + + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\n"); + } + } + } + } + } + + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nVersion: %S%S", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); + +#if BUILD_CONSOLE_INTERFACE + fwprintf(stderr, L"\n--- Fatal Exception ---\n"); + fwprintf(stderr, L"%s\n\n", buffer); +#else + TASKDIALOGCONFIG dialog = {0}; + dialog.cbSize = sizeof(dialog); + dialog.dwFlags = TDF_SIZE_TO_CONTENT | TDF_ENABLE_HYPERLINKS | TDF_ALLOW_DIALOG_CANCELLATION; + dialog.pszMainIcon = TD_ERROR_ICON; + dialog.dwCommonButtons = TDCBF_CLOSE_BUTTON; + dialog.pszWindowTitle = L"Fatal Exception"; + dialog.pszContent = buffer; + dialog.pfCallback = &win32_dialog_callback; + TaskDialogIndirect(&dialog, 0, 0, 0); +#endif + + ExitProcess(1); +} + +#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside +#define OS_WINDOWS 1 + +internal void +w32_entry_point_caller(int argc, WCHAR **wargv) +{ + SetUnhandledExceptionFilter(&win32_exception_filter); + + //- rjf: dynamically load windows functions which are not guaranteed + // in all SDKs + { + HMODULE module = LoadLibraryA("kernel32.dll"); + w32_SetThreadDescription_func = (W32_SetThreadDescription_Type *)GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); + } + + //- rjf: try to allow large pages if we can + B32 large_pages_allowed = 0; + { + HANDLE token; + if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) + { + LUID luid; + if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) + { + TOKEN_PRIVILEGES priv; + priv.PrivilegeCount = 1; + priv.Privileges[0].Luid = luid; + priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + large_pages_allowed = !!AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0); + } + CloseHandle(token); + } + } + + //- rjf: get system info + SYSTEM_INFO sysinfo = {0}; + GetSystemInfo(&sysinfo); + + //- rjf: set up non-dynamically-alloc'd state + // + // (we need to set up some basics before this layer can supply + // memory allocation primitives) + { + os_w32_state.microsecond_resolution = 1; + LARGE_INTEGER large_int_resolution; + if(QueryPerformanceFrequency(&large_int_resolution)) + { + os_w32_state.microsecond_resolution = large_int_resolution.QuadPart; + } + } + { + OS_SystemInfo *info = &os_w32_state.system_info; + info->logical_processor_count = (U64)sysinfo.dwNumberOfProcessors; + info->page_size = sysinfo.dwPageSize; + info->large_page_size = GetLargePageMinimum(); + info->allocation_granularity = sysinfo.dwAllocationGranularity; + } + { + OS_ProcessInfo *info = &os_w32_state.process_info; + info->large_pages_allowed = large_pages_allowed; + info->pid = GetCurrentProcessId(); + } + + //- rjf: extract arguments + Arena *args_arena = arena_alloc(.reserve_size = MB(1), .commit_size = KB(32)); + char **argv = push_array(args_arena, char *, argc); + for(int i = 0; i < argc; i += 1) + { + String16 arg16 = str16_cstring((U16 *)wargv[i]); + String8 arg8 = str8_from_16(args_arena, arg16); + if(str8_match(arg8, str8_lit("--quiet"), StringMatchFlag_CaseInsensitive) || + str8_match(arg8, str8_lit("-quiet"), StringMatchFlag_CaseInsensitive)) + { + win32_g_is_quiet = 1; + } + if(str8_match(arg8, str8_lit("--large_pages"), StringMatchFlag_CaseInsensitive) || + str8_match(arg8, str8_lit("-large_pages"), StringMatchFlag_CaseInsensitive)) + { + arena_default_flags = ArenaFlag_LargePages; + arena_default_reserve_size = Max(MB(64), os_w32_state.system_info.large_page_size); + arena_default_commit_size = arena_default_reserve_size; + } + argv[i] = (char *)arg8.str; + } + + //- rjf: set up thread context + local_persist TCTX tctx; + tctx_init_and_equip(&tctx); + + //- rjf: set up dynamically-alloc'd state + Arena *arena = arena_alloc(); + { + os_w32_state.arena = arena; + { + OS_SystemInfo *info = &os_w32_state.system_info; + U8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0}; + DWORD size = MAX_COMPUTERNAME_LENGTH + 1; + if(GetComputerNameA((char*)buffer, &size)) + { + info->machine_name = push_str8_copy(arena, str8(buffer, size)); + } + } + } + { + OS_ProcessInfo *info = &os_w32_state.process_info; + { + Temp scratch = scratch_begin(0, 0); + DWORD size = KB(32); + U16 *buffer = push_array_no_zero(scratch.arena, U16, size); + DWORD length = GetModuleFileNameW(0, (WCHAR*)buffer, size); + String8 name8 = str8_from_16(scratch.arena, str16(buffer, length)); + String8 name_chopped = str8_chop_last_slash(name8); + info->binary_path = push_str8_copy(arena, name_chopped); + scratch_end(scratch); + } + info->initial_path = os_get_current_path(arena); + { + Temp scratch = scratch_begin(0, 0); + U64 size = KB(32); + U16 *buffer = push_array_no_zero(scratch.arena, U16, size); + if(SUCCEEDED(SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, (WCHAR*)buffer))) + { + info->user_program_data_path = str8_from_16(arena, str16_cstring(buffer)); + } + scratch_end(scratch); + } + { + WCHAR *this_proc_env = GetEnvironmentStringsW(); + U64 start_idx = 0; + for(U64 idx = 0;; idx += 1) + { + if(this_proc_env[idx] == 0) + { + if(start_idx == idx) + { + break; + } + else + { + String16 string16 = str16((U16 *)this_proc_env + start_idx, idx - start_idx); + String8 string = str8_from_16(arena, string16); + str8_list_push(arena, &info->environment, string); + start_idx = idx+1; + } + } + } + } + } + + //- rjf: set up entity storage + InitializeCriticalSection(&os_w32_state.entity_mutex); + os_w32_state.entity_arena = arena_alloc(); + + //- rjf: call into "real" entry point + main_thread_base_entry_point(argc, argv); +} + +#if BUILD_CONSOLE_INTERFACE +int wmain(int argc, WCHAR **argv) +{ + w32_entry_point_caller(argc, argv); + return 0; +} +#else +int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) +{ + w32_entry_point_caller(__argc, __wargv); + return 0; +} +#endif diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index 11da04b8a..b79f06d6e 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -1,500 +1,1811 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Helpers - -internal OS_LNX_Window * -os_lnx_window_from_x11window(Window window) -{ - OS_LNX_Window *result = 0; - for(OS_LNX_Window *w = os_lnx_gfx_state->first_window; w != 0; w = w->next) - { - if(w->window == window) - { - result = w; - break; - } - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void -os_gfx_init(void) -{ - //- rjf: initialize basics - Arena *arena = arena_alloc(); - os_lnx_gfx_state = push_array(arena, OS_LNX_GfxState, 1); - os_lnx_gfx_state->arena = arena; - os_lnx_gfx_state->display = XOpenDisplay(0); - - //- rjf: calculate atoms - os_lnx_gfx_state->wm_delete_window_atom = XInternAtom(os_lnx_gfx_state->display, "WM_DELETE_WINDOW", 0); - os_lnx_gfx_state->wm_sync_request_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST", 0); - os_lnx_gfx_state->wm_sync_request_counter_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST_COUNTER", 0); - - //- rjf: fill out gfx info - os_lnx_gfx_state->gfx_info.double_click_time = 0.5f; - os_lnx_gfx_state->gfx_info.caret_blink_time = 0.5f; - os_lnx_gfx_state->gfx_info.default_refresh_rate = 60.f; -} - -//////////////////////////////// -//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) - -internal OS_GfxInfo * -os_get_gfx_info(void) -{ - return &os_lnx_gfx_state->gfx_info; -} - -//////////////////////////////// -//~ rjf: @os_hooks Clipboards (Implemented Per-OS) - -internal void -os_set_clipboard_text(String8 string) -{ - -} - -internal String8 -os_get_clipboard_text(Arena *arena) -{ - String8 result = {0}; - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Windows (Implemented Per-OS) - -internal OS_Handle -os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title) -{ - //- rjf: allocate window - OS_LNX_Window *w = os_lnx_gfx_state->free_window; - if(w) - { - SLLStackPop(os_lnx_gfx_state->free_window); - } - else - { - w = push_array_no_zero(os_lnx_gfx_state->arena, OS_LNX_Window, 1); - } - MemoryZeroStruct(w); - DLLPushBack(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); - - //- rjf: create window & equip with x11 info - w->window = XCreateWindow(os_lnx_gfx_state->display, - XDefaultRootWindow(os_lnx_gfx_state->display), - 0, 0, resolution.x, resolution.y, - 0, - CopyFromParent, - InputOutput, - CopyFromParent, - 0, - 0); - XSelectInput(os_lnx_gfx_state->display, w->window, - ExposureMask| - PointerMotionMask| - ButtonPressMask| - ButtonReleaseMask| - KeyPressMask| - KeyReleaseMask| - FocusChangeMask); - Atom protocols[] = - { - os_lnx_gfx_state->wm_delete_window_atom, - os_lnx_gfx_state->wm_sync_request_atom, - }; - XSetWMProtocols(os_lnx_gfx_state->display, w->window, protocols, ArrayCount(protocols)); - { - XSyncValue initial_value; - XSyncIntToValue(&initial_value, 0); - w->counter_xid = XSyncCreateCounter(os_lnx_gfx_state->display, initial_value); - } - XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_sync_request_counter_atom, XA_CARDINAL, 32, PropModeReplace, (U8 *)&w->counter_xid, 1); - - //- rjf: attach name - Temp scratch = scratch_begin(0, 0); - String8 title_copy = push_str8_copy(scratch.arena, title); - XStoreName(os_lnx_gfx_state->display, w->window, (char *)title_copy.str); - scratch_end(scratch); - - //- rjf: convert to handle & return - OS_Handle handle = {(U64)w}; - return handle; -} - -internal void -os_window_close(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal void -os_window_first_paint(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} - OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; - XMapWindow(os_lnx_gfx_state->display, w->window); -} - -internal void -os_window_focus(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal B32 -os_window_is_focused(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return 0;} - return 0; -} - -internal B32 -os_window_is_fullscreen(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return 0;} - return 0; -} - -internal void -os_window_set_fullscreen(OS_Handle handle, B32 fullscreen) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal B32 -os_window_is_maximized(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return 0;} - return 0; -} - -internal void -os_window_set_maximized(OS_Handle handle, B32 maximized) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal B32 -os_window_is_minimized(OS_Handle window) -{ - if(os_handle_match(handle, os_handle_zero())) {return 0;} -} - -internal void -os_window_set_minimized(OS_Handle window, B32 minimized) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal void -os_window_bring_to_front(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal void -os_window_set_monitor(OS_Handle handle, OS_Handle monitor) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal void -os_window_clear_custom_border_data(OS_Handle handle) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal void -os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal void -os_window_push_custom_edges(OS_Handle handle, F32 thickness) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal void -os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) -{ - if(os_handle_match(handle, os_handle_zero())) {return;} -} - -internal Rng2F32 -os_rect_from_window(OS_Handle handle) -{ - return r2f32p(0, 0, 0, 0); -} - -internal Rng2F32 -os_client_rect_from_window(OS_Handle handle) -{ - return r2f32p(0, 0, 0, 0); -} - -internal F32 -os_dpi_from_window(OS_Handle handle) -{ - return 0; -} - -//////////////////////////////// -//~ rjf: @os_hooks Monitors (Implemented Per-OS) - -internal OS_HandleArray -os_push_monitors_array(Arena *arena) -{ - OS_HandleArray result = {0}; - return result; -} - -internal OS_Handle -os_primary_monitor(void) -{ - OS_Handle result = {0}; - return result; -} - -internal OS_Handle -os_monitor_from_window(OS_Handle window) -{ - OS_Handle result = {0}; - return result; -} - -internal String8 -os_name_from_monitor(Arena *arena, OS_Handle monitor) -{ - return str8_zero(); -} - -internal Vec2F32 -os_dim_from_monitor(OS_Handle monitor) -{ - return v2f32(0, 0); -} - -//////////////////////////////// -//~ rjf: @os_hooks Events (Implemented Per-OS) - -internal void -os_send_wakeup_event(void) -{ - -} - -internal OS_EventList -os_get_events(Arena *arena, B32 wait) -{ - OS_EventList evts = {0}; - for(;XPending(os_lnx_gfx_state->display) > 0 || (wait && evts.count == 0);) - { - XEvent evt = {0}; - XNextEvent(os_lnx_gfx_state->display, &evt); - switch(evt.type) - { - default:{}break; - - //- rjf: key presses/releases - case KeyPress: - case KeyRelease: - { - // rjf: determine flags - OS_Modifiers flags = 0; - if(evt.xkey.state & ShiftMask) { flags |= OS_Modifier_Shift; } - if(evt.xkey.state & ControlMask) { flags |= OS_Modifier_Ctrl; } - if(evt.xkey.state & Mod1Mask) { flags |= OS_Modifier_Alt; } - - // rjf: map keycode -> keysym - U32 keysym = XLookupKeysym(&evt.xkey, 0); - - // rjf: map keysym -> OS_Key - OS_Key key = OS_Key_Null; - switch(keysym) - { - default: - { - if(0){} - else if(XK_F1 <= keysym && keysym <= XK_F24) { key = (OS_Key)(OS_Key_F1 + (keysym - XK_F1)); } - else if('0' <= keysym && keysym <= '9') { key = OS_Key_0 + (keysym-'0'); } - }break; - case XK_Escape:{key = OS_Key_Esc;};break; - case '-':{key = OS_Key_Minus;}break; - case '=':{key = OS_Key_Equal;}break; - case '[':{key = OS_Key_LeftBracket;}break; - case ']':{key = OS_Key_RightBracket;}break; - case ';':{key = OS_Key_Semicolon;}break; - case '\'':{key = OS_Key_Quote;}break; - case '.':{key = OS_Key_Period;}break; - case ',':{key = OS_Key_Comma;}break; - case '/':{key = OS_Key_Slash;}break; - case '\\':{key = OS_Key_BackSlash;}break; - case 'a':case 'A':{key = OS_Key_A;}break; - case 'b':case 'B':{key = OS_Key_B;}break; - case 'c':case 'C':{key = OS_Key_C;}break; - case 'd':case 'D':{key = OS_Key_D;}break; - case 'e':case 'E':{key = OS_Key_E;}break; - case 'f':case 'F':{key = OS_Key_F;}break; - case 'g':case 'G':{key = OS_Key_G;}break; - case 'h':case 'H':{key = OS_Key_H;}break; - case 'i':case 'I':{key = OS_Key_I;}break; - case 'j':case 'J':{key = OS_Key_J;}break; - case 'k':case 'K':{key = OS_Key_K;}break; - case 'l':case 'L':{key = OS_Key_L;}break; - case 'm':case 'M':{key = OS_Key_M;}break; - case 'n':case 'N':{key = OS_Key_N;}break; - case 'o':case 'O':{key = OS_Key_O;}break; - case 'p':case 'P':{key = OS_Key_P;}break; - case 'q':case 'Q':{key = OS_Key_Q;}break; - case 'r':case 'R':{key = OS_Key_R;}break; - case 's':case 'S':{key = OS_Key_S;}break; - case 't':case 'T':{key = OS_Key_T;}break; - case 'u':case 'U':{key = OS_Key_U;}break; - case 'v':case 'V':{key = OS_Key_V;}break; - case 'w':case 'W':{key = OS_Key_W;}break; - case 'x':case 'X':{key = OS_Key_X;}break; - case 'y':case 'Y':{key = OS_Key_Y;}break; - case 'z':case 'Z':{key = OS_Key_Z;}break; - case ' ':{key = OS_Key_Space;}break; - } - - // rjf: push event - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); - OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == KeyPress ? OS_EventKind_Press : OS_EventKind_Release); - e->window.u64[0] = (U64)window; - e->flags = flags; - e->key = key; - }break; - - //- rjf: mouse button presses/releases - case ButtonPress: - case ButtonRelease: - { - // rjf: determine flags - OS_Modifiers flags = 0; - if(evt.xbutton.state & ShiftMask) { flags |= OS_Modifier_Shift; } - if(evt.xbutton.state & ControlMask) { flags |= OS_Modifier_Ctrl; } - if(evt.xbutton.state & Mod1Mask) { flags |= OS_Modifier_Alt; } - - // rjf: map button -> OS_Key - OS_Key key = OS_Key_Null; - switch(evt.xbutton.button) - { - default:{}break; - case Button1:{key = OS_Key_LeftMouseButton;}break; - case Button2:{key = OS_Key_MiddleMouseButton;}break; - case Button3:{key = OS_Key_RightMouseButton;}break; - } - - // rjf: push event - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); - OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == ButtonPress ? OS_EventKind_Press : OS_EventKind_Release); - e->window.u64[0] = (U64)window; - e->flags = flags; - e->key = key; - }break; - - //- rjf: mouse motion - case MotionNotify: - { - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); - OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_MouseMove); - e->window.u64[0] = (U64)window; - e->pos.x = (F32)evt.xmotion.x; - e->pos.y = (F32)evt.xmotion.y; - }break; - - //- rjf: window focus/unfocus - case FocusIn: - case FocusOut: - { - - }break; - - //- rjf: client messages - case ClientMessage: - { - if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_delete_window_atom) - { - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); - OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_WindowClose); - e->window.u64[0] = (U64)window; - } - else if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_sync_request_atom) - { - OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); - if(window != 0) - { - window->counter_value = 0; - window->counter_value |= evt.xclient.data.l[2]; - window->counter_value |= (evt.xclient.data.l[3] << 32); - XSyncValue value; - XSyncIntToValue(&value, window->counter_value); - XSyncSetCounter(os_lnx_gfx_state->display, window->counter_xid, value); - } - } - }break; - } - } - return evts; -} - -internal OS_Modifiers -os_get_modifiers(void) -{ - return 0; -} - -internal B32 -os_key_is_down(OS_Key key) -{ - return 0; -} - -internal Vec2F32 -os_mouse_from_window(OS_Handle handle) -{ - return v2f32(0, 0); -} - -//////////////////////////////// -//~ rjf: @os_hooks Cursors (Implemented Per-OS) - -internal void -os_set_cursor(OS_Cursor cursor) -{ - -} - -//////////////////////////////// -//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void -os_graphical_message(B32 error, String8 title, String8 message) -{ - -} - -//////////////////////////////// -//~ rjf: @os_hooks Shell Operations - -internal void -os_show_in_filesystem_ui(String8 path) -{ - -} - -internal void -os_open_in_browser(String8 url) -{ - -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ dan: Helpers + + +internal OS_Key +os_lnx_keysym_to_oskey(KeySym keysym) +{ + OS_Key key = OS_Key_Null; + switch(keysym) + { + default: + { + if(0){} + else if(XK_F1 <= keysym && keysym <= XK_F24) { key = (OS_Key)(OS_Key_F1 + (keysym - XK_F1)); } + else if('0' <= keysym && keysym <= '9') { key = OS_Key_0 + (keysym-'0'); } + else if(XK_KP_0 <= keysym && keysym <= XK_KP_9) { key = (OS_Key)(OS_Key_Num0 + (keysym - XK_KP_0)); } + }break; + case XK_Escape:{key = OS_Key_Esc;};break; + case XK_minus:{key = OS_Key_Minus;}break; + case XK_equal:{key = OS_Key_Equal;}break; + case XK_bracketleft:{key = OS_Key_LeftBracket;}break; + case XK_bracketright:{key = OS_Key_RightBracket;}break; + case XK_semicolon:{key = OS_Key_Semicolon;}break; + case XK_apostrophe:{key = OS_Key_Quote;}break; + case XK_period:{key = OS_Key_Period;}break; + case XK_comma:{key = OS_Key_Comma;}break; + case XK_slash:{key = OS_Key_Slash;}break; + case XK_backslash:{key = OS_Key_BackSlash;}break; + case XK_grave:{key = OS_Key_Tick;}break; // Grave accent often maps to Tick + case XK_a:case XK_A:{key = OS_Key_A;}break; + case XK_b:case XK_B:{key = OS_Key_B;}break; + case XK_c:case XK_C:{key = OS_Key_C;}break; + case XK_d:case XK_D:{key = OS_Key_D;}break; + case XK_e:case XK_E:{key = OS_Key_E;}break; + case XK_f:case XK_F:{key = OS_Key_F;}break; + case XK_g:case XK_G:{key = OS_Key_G;}break; + case XK_h:case XK_H:{key = OS_Key_H;}break; + case XK_i:case XK_I:{key = OS_Key_I;}break; + case XK_j:case XK_J:{key = OS_Key_J;}break; + case XK_k:case XK_K:{key = OS_Key_K;}break; + case XK_l:case XK_L:{key = OS_Key_L;}break; + case XK_m:case XK_M:{key = OS_Key_M;}break; + case XK_n:case XK_N:{key = OS_Key_N;}break; + case XK_o:case XK_O:{key = OS_Key_O;}break; + case XK_p:case XK_P:{key = OS_Key_P;}break; + case XK_q:case XK_Q:{key = OS_Key_Q;}break; + case XK_r:case XK_R:{key = OS_Key_R;}break; + case XK_s:case XK_S:{key = OS_Key_S;}break; + case XK_t:case XK_T:{key = OS_Key_T;}break; + case XK_u:case XK_U:{key = OS_Key_U;}break; + case XK_v:case XK_V:{key = OS_Key_V;}break; + case XK_w:case XK_W:{key = OS_Key_W;}break; + case XK_x:case XK_X:{key = OS_Key_X;}break; + case XK_y:case XK_Y:{key = OS_Key_Y;}break; + case XK_z:case XK_Z:{key = OS_Key_Z;}break; + case XK_space:{key = OS_Key_Space;}break; + case XK_BackSpace:{key = OS_Key_Backspace;}break; + case XK_Tab: case XK_ISO_Left_Tab: {key = OS_Key_Tab;}break; // Handle shift+tab + case XK_Return: case XK_KP_Enter: {key = OS_Key_Return;}break; + case XK_Shift_L: case XK_Shift_R: {key = OS_Key_Shift;}break; + case XK_Control_L: case XK_Control_R: {key = OS_Key_Ctrl;}break; + case XK_Alt_L: case XK_Alt_R: {key = OS_Key_Alt;}break; + case XK_Menu: case XK_Super_L: case XK_Super_R: {key = OS_Key_Menu;}break; // Map Super/Windows key to Menu + case XK_Scroll_Lock: {key = OS_Key_ScrollLock;}break; + case XK_Pause: {key = OS_Key_Pause;}break; + case XK_Insert: case XK_KP_Insert: {key = OS_Key_Insert;}break; + case XK_Home: case XK_KP_Home: {key = OS_Key_Home;}break; + case XK_Page_Up: case XK_KP_Page_Up: {key = OS_Key_PageUp;}break; + case XK_Delete: case XK_KP_Delete: {key = OS_Key_Delete;}break; + case XK_End: case XK_KP_End: {key = OS_Key_End;}break; + case XK_Page_Down: case XK_KP_Page_Down: {key = OS_Key_PageDown;}break; + case XK_Up: case XK_KP_Up: {key = OS_Key_Up;}break; + case XK_Left: case XK_KP_Left: {key = OS_Key_Left;}break; + case XK_Down: case XK_KP_Down: {key = OS_Key_Down;}break; + case XK_Right: case XK_KP_Right: {key = OS_Key_Right;}break; + case XK_Num_Lock: {key = OS_Key_NumLock;}break; + case XK_KP_Divide: {key = OS_Key_NumSlash;}break; + case XK_KP_Multiply: {key = OS_Key_NumStar;}break; + case XK_KP_Subtract: {key = OS_Key_NumMinus;}break; + case XK_KP_Add: {key = OS_Key_NumPlus;}break; + case XK_KP_Decimal: {key = OS_Key_NumPeriod;}break; + case XK_Caps_Lock: {key = OS_Key_CapsLock;} break; + } + return key; +} + +internal OS_LNX_Window * +os_lnx_window_from_x11window(Window window) +{ + OS_LNX_Window *result = 0; + for(OS_LNX_Window *w = os_lnx_gfx_state->first_window; w != 0; w = w->next) + { + if(w->window == window) + { + result = w; + break; + } + } + return result; +} + +//////////////////////////////// +//~ dan: @os_hooks Main Initialization API (Implemented Per-OS) + +internal void +os_gfx_init(void) +{ + //- dan: initialize basics + Arena *arena = arena_alloc(); + os_lnx_gfx_state = push_array(arena, OS_LNX_GfxState, 1); + os_lnx_gfx_state->arena = arena; + + // Attempt to open a connection to the X server + Display *display = XOpenDisplay(NULL); + if (!display) { + // Handle the error properly - log it and terminate + const char *display_env = getenv("DISPLAY"); + fprintf(stderr, "ERROR: Failed to connect to X11 server. DISPLAY=%s\n", + display_env ? display_env : "(not set)"); + fprintf(stderr, "Make sure an X server is running and DISPLAY is set correctly.\n"); + exit(1); // Exit with error code - X11 is required + } + os_lnx_gfx_state->display = display; + os_lnx_gfx_state->clipboard_arena = arena_alloc(); // Initialize clipboard arena + + //- dan: calculate atoms + os_lnx_gfx_state->wm_delete_window_atom = XInternAtom(os_lnx_gfx_state->display, "WM_DELETE_WINDOW", 0); + os_lnx_gfx_state->wm_sync_request_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST", 0); + os_lnx_gfx_state->wm_sync_request_counter_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_SYNC_REQUEST_COUNTER", 0); + os_lnx_gfx_state->clipboard_atom = XInternAtom(os_lnx_gfx_state->display, "CLIPBOARD", 0); + os_lnx_gfx_state->targets_atom = XInternAtom(os_lnx_gfx_state->display, "TARGETS", 0); + os_lnx_gfx_state->utf8_string_atom = XInternAtom(os_lnx_gfx_state->display, "UTF8_STRING", 0); + os_lnx_gfx_state->wakeup_atom = XInternAtom(os_lnx_gfx_state->display, "_RADDBG_WAKEUP", 0); + os_lnx_gfx_state->clipboard_target_prop_atom = XInternAtom(os_lnx_gfx_state->display, "_RADDBG_CLIPBOARD_TARGET", 0); + + //- dan: calculate EWMH atoms for window state + os_lnx_gfx_state->net_wm_state_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE", 0); + os_lnx_gfx_state->net_wm_state_maximized_vert_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_MAXIMIZED_VERT", 0); + os_lnx_gfx_state->net_wm_state_maximized_horz_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_MAXIMIZED_HORZ", 0); + os_lnx_gfx_state->net_wm_state_hidden_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_HIDDEN", 0); + os_lnx_gfx_state->net_wm_state_fullscreen_atom = XInternAtom(os_lnx_gfx_state->display, "_NET_WM_STATE_FULLSCREEN", 0); + + //- dan: create dedicated hidden window for clipboard operations + { + XSetWindowAttributes attributes; + attributes.override_redirect = True; // Prevent window manager interference + os_lnx_gfx_state->clipboard_window = XCreateWindow( + os_lnx_gfx_state->display, + XDefaultRootWindow(os_lnx_gfx_state->display), + -1, -1, 1, 1, // Position off-screen, minimal size + 0, CopyFromParent, InputOnly, CopyFromParent, + CWOverrideRedirect, + &attributes); + // We need to select for PropertyChangeMask to receive SelectionNotify events + // when the property we specified in XConvertSelection is updated. + XSelectInput(os_lnx_gfx_state->display, os_lnx_gfx_state->clipboard_window, PropertyChangeMask); + } + + //- dan: initialize clipboard cache + os_lnx_gfx_state->last_retrieved_clipboard_text = str8_lit(""); // Initial empty cache + + //- dan: fill out gfx info + os_lnx_gfx_state->gfx_info.double_click_time = 0.5f; + os_lnx_gfx_state->gfx_info.caret_blink_time = 0.5f; + os_lnx_gfx_state->gfx_info.default_refresh_rate = 60.f; + + //- dan: Populate OS_Key -> KeyCode mapping + MemoryZeroArray(os_lnx_gfx_state->keycode_from_oskey); + Display *d = os_lnx_gfx_state->display; + int min_keycode, max_keycode; + XDisplayKeycodes(d, &min_keycode, &max_keycode); + int keysyms_per_keycode = 0; + KeySym *keysyms = XGetKeyboardMapping(d, min_keycode, (max_keycode - min_keycode + 1), &keysyms_per_keycode); + if (keysyms != NULL) + { + for (int kc = min_keycode; kc <= max_keycode; ++kc) + { + // Check standard keysym (index 0) + KeySym ks = XKeycodeToKeysym(d, kc, 0); + if (ks == XK_Shift_L) { os_lnx_gfx_state->keycode_lshift = kc; } + if (ks == XK_Shift_R) { os_lnx_gfx_state->keycode_rshift = kc; } + if (ks == XK_Control_L) { os_lnx_gfx_state->keycode_lctrl = kc; } + if (ks == XK_Control_R) { os_lnx_gfx_state->keycode_rctrl = kc; } + if (ks == XK_Alt_L) { os_lnx_gfx_state->keycode_lalt = kc; } + if (ks == XK_Alt_R) { os_lnx_gfx_state->keycode_ralt = kc; } + if (ks != NoSymbol) + { + OS_Key oskey = os_lnx_keysym_to_oskey(ks); + if (oskey != OS_Key_Null && os_lnx_gfx_state->keycode_from_oskey[oskey] == 0) + { + // Prioritize non-modifier keys or left-side modifiers for the main table + if (oskey != OS_Key_Shift && oskey != OS_Key_Ctrl && oskey != OS_Key_Alt) { + os_lnx_gfx_state->keycode_from_oskey[oskey] = kc; + } else if (ks == XK_Shift_L || ks == XK_Control_L || ks == XK_Alt_L) { + os_lnx_gfx_state->keycode_from_oskey[oskey] = kc; + } + } + } + } + XFree(keysyms); + } + + //- dan: load cursors + os_lnx_gfx_state->cursors[OS_Cursor_Pointer] = XCreateFontCursor(os_lnx_gfx_state->display, XC_left_ptr); + os_lnx_gfx_state->cursors[OS_Cursor_IBar] = XCreateFontCursor(os_lnx_gfx_state->display, XC_xterm); + os_lnx_gfx_state->cursors[OS_Cursor_LeftRight] = XCreateFontCursor(os_lnx_gfx_state->display, XC_sb_h_double_arrow); + os_lnx_gfx_state->cursors[OS_Cursor_UpDown] = XCreateFontCursor(os_lnx_gfx_state->display, XC_sb_v_double_arrow); + os_lnx_gfx_state->cursors[OS_Cursor_DownRight] = XCreateFontCursor(os_lnx_gfx_state->display, XC_bottom_right_corner); + os_lnx_gfx_state->cursors[OS_Cursor_UpRight] = XCreateFontCursor(os_lnx_gfx_state->display, XC_top_right_corner); + os_lnx_gfx_state->cursors[OS_Cursor_UpDownLeftRight]= XCreateFontCursor(os_lnx_gfx_state->display, XC_fleur); + os_lnx_gfx_state->cursors[OS_Cursor_HandPoint] = XCreateFontCursor(os_lnx_gfx_state->display, XC_hand2); + os_lnx_gfx_state->cursors[OS_Cursor_Disabled] = XCreateFontCursor(os_lnx_gfx_state->display, XC_pirate); + os_set_cursor(OS_Cursor_Pointer); +} + +//////////////////////////////// +//~ dan: @os_hooks Graphics System Info (Implemented Per-OS) + +internal OS_GfxInfo * +os_get_gfx_info(void) +{ + return &os_lnx_gfx_state->gfx_info; +} + +//////////////////////////////// +//~ dan: @os_hooks Clipboards (Implemented Per-OS) + +internal void +os_set_clipboard_text(String8 string) +{ + Display *d = os_lnx_gfx_state->display; + // Arena clear/re-push might be needed if clipboard_buffer is reused often + arena_clear(os_lnx_gfx_state->clipboard_arena); // Use clipboard_arena + os_lnx_gfx_state->clipboard_buffer = push_str8_copy(os_lnx_gfx_state->clipboard_arena, string); + XSetSelectionOwner(d, os_lnx_gfx_state->clipboard_atom, os_lnx_gfx_state->clipboard_window, CurrentTime); + // Ensure the server processes the request + XFlush(d); +} + +internal String8 +os_get_clipboard_text(Arena *arena) +{ + Display *d = os_lnx_gfx_state->display; + Window owner = XGetSelectionOwner(d, os_lnx_gfx_state->clipboard_atom); + String8 result = {0}; // Initialize result + + if (owner == None) + { + // No owner, clear our cache if it wasn't already empty + if (os_lnx_gfx_state->last_retrieved_clipboard_text.size > 0) { + // Clear or manage the arena holding the cache if necessary + arena_clear(os_lnx_gfx_state->clipboard_arena); // Use clipboard_arena + os_lnx_gfx_state->last_retrieved_clipboard_text = str8_lit(""); + } + // Return empty string as there is no owner + result = str8_lit(""); + } + else if (owner == os_lnx_gfx_state->clipboard_window) + { + // We are the owner, return the buffer we set. + result = push_str8_copy(arena, os_lnx_gfx_state->clipboard_buffer); + } + else + { + // Request the selection data from the current owner. + // The result will be placed in the property 'clipboard_target_prop_atom' + // on our dedicated 'clipboard_window'. + // The 'os_get_events' loop will handle the PropertyNotify event when the data arrives. + XConvertSelection(d, + os_lnx_gfx_state->clipboard_atom, // selection atom (CLIPBOARD) + os_lnx_gfx_state->utf8_string_atom, // target format (UTF8_STRING) + os_lnx_gfx_state->clipboard_target_prop_atom, // property to store result on requestor + os_lnx_gfx_state->clipboard_window, // requestor window + CurrentTime); + // NOTE(perf): Cannot block here waiting for clipboard data. Must return cached value + // and handle PropertyNotify event asynchronously. + XFlush(d); + + // Immediately return the last successfully retrieved text from our cache. + // The cache will be updated asynchronously later in the event loop. + result = push_str8_copy(arena, os_lnx_gfx_state->last_retrieved_clipboard_text); + } + + return result; +} + +//////////////////////////////// +//~ dan: @os_hooks Windows (Implemented Per-OS) + +internal OS_Handle +os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title) +{ + //- dan: allocate window + OS_LNX_Window *w = os_lnx_gfx_state->free_window; + if(w) + { + SLLStackPop(os_lnx_gfx_state->free_window); + } + else + { + w = push_array_no_zero(os_lnx_gfx_state->arena, OS_LNX_Window, 1); + } + MemoryZeroStruct(w); + DLLPushBack(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); + + //- dan: create window & equip with x11 info + w->window = XCreateWindow(os_lnx_gfx_state->display, + XDefaultRootWindow(os_lnx_gfx_state->display), + 0, 0, resolution.x, resolution.y, + 0, + CopyFromParent, + InputOutput, + CopyFromParent, + 0, + 0); + XSelectInput(os_lnx_gfx_state->display, w->window, + ExposureMask| + PointerMotionMask| + ButtonPressMask| + ButtonReleaseMask| + KeyPressMask| + KeyReleaseMask| + FocusChangeMask); + + //- dan: attach name + Temp scratch = scratch_begin(0, 0); + String8 title_copy = push_str8_copy(scratch.arena, title); + // XStoreName(os_lnx_gfx_state->display, w->window, (char *)title_copy.str); // MOVED + // scratch_end(scratch); // MOVED + + //--- START NEW GLX/GLEW INIT CODE --- + Display *d = os_lnx_gfx_state->display; + int default_screen = XDefaultScreen(d); + + // Define desired framebuffer attributes + static int visual_attribs[] = { + GLX_X_RENDERABLE, True, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_ALPHA_SIZE, 8, + GLX_DEPTH_SIZE, 24, + GLX_STENCIL_SIZE, 8, + GLX_DOUBLEBUFFER, True, + //GLX_SAMPLE_BUFFERS , 1, // For MSAA + //GLX_SAMPLES , 4, // MSAA samples + None // Null terminate the list + }; + + // Choose FBConfig + int fbcount; + GLXFBConfig *fbc = glXChooseFBConfig(d, default_screen, visual_attribs, &fbcount); + if (!fbc || fbcount <= 0) { + fprintf(stderr, "Failed to retrieve a framebuffer config\n"); + XDestroyWindow(d, w->window); // Destroy the initial dummy window + // Handle error: maybe release 'w' back to free list? + DLLRemove(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); + SLLStackPush(os_lnx_gfx_state->free_window, w); + return os_handle_zero(); + } + // Select the best FBConfig (take the first one for simplicity) + GLXFBConfig best_fbc = fbc[0]; + XFree(fbc); // Free the list returned by glXChooseFBConfig + os_lnx_gfx_state->fb_config = best_fbc; // Store if needed globally + + // Get visual info from FBConfig + XVisualInfo *vi = glXGetVisualFromFBConfig(d, best_fbc); + if (!vi) { + fprintf(stderr, "Could not create visual window info\n"); + XDestroyWindow(d, w->window); // Destroy the initial dummy window + DLLRemove(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); + SLLStackPush(os_lnx_gfx_state->free_window, w); + return os_handle_zero(); + } + + // Destroy the initially created window (it had the wrong visual) + XDestroyWindow(d, w->window); + + // Create a colormap and window attributes + Colormap cmap = XCreateColormap(d, XRootWindow(d, vi->screen), vi->visual, AllocNone); + XSetWindowAttributes swa; + swa.colormap = cmap; + swa.background_pixmap = None; + swa.border_pixel = 0; + swa.event_mask = StructureNotifyMask | ExposureMask | KeyPressMask | KeyReleaseMask | + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask; + + // Re-create the window with the correct visual and attributes + w->window = XCreateWindow(d, + XRootWindow(d, vi->screen), + 0, 0, resolution.x, resolution.y, + 0, // border_width + vi->depth, + InputOutput, + vi->visual, + CWBorderPixel | CWColormap | CWEventMask, // Value mask + &swa); + if (!w->window) { + fprintf(stderr, "Failed to create window with chosen visual\n"); + XFreeColormap(d, cmap); + XFree(vi); + DLLRemove(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); + SLLStackPush(os_lnx_gfx_state->free_window, w); + return os_handle_zero(); + } + // Free the visual info structure + XFree(vi); + XFreeColormap(d, cmap); // Colormap is associated with window now + + // Set window title AFTER final window creation + XStoreName(os_lnx_gfx_state->display, w->window, (char *)title_copy.str); + scratch_end(scratch); + + // Create temporary legacy OpenGL context first for GLEW initialization + // We still need a temporary context to initialize GLEW and get the glXGetProcAddressARB function + GLXContext temp_ctx = glXCreateNewContext(d, best_fbc, GLX_RGBA_TYPE, 0, True); + if (!temp_ctx) { + fprintf(stderr, "Failed to create temporary GL context\\n"); + XDestroyWindow(d, w->window); + DLLRemove(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); + SLLStackPush(os_lnx_gfx_state->free_window, w); + return os_handle_zero(); + } + + // Make temporary context current + if (!glXMakeCurrent(d, w->window, temp_ctx)) { + fprintf(stderr, "Failed to make temporary context current\\n"); + glXDestroyContext(d, temp_ctx); + XDestroyWindow(d, w->window); + DLLRemove(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); + SLLStackPush(os_lnx_gfx_state->free_window, w); + return os_handle_zero(); + } + + fprintf(stderr, "Temporary OpenGL context created and made current\n"); + + // We now have a valid temporary context, but don't set has_valid_context yet + // as we want to initialize GLEW first + + // Initialize GLEW AFTER making the temporary context current + r_gl_init_glew_if_needed(); // Only initialize GLEW here, not resources! + + // Attempt to get the modern context creation function pointer + PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = NULL; + glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) + glXGetProcAddressARB((const GLubyte*)"glXCreateContextAttribsARB"); + + GLXContext final_ctx = NULL; + // Use the flag set by r_gl_init_extensions_if_needed + if(glXCreateContextAttribsARB) + { + int context_attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, 4, // Request OpenGL 4.6 + GLX_CONTEXT_MINOR_VERSION_ARB, 6, + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + // GLX_CONTEXT_FLAGS_ARB , GLX_CONTEXT_DEBUG_BIT_ARB, // Optional debug context + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + None // Null terminate + }; + + // glXCreateContextAttribsARB is defined by glew.h if GLEW_ARB_create_context is defined + final_ctx = glXCreateContextAttribsARB(d, best_fbc, temp_ctx, True, context_attribs); + + if (!final_ctx) { + fprintf(stderr, "Failed to create GL 3.3 context using ARB (glXGetProcAddressARB succeeded), falling back to legacy context.\\n"); + final_ctx = temp_ctx; // Keep the temporary context + temp_ctx = NULL; // Don't destroy it later + } else { + // Successfully created 3.3 context, destroy the temporary one + glXMakeCurrent(d, None, NULL); // Make no context current before destroying temp + // glXDestroyContext(d, temp_ctx); // TEMP: Comment out to test resource sharing + glXDestroyContext(d, temp_ctx); // Destroy temporary context now that we have a working final context + temp_ctx = NULL; // Mark temp as destroyed + + // Make the new 3.3 context current + if (!glXMakeCurrent(d, w->window, final_ctx)) { + fprintf(stderr, "Failed to make final GL 3.3 context current\\n"); + glXDestroyContext(d, final_ctx); + XDestroyWindow(d, w->window); + DLLRemove(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); + SLLStackPush(os_lnx_gfx_state->free_window, w); + return os_handle_zero(); + } + + // Set the context as valid - THIS IS NEW + r_gl_set_has_valid_context(1); + + // Now that the final context is current, create all OpenGL resources + r_gl_create_global_resources(); + } + } + else + { + fprintf(stderr, "glXCreateContextAttribsARB not available. Using legacy context.\n"); + final_ctx = temp_ctx; // Keep the temporary context + temp_ctx = NULL; // Don't destroy it later + // The temporary context is already current + + // Set the context as valid - THIS IS NEW for legacy path + r_gl_set_has_valid_context(1); + + // REMOVED: Create OpenGL resources in the legacy context + // r_gl_create_global_resources(); + } + + // Store the final context (either the new 3.3 or the fallback legacy one) + w->gl_context = final_ctx; + + // ADDED: Log the final OpenGL version *after* the context is made current + if (glXGetCurrentContext() == final_ctx) { + const GLubyte* version = glGetString(GL_VERSION); + fprintf(stderr, ">>> Final OpenGL Context Version: %s\n", version ? (const char*)version : "NULL (Error getting version?)"); + } else { + fprintf(stderr, ">>> ERROR: Final context not current after creation!\n"); + } + + // Resume existing window setup logic + XSelectInput(os_lnx_gfx_state->display, w->window, + ExposureMask| + PointerMotionMask| + ButtonPressMask| + ButtonReleaseMask| + KeyPressMask| + KeyReleaseMask| + FocusChangeMask); + Atom protocols[] = + { + os_lnx_gfx_state->wm_delete_window_atom, + os_lnx_gfx_state->wm_sync_request_atom, + }; + XSetWMProtocols(os_lnx_gfx_state->display, w->window, protocols, ArrayCount(protocols)); + { + XSyncValue initial_value; + XSyncIntToValue(&initial_value, 0); + w->counter_xid = XSyncCreateCounter(os_lnx_gfx_state->display, initial_value); + } + XChangeProperty(os_lnx_gfx_state->display, w->window, os_lnx_gfx_state->wm_sync_request_counter_atom, XA_CARDINAL, 32, PropModeReplace, (U8 *)&w->counter_xid, 1); + + //- dan: convert to handle & return + OS_Handle handle = {(U64)w}; + return handle; +} + +internal void +os_window_close(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + + if (w != 0 && d != 0) + { + // Destroy the XSync counter associated with the window + if (w->counter_xid != None) // Check if counter was created + { + XSyncDestroyCounter(d, w->counter_xid); + if (XSyncDestroyCounter(d, w->counter_xid) == BadValue) { // Check return value + fprintf(stderr, "Warning: Failed to destroy XSync counter %lu.\n", (unsigned long)w->counter_xid); + } + w->counter_xid = None; // Mark as destroyed + } + + // Make context not current and destroy it + if (w->gl_context) { + // Check if this context is current before making null current + if (glXGetCurrentContext() == w->gl_context) { + glXMakeCurrent(d, None, NULL); + } + glXDestroyContext(d, w->gl_context); + w->gl_context = NULL; // Mark as destroyed + } + + // Destroy the X11 window + XDestroyWindow(d, w->window); + + // Remove from the active window list + DLLRemove(os_lnx_gfx_state->first_window, os_lnx_gfx_state->last_window, w); + + // Add to the free list + SLLStackPush(os_lnx_gfx_state->free_window, w); + + // Maybe flush needed? Depends if caller expects immediate effect. + // XFlush(d); + } +} + +internal void +os_window_first_paint(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + XMapWindow(os_lnx_gfx_state->display, w->window); +} + +internal void +os_window_focus(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + if (w != 0 && d != 0) + { + // Raise the window to the top + XRaiseWindow(d, w->window); + + // Set the input focus + // RevertToParent means focus goes to parent if window becomes unviewable. + XSetInputFocus(d, w->window, RevertToParent, CurrentTime); + + // Ensure the server processes the requests + XFlush(d); + } +} + +internal B32 +os_window_is_focused(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) {return 0;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + return w->has_focus; +} + +internal B32 +os_window_is_fullscreen(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) {return 0;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + Atom *props = 0; + B32 is_fullscreen = 0; + + // Check _NET_WM_STATE property for the _NET_WM_STATE_FULLSCREEN atom + int result = XGetWindowProperty(d, + w->window, + os_lnx_gfx_state->net_wm_state_atom, + 0, (~0L), // Read the whole property + False, // Don't delete + XA_ATOM, // Expected type is Atom + &actual_type, &actual_format, + &nitems, &bytes_after, (unsigned char **)&props); + + if (result == Success && props != 0) + { + for (unsigned long i = 0; i < nitems; i++) + { + if (props[i] == os_lnx_gfx_state->net_wm_state_fullscreen_atom) + { + is_fullscreen = 1; + break; + } + } + XFree(props); + } + return is_fullscreen; +} + +internal void +os_window_set_fullscreen(OS_Handle handle, B32 fullscreen) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + Window root = XDefaultRootWindow(d); + + // EWMH action: _NET_WM_STATE_REMOVE=0, _NET_WM_STATE_ADD=1 + long action = fullscreen ? 1 : 0; + + XEvent e = {0}; + e.type = ClientMessage; + e.xclient.window = w->window; + e.xclient.message_type = os_lnx_gfx_state->net_wm_state_atom; + e.xclient.format = 32; + e.xclient.data.l[0] = action; // Add or Remove state + e.xclient.data.l[1] = os_lnx_gfx_state->net_wm_state_fullscreen_atom; + e.xclient.data.l[2] = 0; // No second property + e.xclient.data.l[3] = 1; // Source indication: Normal Application + e.xclient.data.l[4] = 0; + + // Send the event to the root window, requesting the WM change the state + XSendEvent(d, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &e); + XFlush(d); // Ensure the request is sent immediately +} + +internal B32 +os_window_is_maximized(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) {return 0;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + Atom *props = 0; + B32 is_maximized = 0; + + int result = XGetWindowProperty(d, + w->window, + os_lnx_gfx_state->net_wm_state_atom, + 0, (~0L), // Read the whole property + False, // Don't delete + XA_ATOM, // Expected type is Atom + &actual_type, &actual_format, + &nitems, &bytes_after, (unsigned char **)&props); + + if (result == Success && props != 0) + { + B32 found_vert = 0; + B32 found_horz = 0; + for (unsigned long i = 0; i < nitems; i++) + { + if (props[i] == os_lnx_gfx_state->net_wm_state_maximized_vert_atom) + { + found_vert = 1; + } + else if (props[i] == os_lnx_gfx_state->net_wm_state_maximized_horz_atom) + { + found_horz = 1; + } + } + is_maximized = found_vert && found_horz; + XFree(props); + } + + return is_maximized; +} + +internal void +os_window_set_maximized(OS_Handle handle, B32 maximized) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + Window root = XDefaultRootWindow(d); + + // Action: _NET_WM_STATE_REMOVE=0, _NET_WM_STATE_ADD=1, _NET_WM_STATE_TOGGLE=2 + long action = maximized ? 1 : 0; + + XEvent e = {0}; + e.type = ClientMessage; + e.xclient.window = w->window; + e.xclient.message_type = os_lnx_gfx_state->net_wm_state_atom; + e.xclient.format = 32; + e.xclient.data.l[0] = action; // Add or Remove state + e.xclient.data.l[1] = os_lnx_gfx_state->net_wm_state_maximized_horz_atom; + e.xclient.data.l[2] = os_lnx_gfx_state->net_wm_state_maximized_vert_atom; + e.xclient.data.l[3] = 0; // Source indication (0 for normal apps) + e.xclient.data.l[4] = 0; + + XSendEvent(d, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &e); + XFlush(d); +} + +internal B32 +os_window_is_minimized(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) {return 0;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + Atom *props = 0; + B32 is_hidden = 0; + + int result = XGetWindowProperty(d, + w->window, + os_lnx_gfx_state->net_wm_state_atom, + 0, (~0L), + False, + XA_ATOM, + &actual_type, &actual_format, + &nitems, &bytes_after, (unsigned char **)&props); + + if (result == Success && props != 0) + { + for (unsigned long i = 0; i < nitems; i++) + { + if (props[i] == os_lnx_gfx_state->net_wm_state_hidden_atom) + { + is_hidden = 1; + break; + } + } + XFree(props); + } + // Additionally, check the classic WM_STATE IconicState for compatibility? + // Some older WMs might not fully support EWMH _NET_WM_STATE_HIDDEN. + // Atom wm_state = XInternAtom(d, "WM_STATE", False); + // ... query WM_STATE property ... check for IconicState ... + // For now, just rely on EWMH. + + return is_hidden; +} + +internal void +os_window_set_minimized(OS_Handle handle, B32 minimized) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + + if (minimized) + { + // Request the window manager to iconify (minimize) the window + XIconifyWindow(d, w->window, DefaultScreen(d)); + } + else + { + // Request the window manager to remove the hidden state (un-minimize) + // using EWMH _NET_WM_STATE message. + Window root = XDefaultRootWindow(d); + XEvent e = {0}; + e.type = ClientMessage; + e.xclient.window = w->window; + e.xclient.message_type = os_lnx_gfx_state->net_wm_state_atom; + e.xclient.format = 32; + e.xclient.data.l[0] = 0; // _NET_WM_STATE_REMOVE + e.xclient.data.l[1] = os_lnx_gfx_state->net_wm_state_hidden_atom; + e.xclient.data.l[2] = 0; // No second property + e.xclient.data.l[3] = 1; // Source indication: Normal Application + e.xclient.data.l[4] = 0; + + XSendEvent(d, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &e); + + // Ensure the window is mapped and raised + XMapRaised(d, w->window); + } + XFlush(d); +} + +internal void +os_window_bring_to_front(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + if (w != 0 && d != 0) + { + XRaiseWindow(d, w->window); + XFlush(d); + } +} + +internal void +os_window_set_monitor(OS_Handle handle, OS_Handle monitor) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + Display *d = os_lnx_gfx_state->display; + Window root = XDefaultRootWindow(d); + RROutput output_id = (RROutput)monitor.u64[0]; + + if (w == 0 || d == 0 || output_id == None) { + return; + } + + // Get target monitor geometry + Rng2S32 monitor_rect = {0}; + int monitor_width = 0; + int monitor_height = 0; + XRRScreenResources *res = XRRGetScreenResourcesCurrent(d, root); + if (res) { + XRROutputInfo *output_info = XRRGetOutputInfo(d, res, output_id); + if (output_info && output_info->crtc != None) { + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(d, res, output_info->crtc); + if (crtc_info) { + monitor_rect = r2s32p(crtc_info->x, crtc_info->y, + crtc_info->x + crtc_info->width, + crtc_info->y + crtc_info->height); + monitor_width = crtc_info->width; + monitor_height = crtc_info->height; + XRRFreeCrtcInfo(crtc_info); + } + } + if (output_info) XRRFreeOutputInfo(output_info); + XRRFreeScreenResources(res); + } + + // Check if monitor info was successfully obtained + if (monitor_width <= 0 || monitor_height <= 0) { + return; // Failed to get valid monitor geometry + } + + // Get current window geometry (size only needed) + Window root_return; + int win_x_ignored, win_y_ignored; + unsigned int win_width, win_height, win_border_ignored, win_depth_ignored; + if (!XGetGeometry(d, w->window, &root_return, &win_x_ignored, &win_y_ignored, &win_width, &win_height, &win_border_ignored, &win_depth_ignored)) { + return; // Failed to get window geometry + } + + // Calculate centered position on the target monitor + S32 target_x = monitor_rect.x0 + (monitor_width / 2) - ((S32)win_width / 2); + S32 target_y = monitor_rect.y0 + (monitor_height / 2) - ((S32)win_height / 2); + + // Move the window + XMoveWindow(d, w->window, target_x, target_y); + XFlush(d); // Ensure the request is sent +} + +internal void +os_window_clear_custom_border_data(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} +} + +internal void +os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} +} + +internal void +os_window_push_custom_edges(OS_Handle handle, F32 thickness) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} +} + +internal void +os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) +{ + if(os_handle_match(handle, os_handle_zero())) {return;} +} + +internal Rng2F32 +os_rect_from_window(OS_Handle handle) +{ + Rng2F32 result = r2f32p(0, 0, 0, 0); + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + if (w != 0) + { + Display *d = os_lnx_gfx_state->display; + Window root = XDefaultRootWindow(d); + int x, y; + unsigned int width, height, border_width, depth; + Window child_return; + + // Get window geometry (width, height) + if (XGetGeometry(d, w->window, &root, &x, &y, &width, &height, &border_width, &depth)) + { + // Get window position relative to root + if (XTranslateCoordinates(d, w->window, root, 0, 0, &x, &y, &child_return)) + { + result.x0 = (F32)x; + result.y0 = (F32)y; + result.x1 = (F32)(x + width); + result.y1 = (F32)(y + height); + } + } + } + return result; +} + +internal Rng2F32 +os_client_rect_from_window(OS_Handle handle) +{ + Rng2F32 result = r2f32p(0, 0, 0, 0); + OS_LNX_Window *w = (OS_LNX_Window *)handle.u64[0]; + if (w != 0) + { + Display *d = os_lnx_gfx_state->display; + Window root_return; + int x_return, y_return; + unsigned int width, height, border_width_return, depth_return; + + if (XGetGeometry(d, w->window, &root_return, &x_return, &y_return, &width, &height, &border_width_return, &depth_return)) + { + // Client rect is always 0,0 to width, height relative to the window itself + result.x0 = 0; + result.y0 = 0; + result.x1 = (F32)width; + result.y1 = (F32)height; + } + } + return result; +} + +internal F32 +os_dpi_from_window(OS_Handle handle) +{ + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + if (w != 0 && w->dpi > 0.0f) // Check if DPI is already cached + { + return w->dpi; + } + + // Default DPI + F32 dpi = 96.0f; + Display *d = os_lnx_gfx_state->display; + char *resource_manager = XResourceManagerString(d); + Temp scratch = scratch_begin(0, 0); // Begin scratch arena + + if (resource_manager) + { + XrmDatabase db = XrmGetStringDatabase(resource_manager); + if (db) + { + char *type = NULL; + XrmValue value; + // Try to get Xft.dpi + if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value) == True) + { + if (value.addr && value.size > 0) + { + // Convert string value to float + // Use scratch arena for temporary allocation + char *str_dpi = push_array(scratch.arena, char, value.size + 1); + if(str_dpi) + { + MemoryCopy(str_dpi, value.addr, value.size); // Use MemoryCopy for safety + str_dpi[value.size] = '\0'; + dpi = atof(str_dpi); + // No MemoryFree needed, scratch arena handles cleanup + // Basic validation: DPI shouldn't be zero or negative + if (dpi <= 0) { dpi = 96.0f; } + } + } + } + else + { + // Fallback: Calculate DPI from screen dimensions if Xft.dpi is not found + int screen_num = DefaultScreen(d); + int width_px = DisplayWidth(d, screen_num); + int width_mm = DisplayWidthMM(d, screen_num); + if (width_mm > 0) + { + dpi = (F32)(width_px * 25.4 / width_mm); + if (dpi <= 0) { dpi = 96.0f; } // Sanity check + } + } + XrmDestroyDatabase(db); + } + } + else + { + // Fallback if no resource manager string: Calculate DPI from screen dimensions + int screen_num = DefaultScreen(d); + int width_px = DisplayWidth(d, screen_num); + int width_mm = DisplayWidthMM(d, screen_num); + if (width_mm > 0) + { + dpi = (F32)(width_px * 25.4 / width_mm); + if (dpi <= 0) { dpi = 96.0f; } // Sanity check + } + } + + scratch_end(scratch); // End scratch arena scope + + if (w != 0) // Cache the calculated DPI if window is valid + { + w->dpi = dpi; + } + + return dpi; +} + +//////////////////////////////// +//~ dan: @os_hooks Monitors (Implemented Per-OS) + +internal OS_HandleArray +os_push_monitors_array(Arena *arena) +{ + OS_HandleArray array = {0}; + OS_HandleList list = {0}; + Display *d = os_lnx_gfx_state->display; + Window root = XDefaultRootWindow(d); + + XRRScreenResources *res = XRRGetScreenResourcesCurrent(d, root); + if (res == NULL) + { + return array; // Failed to get resources + } + + for (int i = 0; i < res->noutput; i++) + { + XRROutputInfo *output_info = XRRGetOutputInfo(d, res, res->outputs[i]); + if (output_info == NULL) + { + continue; + } + + // Check if the output is connected and has an active CRTC (is an active monitor) + if (output_info->connection == RR_Connected && output_info->crtc != None) + { + // Consider this an active monitor + // Use the RROutput ID as the handle value + OS_Handle monitor_handle = {(U64)res->outputs[i]}; + os_handle_list_push(arena, &list, monitor_handle); + } + + XRRFreeOutputInfo(output_info); + } + + XRRFreeScreenResources(res); + + array = os_handle_array_from_list(arena, &list); + return array; +} + +internal OS_Handle +os_primary_monitor(void) +{ + OS_Handle result = {0}; + Display *d = os_lnx_gfx_state->display; + Window root = XDefaultRootWindow(d); + + // Use Xrandr to get the primary output + RROutput primary_output = XRRGetOutputPrimary(d, root); + + if (primary_output != None) + { + // Assign the RROutput ID to the handle + result.u64[0] = (U64)primary_output; + } + // else: primary_output is None, meaning no primary output is set or Xrandr failed. + // Return the zero handle in this case. + + return result; +} + +internal OS_Handle +os_monitor_from_window(OS_Handle window) +{ + OS_Handle result = {0}; + OS_LNX_Window *w = (OS_LNX_Window*)window.u64[0]; + Display *d = os_lnx_gfx_state->display; + + if (w == 0 || d == 0) { + return result; // Return zero handle if window or display is invalid + } + + Window root = XDefaultRootWindow(d); + int win_x, win_y; + unsigned int win_width, win_height, win_border, win_depth; + Window child_return; + + // 1. Get window geometry + if (!XGetGeometry(d, w->window, &root, &win_x, &win_y, &win_width, &win_height, &win_border, &win_depth)) { + return result; // Failed to get window geometry + } + // Translate coordinates relative to root + if (!XTranslateCoordinates(d, w->window, root, 0, 0, &win_x, &win_y, &child_return)) { + return result; // Failed to translate coordinates + } + Rng2S32 window_rect = r2s32p(win_x, win_y, win_x + win_width, win_y + win_height); + + // 2. Get monitor resources + XRRScreenResources *res = XRRGetScreenResourcesCurrent(d, root); + if (res == NULL) { + return result; // Failed to get screen resources + } + + RROutput best_output = None; + S64 max_intersection_area = -1; + + // 3. Iterate through monitors + for (int i = 0; i < res->noutput; i++) { + XRROutputInfo *output_info = XRRGetOutputInfo(d, res, res->outputs[i]); + if (output_info == NULL) { + continue; + } + + // Consider only connected outputs with an active CRTC + if (output_info->connection == RR_Connected && output_info->crtc != None) { + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(d, res, output_info->crtc); + if (crtc_info != NULL) { + // 4. Get monitor geometry + Rng2S32 monitor_rect = r2s32p(crtc_info->x, crtc_info->y, + crtc_info->x + crtc_info->width, + crtc_info->y + crtc_info->height); + + // 5. Calculate intersection area + Rng2S32 intersection = intersect_2s32(window_rect, monitor_rect); + S64 area = (S64)dim_2s32(intersection).x * (S64)dim_2s32(intersection).y; + + if (area > max_intersection_area) { + max_intersection_area = area; + best_output = res->outputs[i]; + } + XRRFreeCrtcInfo(crtc_info); + } + } + XRRFreeOutputInfo(output_info); + } + + XRRFreeScreenResources(res); + + // 6. Return handle of the best monitor + if (best_output != None) { + result.u64[0] = (U64)best_output; + } + // If no intersection found, or only disconnected monitors, result remains zero handle. + + return result; +} + +internal String8 +os_name_from_monitor(Arena *arena, OS_Handle monitor) +{ + String8 result = {0}; + Display *d = os_lnx_gfx_state->display; + Window root = XDefaultRootWindow(d); + RROutput output_id = (RROutput)monitor.u64[0]; + + if (output_id == None || d == NULL) { + return result; // Return zero string if handle or display is invalid + } + + XRRScreenResources *res = XRRGetScreenResourcesCurrent(d, root); + if (res == NULL) { + return result; // Failed to get screen resources + } + + XRROutputInfo *output_info = XRRGetOutputInfo(d, res, output_id); + if (output_info != NULL) + { + // Copy the name string into the arena + if (output_info->name != NULL && output_info->nameLen > 0) + { + result = push_str8_copy(arena, str8((U8*)output_info->name, output_info->nameLen)); + } + XRRFreeOutputInfo(output_info); + } + + XRRFreeScreenResources(res); + + return result; +} + +internal Vec2F32 +os_dim_from_monitor(OS_Handle monitor) +{ + Vec2F32 result = v2f32(0, 0); + Display *d = os_lnx_gfx_state->display; + Window root = XDefaultRootWindow(d); + RROutput output_id = (RROutput)monitor.u64[0]; + + if (output_id == None || d == NULL) { + return result; + } + + XRRScreenResources *res = XRRGetScreenResourcesCurrent(d, root); + if (res == NULL) { + return result; + } + + XRROutputInfo *output_info = XRRGetOutputInfo(d, res, output_id); + if (output_info == NULL || output_info->crtc == None) { + if (output_info) XRRFreeOutputInfo(output_info); + XRRFreeScreenResources(res); + return result; + } + + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(d, res, output_info->crtc); + if (crtc_info != NULL) + { + result.x = (F32)crtc_info->width; + result.y = (F32)crtc_info->height; + XRRFreeCrtcInfo(crtc_info); + } + + XRRFreeOutputInfo(output_info); + XRRFreeScreenResources(res); + + return result; +} + +//////////////////////////////// +//~ dan: @os_hooks Events (Implemented Per-OS) + +internal void +os_send_wakeup_event(void) +{ + Display *d = os_lnx_gfx_state->display; + Window root = XDefaultRootWindow(d); + XEvent event = {0}; + event.type = ClientMessage; + event.xclient.display = d; + event.xclient.window = root; // Sending to root, could send to specific window + event.xclient.message_type = os_lnx_gfx_state->wakeup_atom; + event.xclient.format = 32; // size of data items (l[0]...l[4]) in bits + // event.xclient.data.l[0..4] can contain custom data if needed + XSendEvent(d, root, False, NoEventMask, &event); + XFlush(d); +} + +internal OS_EventList +os_get_events(Arena *arena, B32 wait) +{ + OS_EventList evts = {0}; + Display *d = os_lnx_gfx_state->display; // Cache display pointer + + // Check for pending events or wait if requested + if(XPending(d) == 0 && wait) + { + // No events pending, and we need to wait. Use select() on the X connection FD. + int x11_fd = ConnectionNumber(d); + fd_set fds; + FD_ZERO(&fds); + FD_SET(x11_fd, &fds); + + // Select blocks until the FD is readable (meaning X event is available) + // No timeout specified, so it waits indefinitely like GetMessage. + int select_result = select(x11_fd + 1, &fds, NULL, NULL, NULL); + + if (select_result < 0 && errno != EINTR) { + // Handle select error (e.g., log it), but avoid busy-looping. + // For now, just return empty list on error. + perror("select"); + return evts; + } + // If select_result > 0 or == 0 (EINTR), we proceed to check XPending again below. + } + + // Process all currently pending events + while(XPending(d) > 0) + { + XEvent evt = {0}; + XNextEvent(d, &evt); + + // Add global modifier update here if needed, based on event state + // Example: os_lnx_gfx_state->modifiers = os_lnx_get_modifiers_from_state(evt.xkey.state or evt.xbutton.state); + + switch(evt.type) + { + default:{}break; + + //- dan: key presses/releases + case KeyPress: + case KeyRelease: + { + // dan: determine flags + OS_Modifiers flags = 0; + if(evt.xkey.state & ShiftMask) { flags |= OS_Modifier_Shift; } + if(evt.xkey.state & ControlMask) { flags |= OS_Modifier_Ctrl; } + if(evt.xkey.state & Mod1Mask) { flags |= OS_Modifier_Alt; } + + // dan: map keycode -> keysym & possibly text + KeySym keysym = NoSymbol; + char text_buffer[16]; // Buffer for translated characters + int nbytes = XLookupString(&evt.xkey, text_buffer, sizeof(text_buffer), &keysym, NULL); + + // dan: map keysym -> OS_Key + OS_Key key = os_lnx_keysym_to_oskey(keysym); + + // dan: push Press/Release event + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xkey.window); + if (key != OS_Key_Null) // Only push press/release for keys we map + { + OS_Event *press_release_evt = os_event_list_push_new(arena, &evts, evt.type == KeyPress ? OS_EventKind_Press : OS_EventKind_Release); + press_release_evt->window.u64[0] = (U64)window; + press_release_evt->flags = flags; + press_release_evt->key = key; + os_lnx_gfx_state->key_is_down[key] = (evt.type == KeyPress); + } + + // dan: push Text event if applicable (only on KeyPress) + if (evt.type == KeyPress && nbytes > 0) + { + // Filter out control characters except for common ones like tab, enter, backspace if desired + // For simplicity, let's push most things XLookupString gives us. + // A more robust implementation might check iscntrl() but allow specific control codes. + if (!(flags & OS_Modifier_Ctrl) && !(flags & OS_Modifier_Alt)) // Don't push text for Ctrl/Alt combinations usually + { + String8 text_str = str8((U8*)text_buffer, nbytes); + // Decode UTF-8 potentially received from XLookupString + UnicodeDecode decoded = utf8_decode(text_str.str, text_str.size); + U32 codepoint = decoded.codepoint; // Get codepoint directly + if (codepoint != 0 && codepoint != 127) // Skip null and delete + { + OS_Event *text_evt = os_event_list_push_new(arena, &evts, OS_EventKind_Text); + text_evt->window.u64[0] = (U64)window; + text_evt->flags = flags; // Modifiers might be relevant for text sometimes? + text_evt->character = codepoint; + } + } + } + }break; + + //- dan: mouse button presses/releases + case ButtonPress: + case ButtonRelease: + { + // dan: determine flags + OS_Modifiers flags = 0; + if(evt.xbutton.state & ShiftMask) { flags |= OS_Modifier_Shift; } + if(evt.xbutton.state & ControlMask) { flags |= OS_Modifier_Ctrl; } + if(evt.xbutton.state & Mod1Mask) { flags |= OS_Modifier_Alt; } + + // dan: map button -> OS_Key + OS_Key key = OS_Key_Null; + switch(evt.xbutton.button) + { + default:{}break; + case Button1:{key = OS_Key_LeftMouseButton;}break; + case Button2:{key = OS_Key_MiddleMouseButton;}break; + case Button3:{key = OS_Key_RightMouseButton;}break; + case Button4: // Scroll up + case Button5: // Scroll down + { + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xbutton.window); + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_Scroll); + e->window.u64[0] = (U64)window; + e->flags = flags; + e->pos.x = (F32)evt.xbutton.x; + e->pos.y = (F32)evt.xbutton.y; + e->delta.y = (evt.xbutton.button == Button4) ? 120.f : -120.f; // Simulate standard wheel delta + } break; + } + + if (key != OS_Key_Null) + { + // dan: push event + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); + OS_Event *e = os_event_list_push_new(arena, &evts, evt.type == ButtonPress ? OS_EventKind_Press : OS_EventKind_Release); + e->window.u64[0] = (U64)window; + e->flags = flags; + e->key = key; + os_lnx_gfx_state->key_is_down[key] = (evt.type == ButtonPress); + } + }break; + + //- dan: mouse motion + case MotionNotify: + { + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_MouseMove); + e->window.u64[0] = (U64)window; + e->pos.x = (F32)evt.xmotion.x; + e->pos.y = (F32)evt.xmotion.y; + }break; + + //- dan: window focus/unfocus + case FocusIn: + case FocusOut: + { + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xfocus.window); + if (window) { + window->has_focus = (evt.type == FocusIn); + if (evt.type == FocusOut) { + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_WindowLoseFocus); + e->window.u64[0] = (U64)window; + // Clear key state on focus lost? Maybe not necessary if we query directly. + } + } + }break; + + //- dan: client messages + case ClientMessage: + { + if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_delete_window_atom) + { + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_WindowClose); + e->window.u64[0] = (U64)window; + } + else if((Atom)evt.xclient.data.l[0] == os_lnx_gfx_state->wm_sync_request_atom) + { + OS_LNX_Window *window = os_lnx_window_from_x11window(evt.xclient.window); + if(window != 0) + { + window->counter_value = 0; + window->counter_value |= evt.xclient.data.l[2]; + window->counter_value |= (evt.xclient.data.l[3] << 32); + XSyncValue value; + XSyncIntToValue(&value, window->counter_value); + XSyncSetCounter(os_lnx_gfx_state->display, window->counter_xid, value); + } + } + else if (evt.xclient.message_type == os_lnx_gfx_state->wakeup_atom) + { + OS_Event *e = os_event_list_push_new(arena, &evts, OS_EventKind_Wakeup); + // Optional: copy data from event.xclient.data.l if needed + } + }break; + + //- dan: clipboard handling (SelectionRequest / PropertyNotify for getting data) + case SelectionRequest: + { + XSelectionRequestEvent *req = &evt.xselectionrequest; + XEvent response = {0}; + response.type = SelectionNotify; + response.xselection.display = req->display; + response.xselection.requestor = req->requestor; + response.xselection.selection = req->selection; + response.xselection.target = req->target; + response.xselection.property = None; // Default to None (failure) + response.xselection.time = req->time; + + // Only handle requests if we are the owner (using clipboard_window) + if (req->owner == os_lnx_gfx_state->clipboard_window && req->selection == os_lnx_gfx_state->clipboard_atom) + { + if (req->target == os_lnx_gfx_state->targets_atom) + { + // Respond with the list of supported targets + Atom supported_targets[] = { os_lnx_gfx_state->targets_atom, os_lnx_gfx_state->utf8_string_atom }; + XChangeProperty(d, req->requestor, req->property, XA_ATOM, 32, PropModeReplace, (unsigned char*)supported_targets, ArrayCount(supported_targets)); + response.xselection.property = req->property; // Indicate success + } + else if (req->target == os_lnx_gfx_state->utf8_string_atom) + { + // Respond with the actual clipboard data + XChangeProperty(d, req->requestor, req->property, os_lnx_gfx_state->utf8_string_atom, 8, // 8 bits per char for UTF8 + PropModeReplace, os_lnx_gfx_state->clipboard_buffer.str, os_lnx_gfx_state->clipboard_buffer.size); + response.xselection.property = req->property; // Indicate success + } + // Add other target formats here if needed (e.g., TARGETS) + } + // If we are not the owner, or target is unsupported, property remains None (failure) + + XSendEvent(d, req->requestor, False, NoEventMask, &response); + XFlush(d); + } break; + + // This handles the notification that clipboard data is ready after we called XConvertSelection + case PropertyNotify: + { + if (evt.xproperty.state == PropertyNewValue && + evt.xproperty.window == os_lnx_gfx_state->clipboard_window && + evt.xproperty.atom == os_lnx_gfx_state->clipboard_target_prop_atom) + { + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long bytes_after; + unsigned char *data = 0; + int read_result = XGetWindowProperty(d, + os_lnx_gfx_state->clipboard_window, // Read from our dedicated window + os_lnx_gfx_state->clipboard_target_prop_atom, // The property we specified + 0, // Offset 0 + (~0L), // Read the whole property (~0L is common practice for large size) + True, // Delete the property after reading + AnyPropertyType, // Accept any type, but check actual_type later + &actual_type, &actual_format, &nitems, &bytes_after, &data); + + if (read_result == Success && data != 0) + { + if (actual_type == os_lnx_gfx_state->utf8_string_atom && actual_format == 8) + { + arena_clear(os_lnx_gfx_state->clipboard_arena); // Use clipboard_arena + os_lnx_gfx_state->last_retrieved_clipboard_text = push_str8_copy(os_lnx_gfx_state->clipboard_arena, str8(data, nitems)); // Use clipboard_arena + } + XFree(data); + } + } + } break; + } + } + return evts; +} + +internal OS_Modifiers +os_get_modifiers(void) +{ + OS_Modifiers mods = 0; + Display *d = os_lnx_gfx_state->display; + if (d == 0) { return mods; } // Return 0 if display is invalid + + char keys_return[32]; // XQueryKeymap returns 32 bytes + XQueryKeymap(d, keys_return); + + // Check Shift + KeyCode kc_l = os_lnx_gfx_state->keycode_lshift; + KeyCode kc_r = os_lnx_gfx_state->keycode_rshift; + if ((kc_l != 0 && (keys_return[kc_l / 8] & (1 << (kc_l % 8)))) || + (kc_r != 0 && (keys_return[kc_r / 8] & (1 << (kc_r % 8))))) + { + mods |= OS_Modifier_Shift; + } + + // Check Ctrl + kc_l = os_lnx_gfx_state->keycode_lctrl; + kc_r = os_lnx_gfx_state->keycode_rctrl; + if ((kc_l != 0 && (keys_return[kc_l / 8] & (1 << (kc_l % 8)))) || + (kc_r != 0 && (keys_return[kc_r / 8] & (1 << (kc_r % 8))))) + { + mods |= OS_Modifier_Ctrl; + } + + // Check Alt + kc_l = os_lnx_gfx_state->keycode_lalt; + kc_r = os_lnx_gfx_state->keycode_ralt; + if ((kc_l != 0 && (keys_return[kc_l / 8] & (1 << (kc_l % 8)))) || + (kc_r != 0 && (keys_return[kc_r / 8] & (1 << (kc_r % 8))))) + { + mods |= OS_Modifier_Alt; + } + + return mods; +} + +internal B32 +os_key_is_down(OS_Key key) +{ + if (key >= OS_Key_COUNT) + { + return 0; + } + + // dan: query real-time key state instead of relying on cached event state + Display *d = os_lnx_gfx_state->display; + if (d == 0) { // Check if display is valid + return 0; + } + KeyCode kc_left = 0, kc_right = 0; + + // Check if it's a modifier key and get specific left/right codes + if (key == OS_Key_Shift) + { + kc_left = os_lnx_gfx_state->keycode_lshift; + kc_right = os_lnx_gfx_state->keycode_rshift; + } + else if (key == OS_Key_Ctrl) + { + kc_left = os_lnx_gfx_state->keycode_lctrl; + kc_right = os_lnx_gfx_state->keycode_rctrl; + } + else if (key == OS_Key_Alt) + { + kc_left = os_lnx_gfx_state->keycode_lalt; + kc_right = os_lnx_gfx_state->keycode_ralt; + } + else + { + // Regular key, use the main table + kc_left = os_lnx_gfx_state->keycode_from_oskey[key]; + // kc_right remains 0 + } + + // Check if at least one keycode is valid + if (kc_left == 0 && kc_right == 0) + { + return 0; // Unmapped key + } + + char keys_return[32]; // XQueryKeymap returns 32 bytes + XQueryKeymap(d, keys_return); + + B32 is_down = 0; + // Check the bit corresponding to the left KeyCode + if (kc_left != 0 && (keys_return[kc_left / 8] & (1 << (kc_left % 8)))) + { + is_down = 1; + } + // Check the bit corresponding to the right KeyCode if applicable + if (!is_down && kc_right != 0 && (keys_return[kc_right / 8] & (1 << (kc_right % 8)))) + { + is_down = 1; + } + + return is_down; +} + +internal Vec2F32 +os_mouse_from_window(OS_Handle handle) +{ + Vec2F32 result = v2f32(0, 0); + OS_LNX_Window *w = (OS_LNX_Window*)handle.u64[0]; + if (w) + { + Window root_return, child_return; + int root_x_return, root_y_return, win_x_return, win_y_return; + unsigned int mask_return; + if (XQueryPointer(os_lnx_gfx_state->display, w->window, &root_return, &child_return, + &root_x_return, &root_y_return, &win_x_return, &win_y_return, &mask_return)) + { + result.x = (F32)win_x_return; + result.y = (F32)win_y_return; + } + } + return result; +} + +//////////////////////////////// +//~ dan: @os_hooks Cursors (Implemented Per-OS) + +internal void +os_set_cursor(OS_Cursor cursor) +{ + if (cursor < OS_Cursor_COUNT) + { + Cursor xcursor = os_lnx_gfx_state->cursors[cursor]; + if (xcursor != os_lnx_gfx_state->current_cursor) + { + os_lnx_gfx_state->current_cursor = xcursor; + // Apply to all windows, or just the focused one? Apply to all for now. + for(OS_LNX_Window *w = os_lnx_gfx_state->first_window; w != 0; w = w->next) + { + XDefineCursor(os_lnx_gfx_state->display, w->window, xcursor); + } + XFlush(os_lnx_gfx_state->display); + } + } +} + +//////////////////////////////// +//~ dan: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) + +internal void +os_graphical_message(B32 error, String8 title, String8 message) +{ + Temp scratch = scratch_begin(0, 0); + String8List cmd_line = {0}; + + str8_list_push(scratch.arena, &cmd_line, str8_lit("zenity")); + if(error) + { + str8_list_push(scratch.arena, &cmd_line, str8_lit("--error")); + } + else + { + str8_list_push(scratch.arena, &cmd_line, str8_lit("--info")); + } + + // Format title and message arguments safely for the command line + String8 title_arg = push_str8f(scratch.arena, "--title=\"%S\"", title); + String8 text_arg = push_str8f(scratch.arena, "--text=\"%S\"", message); + + str8_list_push(scratch.arena, &cmd_line, title_arg); + str8_list_push(scratch.arena, &cmd_line, text_arg); + + // Launch the process + OS_ProcessLaunchParams params = {0}; + params.cmd_line = cmd_line; + params.inherit_env = 1; + params.consoleless = 1; // Don't need a console for zenity + + OS_Handle proc_handle = os_process_launch(¶ms); + + // We don't need to wait for the dialog, detach immediately. + if (!os_handle_match(proc_handle, os_handle_zero())) + { + os_process_detach(proc_handle); + } + scratch_end(scratch); +} + +//////////////////////////////// +//~ dan: @os_hooks Shell Operations + +internal void +os_show_in_filesystem_ui(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + + // Get the parent directory of the path + String8 dir_path = str8_chop_last_slash(path_copy); + if (dir_path.size == 0) // Handle root or relative path in cwd + { + dir_path = str8_lit("."); + } + + // Construct the command line: xdg-open + String8List cmd_line = {0}; + str8_list_push(scratch.arena, &cmd_line, str8_lit("xdg-open")); + str8_list_push(scratch.arena, &cmd_line, dir_path); + + // Launch the process + OS_ProcessLaunchParams params = {0}; + params.cmd_line = cmd_line; + params.inherit_env = 1; + params.consoleless = 1; // Don't need a console for xdg-open + + OS_Handle proc_handle = os_process_launch(¶ms); + + // We don't need to wait for xdg-open, detach immediately. + if (!os_handle_match(proc_handle, os_handle_zero())) + { + os_process_detach(proc_handle); + } + scratch_end(scratch); +} + +internal void +os_open_in_browser(String8 url) +{ + Temp scratch = scratch_begin(0, 0); + // Construct the command line: xdg-open + String8List cmd_line = {0}; + str8_list_push(scratch.arena, &cmd_line, str8_lit("xdg-open")); + str8_list_push(scratch.arena, &cmd_line, url); // Pass URL directly + + // Launch the process + OS_ProcessLaunchParams params = {0}; + params.cmd_line = cmd_line; + params.inherit_env = 1; + params.consoleless = 1; // Don't need a console for xdg-open + + OS_Handle proc_handle = os_process_launch(¶ms); + + // We don't need to wait for xdg-open, detach immediately. + if (!os_handle_match(proc_handle, os_handle_zero())) + { + os_process_detach(proc_handle); + } + scratch_end(scratch); +} + diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index 4ac97ab30..7a6b28e98 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -1,56 +1,89 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_GFX_LINUX_H -#define OS_GFX_LINUX_H - -//////////////////////////////// -//~ rjf: Includes - -#include -#include -#include -#include -#include - -//////////////////////////////// -//~ rjf: Window State - -typedef struct OS_LNX_Window OS_LNX_Window; -struct OS_LNX_Window -{ - OS_LNX_Window *next; - OS_LNX_Window *prev; - Window window; - XID counter_xid; - U64 counter_value; -}; - -//////////////////////////////// -//~ rjf: State Bundle - -typedef struct OS_LNX_GfxState OS_LNX_GfxState; -struct OS_LNX_GfxState -{ - Arena *arena; - Display *display; - OS_LNX_Window *first_window; - OS_LNX_Window *last_window; - OS_LNX_Window *free_window; - Atom wm_delete_window_atom; - Atom wm_sync_request_atom; - Atom wm_sync_request_counter_atom; - OS_GfxInfo gfx_info; -}; - -//////////////////////////////// -//~ rjf: Globals - -global OS_LNX_GfxState *os_lnx_gfx_state = 0; - -//////////////////////////////// -//~ rjf: Helpers - -internal OS_LNX_Window *os_lnx_window_from_x11window(Window window); - -#endif // OS_GFX_LINUX_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_GFX_LINUX_H +#define OS_GFX_LINUX_H + +//////////////////////////////// +//~ dan: Includes + +// Corrected Order: GLEW first +#include // For OpenGL extensions +#include // For GLX types +#include // For GLX extensions + +// Add typedef for function pointer +typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*, GLXFBConfig, GLXContext, Bool, const int*); + +#include +#include +#include +#include +#include +#include +#include +#include +//////////////////////////////// +//~ dan: Window State + +typedef struct OS_LNX_Window OS_LNX_Window; +struct OS_LNX_Window +{ + OS_LNX_Window *next; + OS_LNX_Window *prev; + Window window; + XID counter_xid; + U64 counter_value; + B32 has_focus; + F32 dpi; + GLXContext gl_context; +}; + +//////////////////////////////// +//~ dan: State Bundle + +typedef struct OS_LNX_GfxState OS_LNX_GfxState; +struct OS_LNX_GfxState +{ + Arena *arena; + Display *display; + OS_LNX_Window *first_window; + OS_LNX_Window *last_window; + OS_LNX_Window *free_window; + Atom wm_delete_window_atom; + Atom wm_sync_request_atom; + Atom wm_sync_request_counter_atom; + Atom clipboard_atom; + Atom targets_atom; + Atom utf8_string_atom; + Atom wakeup_atom; + String8 clipboard_buffer; + Window clipboard_window; + Atom clipboard_target_prop_atom; + String8 last_retrieved_clipboard_text; + Arena *clipboard_arena; + B8 key_is_down[OS_Key_COUNT]; + KeyCode keycode_from_oskey[OS_Key_COUNT]; + KeyCode keycode_lshift; KeyCode keycode_rshift; + KeyCode keycode_lctrl; KeyCode keycode_rctrl; + KeyCode keycode_lalt; KeyCode keycode_ralt; + Cursor cursors[OS_Cursor_COUNT]; + Cursor current_cursor; + OS_GfxInfo gfx_info; + GLXFBConfig fb_config; + B32 glew_initialized; + + // EWMH Atoms + Atom net_wm_state_atom; + Atom net_wm_state_maximized_vert_atom; + Atom net_wm_state_maximized_horz_atom; + Atom net_wm_state_hidden_atom; + Atom net_wm_state_fullscreen_atom; +}; + +//////////////////////////////// +//~ dan: Globals + +global OS_LNX_GfxState *os_lnx_gfx_state = 0; + +#endif // OS_GFX_LINUX_H diff --git a/src/os/gfx/os_gfx.h b/src/os/gfx/os_gfx.h index f2a860d7c..8dd064b3c 100644 --- a/src/os/gfx/os_gfx.h +++ b/src/os/gfx/os_gfx.h @@ -91,6 +91,7 @@ struct OS_Event Vec2F32 pos; Vec2F32 delta; String8List strings; + U64 flags; }; typedef struct OS_EventList OS_EventList; diff --git a/src/render/opengl/generated/render_opengl.meta.c b/src/render/opengl/generated/render_opengl.meta.c new file mode 100644 index 000000000..6bfa08431 --- /dev/null +++ b/src/render/opengl/generated/render_opengl.meta.c @@ -0,0 +1,49 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +C_LINKAGE_BEGIN +String8 * r_gl_g_vshad_kind_source_ptr_table[4] = +{ +&r_gl_g_rect_shader_vs_src, +&r_gl_g_blur_shader_vs_src, +&r_gl_g_mesh_shader_vs_src, +&r_gl_g_finalize_shader_vs_src, +}; + +String8 r_gl_g_vshad_kind_source_name_table[4] = +{ +str8_lit_comp("r_gl_g_rect_shader_vs_src"), +str8_lit_comp("r_gl_g_blur_shader_vs_src"), +str8_lit_comp("r_gl_g_mesh_shader_vs_src"), +str8_lit_comp("r_gl_g_finalize_shader_vs_src"), +}; + +String8 * r_gl_g_pshad_kind_source_ptr_table[5] = +{ +&r_gl_g_rect_shader_fs_src, +&r_gl_g_blur_shader_fs_src, +&r_gl_g_mesh_shader_fs_src, +&r_gl_g_geo3dcomposite_shader_fs_src, +&r_gl_g_finalize_shader_fs_src, +}; + +String8 r_gl_g_pshad_kind_source_name_table[5] = +{ +str8_lit_comp("r_gl_g_rect_shader_fs_src"), +str8_lit_comp("r_gl_g_blur_shader_fs_src"), +str8_lit_comp("r_gl_g_mesh_shader_fs_src"), +str8_lit_comp("r_gl_g_geo3dcomposite_shader_fs_src"), +str8_lit_comp("r_gl_g_finalize_shader_fs_src"), +}; + +U64 r_gl_g_uniform_type_kind_size_table[3] = +{ +sizeof(R_GL_Uniforms_Rect), +sizeof(R_GL_Uniforms_Blur), +sizeof(R_GL_Uniforms_Mesh), +}; + +C_LINKAGE_END + diff --git a/src/render/opengl/generated/render_opengl.meta.h b/src/render/opengl/generated/render_opengl.meta.h new file mode 100644 index 000000000..74189b980 --- /dev/null +++ b/src/render/opengl/generated/render_opengl.meta.h @@ -0,0 +1,477 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + +#ifndef RENDER_OPENGL_META_H +#define RENDER_OPENGL_META_H + +typedef enum R_GL_VShadKind +{ +R_GL_VShadKind_Rect, +R_GL_VShadKind_Blur, +R_GL_VShadKind_Mesh, +R_GL_VShadKind_FullscreenQuad, +R_GL_VShadKind_COUNT, +} R_GL_VShadKind; + +typedef enum R_GL_PShadKind +{ +R_GL_PShadKind_Rect, +R_GL_PShadKind_Blur, +R_GL_PShadKind_Mesh, +R_GL_PShadKind_Geo3DComposite, +R_GL_PShadKind_Finalize, +R_GL_PShadKind_COUNT, +} R_GL_PShadKind; + +typedef enum R_GL_UniformTypeKind +{ +R_GL_UniformTypeKind_Rect, +R_GL_UniformTypeKind_Blur, +R_GL_UniformTypeKind_Mesh, +R_GL_UniformTypeKind_COUNT, +} R_GL_UniformTypeKind; + +C_LINKAGE_BEGIN +read_only global String8 r_gl_g_rect_shader_vs_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"// Instance inputs matching VAO setup (8 vec4s)\n" +"layout(location = 0) in vec4 dst_rect_px_in; // {x0, y0, x1, y1}\n" +"layout(location = 1) in vec4 src_rect_px_in; // {x0, y0, x1, y1}\n" +"layout(location = 2) in vec4 color00_in; // {r, g, b, a} BL\n" +"layout(location = 3) in vec4 color01_in; // {r, g, b, a} TL\n" +"layout(location = 4) in vec4 color10_in; // {r, g, b, a} BR\n" +"layout(location = 5) in vec4 color11_in; // {r, g, b, a} TR\n" +"layout(location = 6) in vec4 corner_radii_px_in; // {bl, tl, br, tr} - CHECK ORDER vs D3D11\n" +"layout(location = 7) in vec4 style_params_in; // {border, soft, omit_tex, unused}\n" +"\n" +"// Uniforms (Unchanged)\n" +"layout (std140) uniform R_GL_Uniforms_Rect { // Binding Point 0\n" +" vec2 viewport_size_px;\n" +" float opacity;\n" +" mat4 texture_sample_channel_map;\n" +" vec2 texture_t2d_size_px;\n" +" mat3 xform; // Column-major\n" +" vec2 xform_scale;\n" +"};\n" +"\n" +"// Outputs to Fragment Shader (Matches D3D11 rect shader outputs)\n" +"out VS_OUT {\n" +" vec2 rect_half_size_px;\n" +" vec2 texcoord_pct;\n" +" vec2 sdf_sample_pos;\n" +" vec4 tint;\n" +" float corner_radius_px;\n" +" flat float border_thickness_px;\n" +" flat float softness_px;\n" +" flat float omit_texture;\n" +"} vs_out;\n" +"\n" +"void main() {\n" +" // Unpack instance data\n" +" vec2 dst_p0_px = dst_rect_px_in.xy;\n" +" vec2 dst_p1_px = dst_rect_px_in.zw;\n" +" vec2 src_p0_px = src_rect_px_in.xy;\n" +" vec2 src_p1_px = src_rect_px_in.zw;\n" +" vec2 dst_size_px = abs(dst_p1_px - dst_p0_px);\n" +"\n" +" float border_thickness_px = style_params_in.x;\n" +" float softness_px = style_params_in.y;\n" +" float omit_texture = style_params_in.z;\n" +"\n" +" // Generate standard CCW triangle strip quad vertex percentages (0,0), (1,0), (0,1), (1,1)\n" +" // gl_VertexID 0 -> (0,0) -> BL\n" +" // gl_VertexID 1 -> (1,0) -> BR\n" +" // gl_VertexID 2 -> (0,1) -> TL\n" +" // gl_VertexID 3 -> (1,1) -> TR\n" +" vec2 vertex_pct = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1));\n" +"\n" +" // Map vertex percentage to destination rect coordinates\n" +" vec2 current_dst_pos_px = mix(dst_p0_px, dst_p1_px, vertex_pct);\n" +"\n" +" // Calculate output position\n" +" vec3 xformed_pos = xform * vec3(current_dst_pos_px, 1.0);\n" +" vec2 ndc_pos = (xformed_pos.xy / viewport_size_px) * 2.0 - 1.0;\n" +" ndc_pos.y = -ndc_pos.y; // Flip Y for GL NDC\n" +" gl_Position = vec4(ndc_pos, 0.0, 1.0);\n" +"\n" +" // Map vertex percentage to source rect coordinates for texture coords\n" +" vec2 current_src_pos_px = mix(src_p0_px, src_p1_px, vertex_pct);\n" +" vs_out.texcoord_pct = current_src_pos_px / texture_t2d_size_px;\n" +"\n" +" // Select color based on vertex ID (matching D3D11 logic for vertex IDs 0..3 -> TL, BL, TR, BR)\n" +" vec4 color_bl = color00_in; // Location 2\n" +" vec4 color_br = color10_in; // Location 4\n" +" vec4 color_tl = color01_in; // Location 3\n" +" vec4 color_tr = color11_in; // Location 5\n" +" vec4 colors[4] = vec4[4](color_bl, color_br, color_tl, color_tr); // GLSL array constructor syntax\n" +" vs_out.tint = colors[gl_VertexID];\n" +"\n" +" // Select corner radius based on vertex ID (matching D3D11 logic)\n" +" // GL Vertex IDs 0, 1, 2, 3 correspond to BL, BR, TL, TR based on vertex_pct calc\n" +" // Input corner_radii_px_in (loc 6) is {bl, tl, br, tr} -> {x, y, z, w}\n" +" // D3D11 mapping (see thought process): ID 0 (BL)->x, ID 1 (BR)->z, ID 2 (TL)->y, ID 3 (TR)->w\n" +" float radii[4] = float[4](corner_radii_px_in.x, corner_radii_px_in.z, corner_radii_px_in.y, corner_radii_px_in.w); // GLSL array constructor syntax\n" +" vs_out.corner_radius_px = radii[gl_VertexID];\n" +"\n" +" // Calculate SDF sample position based on vertex percentage relative to center\n" +" vs_out.rect_half_size_px = dst_size_px / 2.0 * xform_scale;\n" +" vs_out.sdf_sample_pos = (2.0 * vertex_pct - 1.0) * vs_out.rect_half_size_px;\n" +"\n" +" // Pass through flat values\n" +" vs_out.border_thickness_px = border_thickness_px;\n" +" vs_out.softness_px = softness_px;\n" +" vs_out.omit_texture = omit_texture;\n" +"}\n" +"" +); + +read_only global String8 r_gl_g_rect_shader_fs_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"in VS_OUT {\n" +" vec2 rect_half_size_px;\n" +" vec2 texcoord_pct;\n" +" vec2 sdf_sample_pos;\n" +" vec4 tint;\n" +" float corner_radius_px;\n" +" flat float border_thickness_px;\n" +" flat float softness_px;\n" +" flat float omit_texture;\n" +"} fs_in;\n" +"\n" +"layout (std140) uniform R_GL_Uniforms_Rect // Binding Point 0\n" +"{\n" +" vec2 viewport_size_px;\n" +" float opacity;\n" +" // float _padding0_;\n" +" mat4 texture_sample_channel_map;\n" +" vec2 texture_t2d_size_px;\n" +" // vec2 translate;\n" +" mat3 xform;\n" +" vec2 xform_scale;\n" +" // vec2 _padding1_;\n" +"};\n" +"\n" +"uniform sampler2D main_t2d; // Texture Unit 0\n" +"\n" +"out vec4 FragColor;\n" +"\n" +"// Corrected SDF function\n" +"float rect_sdf(vec2 sample_pos, vec2 rect_half_size, float r)\n" +"{\n" +" vec2 d = abs(sample_pos) - rect_half_size + r;\n" +" return length(max(d, 0.0)) - r;\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" // Tint is already interpolated\n" +" vec4 tint = fs_in.tint;\n" +"\n" +" // Sample texture\n" +" vec4 albedo_sample = vec4(1.0);\n" +" if(fs_in.omit_texture < 0.5) // Use < 0.5 for bool comparison\n" +" {\n" +" // REMOVED explicit V flip added previously. Use coords directly from VS.\n" +" albedo_sample = texture(main_t2d, fs_in.texcoord_pct) * texture_sample_channel_map;\n" +" }\n" +"\n" +" // Determine SDF sample position (already interpolated)\n" +" vec2 sdf_sample_pos = fs_in.sdf_sample_pos; // Keep for potential future use\n" +"\n" +" // Sample for borders\n" +" float border_sdf_t = 1.0; // Default to 1 (no border effect)\n" +" float softness_px = max(fs_in.softness_px, 0.01); // Ensure softness is positive\n" +" float border_thickness_px = fs_in.border_thickness_px;\n" +" if(border_thickness_px > 0.0)\n" +" {\n" +" // Adjust half size by softness before calculating SDF, matching D3D11\n" +" vec2 inner_half_size = fs_in.rect_half_size_px - vec2(softness_px * 2.0) - border_thickness_px;\n" +" float inner_radius = max(fs_in.corner_radius_px - border_thickness_px, 0.0);\n" +" float border_sdf_s = rect_sdf(sdf_sample_pos, inner_half_size, inner_radius);\n" +"\n" +" float border_smooth_range = max(2.0 * softness_px, 1.0);\n" +" // Calculate coverage *outside* the inner edge (0 inside fill, ramps to 1 outside)\n" +" border_sdf_t = smoothstep(0.0, border_smooth_range, border_sdf_s);\n" +" }\n" +"\n" +" // Sample for corners (outer edge)\n" +" float corner_sdf_t = 1.0; // Default to 1 (no corner effect)\n" +" // Check radius OR softness > 0.75 (D3D11 logic)\n" +" if(fs_in.corner_radius_px > 0.0 || softness_px > 0.75)\n" +" {\n" +" // Adjust half size by softness before calculating SDF, matching D3D11\n" +" vec2 outer_half_size = fs_in.rect_half_size_px - vec2(softness_px * 2.0);\n" +" float outer_radius = max(fs_in.corner_radius_px, 0.0);\n" +" float corner_sdf_s = rect_sdf(sdf_sample_pos, outer_half_size, outer_radius);\n" +"\n" +" float corner_smooth_range = max(2.0 * softness_px, 1.0);\n" +" // Calculate coverage *inside* the outer edge (1 inside, ramps to 0 outside)\n" +" corner_sdf_t = 1.0 - smoothstep(0.0, corner_smooth_range, corner_sdf_s);\n" +" }\n" +"\n" +" // Form final color - Multiply tint and texture colors, combine alphas\n" +" FragColor.rgb = tint.rgb * albedo_sample.rgb; // Multiply RGB components\n" +" FragColor.a = tint.a * albedo_sample.a; // Multiply Alpha components\n" +"\n" +" // Apply global opacity and SDF alpha factors\n" +" FragColor.a *= opacity;\n" +" FragColor.a *= corner_sdf_t; // Multiply by corner coverage\n" +" FragColor.a *= border_sdf_t; // Multiply by border coverage\n" +"\n" +" // Final discard if alpha is near zero\n" +" if (FragColor.a < 0.001)\n" +" {\n" +" discard;\n" +" }\n" +"}\n" +"" +); + +read_only global String8 r_gl_g_blur_shader_vs_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"// Define uniforms matching the FS for rect/corners\n" +"layout (std140) uniform BlurUniforms // Binding Point 1 \n" +"{\n" +" vec4 rect; // vec4(min.x, min.y, max.x, max.y) \n" +" vec4 corner_radii_px; // vec4(bl, tl, br, tr) \n" +" vec2 direction; // vec2(dx, dy) unit vector for blur pass \n" +" vec2 viewport_size; // vec2(width, height) of render target \n" +" uint blur_count; // Number of samples (adjusted for bilinear) \n" +" // padding... \n" +" vec4 kernel[32]; // vec4(weight, offset, unused, unused) \n" +"};\n" +"\n" +"// Define output block to pass SDF data to FS\n" +"out VS_OUT {\n" +" vec2 sdf_sample_pos;\n" +" flat vec2 rect_half_size;\n" +" float corner_radius;\n" +"} vs_out;\n" +"\n" +"out vec2 TexCoord;\n" +"\n" +"void main()\n" +"{\n" +" // Fullscreen quad vertices (-1,-1), (1,-1), (-1, 1), (1, 1) using triangle strip\n" +" // VertexID: 0 -> (-1,-1), 1 -> (1,-1), 2 -> (-1, 1), 3 -> (1, 1)\n" +" vec2 pos = vec2( (gl_VertexID << 1) - 1.0, (gl_VertexID & 1) * 2.0 - 1.0 );\n" +" gl_Position = vec4(pos, 0.0, 1.0);\n" +" \n" +" // Texcoord (0,0) bottom-left to (1,1) top-right \n" +" TexCoord = pos * 0.5 + 0.5; \n" +" // TexCoord.y = 1.0 - TexCoord.y; // Y is already correct for fullscreen quad\n" +"\n" +" // Calculate SDF inputs\n" +" vs_out.rect_half_size = (rect.zw - rect.xy) * 0.5;\n" +" \n" +" // Determine cornercoords based on vertex ID (0: BL, 1: BR, 2: TL, 3: TR)\n" +" vec2 cornercoords_pct = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1)); // Renamed from cornercoords__pct\n" +" vs_out.sdf_sample_pos = (2.0 * cornercoords_pct - 1.0) * vs_out.rect_half_size;\n" +"\n" +" // Select corner radius based on vertex ID\n" +" // ID 0 (BL) -> radii.x\n" +" // ID 1 (BR) -> radii.z\n" +" // ID 2 (TL) -> radii.y\n" +" // ID 3 (TR) -> radii.w\n" +" if(gl_VertexID == 0) vs_out.corner_radius = corner_radii_px.x;\n" +" else if(gl_VertexID == 1) vs_out.corner_radius = corner_radii_px.z;\n" +" else if(gl_VertexID == 2) vs_out.corner_radius = corner_radii_px.y;\n" +" else /* ID 3 */ vs_out.corner_radius = corner_radii_px.w;\n" +"\n" +" // D3D11 vertex shader passes rect_half_size - 2.0. Match this.\n" +" vs_out.rect_half_size -= 2.0; \n" +"}\n" +"" +); + +read_only global String8 r_gl_g_blur_shader_fs_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"in vec2 TexCoord;\n" +"// Added input block matching the VS structure (even if VS isn't updated yet)\n" +"in VS_OUT {\n" +" vec2 sdf_sample_pos;\n" +" flat vec2 rect_half_size;\n" +" float corner_radius;\n" +"} fs_in;\n" +"out vec4 FragColor;\n" +"\n" +"uniform sampler2D main_t2d; // Texture Unit 0 \n" +"\n" +"layout (std140) uniform BlurUniforms // Binding Point 1 \n" +"{\n" +" vec4 rect; // vec4(min.x, min.y, max.x, max.y) \n" +" vec4 corner_radii_px; // vec4(bl, tl, br, tr) \n" +" vec2 direction; // vec2(dx, dy) unit vector for blur pass \n" +" vec2 viewport_size; // vec2(width, height) of render target \n" +" uint blur_count; // Number of samples (adjusted for bilinear) \n" +" // padding... \n" +" vec4 kernel[32]; // vec4(weight, offset, unused, unused) \n" +"};\n" +"\n" +"// SDF for rounded rectangle \n" +"float rect_sdf(vec2 sample_pos, vec2 rect_half_size, float r)\n" +"{\n" +" vec2 d = abs(sample_pos) - rect_half_size + r;\n" +" return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r;\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" vec4 sum = vec4(0.0);\n" +" vec2 uv = TexCoord;\n" +" vec2 texelSize = 1.0 / viewport_size;\n" +" \n" +" // Apply first weight (at offset 0)\n" +" sum += texture(main_t2d, uv) * kernel[0].x;\n" +" \n" +" // Apply remaining weights using bilinear optimization\n" +" for(uint i = 1u; i < blur_count; ++i)\n" +" {\n" +" vec2 offset = direction * kernel[i].y * texelSize;\n" +" sum += texture(main_t2d, uv + offset) * kernel[i].x;\n" +" sum += texture(main_t2d, uv - offset) * kernel[i].x;\n" +" }\n" +" \n" +" // Clip to rounded rectangle using SDF\n" +" float corner_sdf_s = rect_sdf(fs_in.sdf_sample_pos, fs_in.rect_half_size, fs_in.corner_radius);\n" +" // D3D11 uses smoothstep(0, 2*softness, ...), let's use a small fixed softness for now\n" +" float softness = 1.0; \n" +" float corner_sdf_t = 1.0 - smoothstep(-softness, softness, corner_sdf_s);\n" +"\n" +" // Discard fragments outside the rounded rectangle (similar to D3D11)\n" +" if (corner_sdf_t < 0.001) // Use a small threshold instead of 0.9 check\n" +" {\n" +" discard;\n" +" }\n" +"\n" +" FragColor = vec4(sum.rgb, 1.0); // Output color, force alpha to 1\n" +"\n" +" // Optional: Apply coverage to alpha if needed later\n" +" // FragColor.a = corner_sdf_t; \n" +"}\n" +"" +); + +read_only global String8 r_gl_g_mesh_shader_vs_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"layout (location = 0) in vec3 aPos;\n" +"layout (location = 1) in vec3 aNormal;\n" +"layout (location = 2) in vec2 aTexCoord;\n" +"layout (location = 3) in vec3 aColor;\n" +"layout (location = 4) in mat4 instance_xform; // Per-instance transform \n" +"\n" +"layout (std140) uniform R_GL_Uniforms_Mesh { // Use correct uniform block name matching C struct\n" +" mat4 view_proj_matrix; // Combined view * projection \n" +"};\n" +"\n" +"out vec3 FragPos;\n" +"out vec3 Normal;\n" +"out vec2 TexCoords;\n" +"out vec3 VertexColor;\n" +"\n" +"void main()\n" +"{\n" +" vec4 worldPos = instance_xform * vec4(aPos, 1.0);\n" +" gl_Position = view_proj_matrix * worldPos;\n" +" FragPos = vec3(worldPos);\n" +" Normal = mat3(transpose(inverse(instance_xform))) * aNormal; // Correct normal transformation\n" +" TexCoords = aTexCoord;\n" +" VertexColor = aColor;\n" +"}\n" +"" +); + +read_only global String8 r_gl_g_mesh_shader_fs_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"out vec4 FragColor;\n" +"\n" +"in vec3 FragPos;\n" +"in vec3 Normal;\n" +"in vec2 TexCoords;\n" +"in vec3 VertexColor;\n" +"\n" +"uniform sampler2D main_t2d; // Texture Unit 0 \n" +"\n" +"void main()\n" +"{\n" +" // Simple: Output vertex color modulated by texture (if available) \n" +" // vec4 texColor = texture(main_t2d, TexCoords); \n" +" FragColor = vec4(VertexColor, 1.0); // * texColor; // Modulate later if needed \n" +" // Add lighting calculations here if needed \n" +"}\n" +"" +); + +read_only global String8 r_gl_g_geo3dcomposite_shader_fs_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"in vec2 TexCoord;\n" +"uniform sampler2D main_t2d; // Renamed from stage_t2d \n" +"out vec4 FragColor;\n" +"void main()\n" +"{\n" +" FragColor = texture(main_t2d, TexCoord); // Use renamed uniform\n" +"}\n" +"" +); + +read_only global String8 r_gl_g_finalize_shader_vs_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"out vec2 TexCoord;\n" +"void main()\n" +"{\n" +" // Correct way for (-1,-1), (1,-1), (-1, 1), (1, 1) triangle strip \n" +" vec2 pos = vec2( (gl_VertexID & 1) * 2.0 - 1.0, (gl_VertexID & 2) - 1.0 ); \n" +" gl_Position = vec4(pos, 0.0, 1.0);\n" +" // Texcoord (0,0) bottom-left to (1,1) top-right \n" +" TexCoord = pos * 0.5 + 0.5;\n" +" // TexCoord.y = 1.0 - TexCoord.y; // Flip V if input texture requires it \n" +"}\n" +"" +); + +read_only global String8 r_gl_g_finalize_shader_fs_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"in vec2 TexCoord;\n" +"uniform sampler2D main_t2d; // Renamed from stage_t2d \n" +"out vec4 FragColor;\n" +"void main()\n" +"{\n" +" FragColor = texture(main_t2d, TexCoord); // Use renamed uniform\n" +" FragColor.a = 1.0; // Force alpha to 1 for final output \n" +"}\n" +"" +); + + +C_LINKAGE_END + +#endif // RENDER_OPENGL_META_H diff --git a/src/render/opengl/render_opengl.c b/src/render/opengl/render_opengl.c new file mode 100644 index 000000000..07e72d0dd --- /dev/null +++ b/src/render/opengl/render_opengl.c @@ -0,0 +1,2635 @@ +//////////////////////////////// +//~ dan: Generated Code + +#include "generated/render_opengl.meta.c" + + +//////////////////////////////// +//~ dan: Helper function for rounding a float to an integer +internal S32 +round_f32_s32(F32 v) +{ + return (S32)round_f32(v); +} + +//////////////////////////////// +//~ dan: Helper for initializing 4x4 matrices with 16 values +internal Mat4x4F32 +m4x4f32(F32 m00, F32 m01, F32 m02, F32 m03, + F32 m10, F32 m11, F32 m12, F32 m13, + F32 m20, F32 m21, F32 m22, F32 m23, + F32 m30, F32 m31, F32 m32, F32 m33) +{ + Mat4x4F32 result = { + {{m00, m01, m02, m03}, + {m10, m11, m12, m13}, + {m20, m21, m22, m23}, + {m30, m31, m32, m33}} + }; + return result; +} + +//////////////////////////////// + +//////////////////////////////// +//~ dan: Helpers + +internal R_GL_Window * +r_gl_window_from_handle(R_Handle handle) +{ + // Simple check: lowest bit is 1 for windows + if((handle.u64[0] & 1) == 1) + { + // Ensure the pointer part is non-NULL before dereferencing potentially + R_GL_Window* potential_window = (R_GL_Window *)(handle.u64[0] & ~0x3ull); + if (potential_window != 0 && potential_window->next != potential_window) // Check against self-referencing nil handle + { + return potential_window; + } + } + return &r_gl_window_nil; +} + +internal R_Handle +r_gl_handle_from_window(R_GL_Window *window) +{ + R_Handle result = {0}; + if (window != 0 && window != &r_gl_window_nil) + { + result.u64[0] = (U64)window | 0x1; + } + return result; +} + +internal R_GL_Tex2D * +r_gl_tex2d_from_handle(R_Handle handle) +{ + // Simple check: lowest two bits are 10 for textures + if((handle.u64[0] & 0x3) == 0x2) + { + R_GL_Tex2D* potential_tex = (R_GL_Tex2D *)(handle.u64[0] & ~0x3ull); + if (potential_tex != 0 && potential_tex->next != potential_tex) // Check against self-referencing nil handle + { + return potential_tex; + } + } + return &r_gl_tex2d_nil; +} + +internal R_Handle +r_gl_handle_from_tex2d(R_GL_Tex2D *texture) +{ + R_Handle result = {0}; + if (texture != 0 && texture != &r_gl_tex2d_nil) + { + result.u64[0] = (U64)texture | 0x2; + } + return result; +} + +internal R_GL_Buffer * +r_gl_buffer_from_handle(R_Handle handle) +{ + // Simple check: lowest two bits are 11 for buffers + if((handle.u64[0] & 0x3) == 0x3) + { + R_GL_Buffer* potential_buf = (R_GL_Buffer *)(handle.u64[0] & ~0x3ull); + if (potential_buf != 0 && potential_buf->next != potential_buf) // Check against self-referencing nil handle + { + return potential_buf; + } + } + return &r_gl_buffer_nil; +} + +internal R_Handle +r_gl_handle_from_buffer(R_GL_Buffer *buffer) +{ + R_Handle result = {0}; + if (buffer != 0 && buffer != &r_gl_buffer_nil) + { + result.u64[0] = (U64)buffer | 0x3; + } + return result; +} + +internal GLenum +r_gl_usage_from_resource_kind(R_ResourceKind kind) +{ + GLenum result = GL_STATIC_DRAW; + switch(kind) + { + case R_ResourceKind_Static: result = GL_STATIC_DRAW; break; + case R_ResourceKind_Dynamic: result = GL_DYNAMIC_DRAW; break; + case R_ResourceKind_Stream: result = GL_STREAM_DRAW; break; + default: break; // Should not happen + } + return result; +} + +internal GLenum +r_gl_format_from_tex2d_format(R_Tex2DFormat format) +{ + GLenum result = GL_RGBA; + switch(format) + { + case R_Tex2DFormat_R8: result = GL_RED; break; + case R_Tex2DFormat_RG8: result = GL_RG; break; + case R_Tex2DFormat_RGBA8: result = GL_RGBA; break; + case R_Tex2DFormat_BGRA8: result = GL_BGRA; break; + case R_Tex2DFormat_R16: result = GL_RED; break; // Note: type will be GL_UNSIGNED_SHORT or similar + case R_Tex2DFormat_RGBA16: result = GL_RGBA; break; // Note: type will be GL_UNSIGNED_SHORT or similar + case R_Tex2DFormat_R32: result = GL_RED; break; // Note: type will be GL_FLOAT + case R_Tex2DFormat_RG32: result = GL_RG; break; // Note: type will be GL_FLOAT + case R_Tex2DFormat_RGBA32: result = GL_RGBA; break; // Note: type will be GL_FLOAT + default: break; + } + return result; +} + +internal GLint +r_gl_internal_format_from_tex2d_format(R_Tex2DFormat format) +{ + GLint result = GL_RGBA8; + switch(format) + { + case R_Tex2DFormat_R8: result = GL_R8; break; + case R_Tex2DFormat_RG8: result = GL_RG8; break; + case R_Tex2DFormat_RGBA8: result = GL_RGBA8; break; + case R_Tex2DFormat_BGRA8: result = GL_RGBA8; break; + case R_Tex2DFormat_R16: result = GL_R16; break; // Use GL_R16UI or GL_R16I for integer formats if needed + case R_Tex2DFormat_RGBA16: result = GL_RGBA16; break; // Use GL_RGBA16UI or GL_RGBA16I for integer formats + case R_Tex2DFormat_R32: result = GL_R32F; break; + case R_Tex2DFormat_RG32: result = GL_RG32F; break; + case R_Tex2DFormat_RGBA32: result = GL_RGBA32F; break; + default: break; + } + return result; +} + +internal GLenum +r_gl_type_from_tex2d_format(R_Tex2DFormat format) +{ + GLenum result = GL_UNSIGNED_BYTE; + switch(format) + { + case R_Tex2DFormat_R8: + case R_Tex2DFormat_RG8: + case R_Tex2DFormat_RGBA8: + case R_Tex2DFormat_BGRA8: result = GL_UNSIGNED_BYTE; break; + case R_Tex2DFormat_R16: result = GL_UNSIGNED_SHORT; break; // Or GL_HALF_FLOAT if using FP16 + case R_Tex2DFormat_RGBA16: result = GL_UNSIGNED_SHORT; break; // Or GL_HALF_FLOAT + case R_Tex2DFormat_R32: + case R_Tex2DFormat_RG32: + case R_Tex2DFormat_RGBA32: result = GL_FLOAT; break; + default: break; + } + return result; +} + +internal GLint +r_gl_filter_from_sample_kind(R_Tex2DSampleKind kind) +{ + GLint result = GL_LINEAR; + switch(kind) + { + case R_Tex2DSampleKind_Nearest: result = GL_NEAREST; break; + case R_Tex2DSampleKind_Linear: result = GL_LINEAR; break; + default: break; + } + return result; +} + +//////////////////////////////// +//~ dan: Buffer Update Helpers + +internal void +r_gl_buffer_update_sub_data(R_Handle handle, U64 offset, U64 size, void *data) +{ + R_GL_Buffer *buffer = r_gl_buffer_from_handle(handle); + if (buffer == &r_gl_buffer_nil || buffer->buffer_id == 0 || size == 0) + { + return; + } + + Assert(buffer->kind == R_ResourceKind_Dynamic || buffer->kind == R_ResourceKind_Stream); + Assert(offset + size <= buffer->size); + Assert(buffer->target != 0); // Target should have been set on alloc + + GLenum target = buffer->target; // Use stored target + + glBindBuffer(target, buffer->buffer_id); + r_gl_check_error("glBindBuffer UpdateSubData"); + + glBufferSubData(target, (GLintptr)offset, (GLsizeiptr)size, data); + r_gl_check_error("glBufferSubData"); + + glBindBuffer(target, 0); +} + +internal void* +r_gl_buffer_map_range(R_Handle handle, U64 offset, U64 size, GLbitfield access_flags) +{ + R_GL_Buffer *buffer = r_gl_buffer_from_handle(handle); + if (buffer == &r_gl_buffer_nil || buffer->buffer_id == 0 || size == 0) + { + return NULL; + } + + Assert(buffer->kind == R_ResourceKind_Stream || buffer->kind == R_ResourceKind_Dynamic); // Map usually for Stream/Dynamic + Assert(offset + size <= buffer->size); + Assert(buffer->target != 0); + + GLenum target = buffer->target; // Use stored target + + glBindBuffer(target, buffer->buffer_id); + r_gl_check_error("glBindBuffer MapRange"); + + void *ptr = glMapBufferRange(target, (GLintptr)offset, (GLsizeiptr)size, access_flags); + r_gl_check_error("glMapBufferRange"); + + // Do not unbind here, caller must call unmap which implies binding. + // glBindBuffer(target, 0); + + return ptr; +} + +internal B32 +r_gl_buffer_unmap(R_Handle handle) +{ + R_GL_Buffer *buffer = r_gl_buffer_from_handle(handle); + if (buffer == &r_gl_buffer_nil || buffer->buffer_id == 0) + { + return 0; // Indicate failure or no-op + } + Assert(buffer->target != 0); + + GLenum target = buffer->target; // Use stored target + + glBindBuffer(target, buffer->buffer_id); + r_gl_check_error("glBindBuffer Unmap"); + + GLboolean result = glUnmapBuffer(target); + r_gl_check_error("glUnmapBuffer"); + + glBindBuffer(target, 0); + + // glUnmapBuffer returns GL_TRUE unless the data store contents have become corrupt. + return (result == GL_TRUE); +} + +//////////////////////////////// +//~ dan: Basic OpenGL Error Checking + +internal void +r_gl_check_error_line_file(const char *op, int line, char *file) +{ + if (!r_gl_state->has_valid_context) + { + fprintf(stderr, "Warning: OpenGL error check with no valid context for operation '%s' at %s:%d\n", + op, file, line); + return; + } + + GLenum err = glGetError(); + if(err != GL_NO_ERROR) + { + // Map error code to a readable string + const char* error_str = "Unknown Error"; + switch(err) + { + case GL_INVALID_ENUM: error_str = "GL_INVALID_ENUM"; break; + case GL_INVALID_VALUE: error_str = "GL_INVALID_VALUE"; break; + case GL_INVALID_OPERATION: error_str = "GL_INVALID_OPERATION"; break; + case GL_STACK_OVERFLOW: error_str = "GL_STACK_OVERFLOW"; break; + case GL_STACK_UNDERFLOW: error_str = "GL_STACK_UNDERFLOW"; break; + case GL_OUT_OF_MEMORY: error_str = "GL_OUT_OF_MEMORY"; break; + } + fprintf(stderr, "OpenGL Error 0x%x (%s) for operation '%s' at %s:%d\n", + err, error_str, op, file, line); + } +} +#define r_gl_check_error(op) r_gl_check_error_line_file(op, __LINE__, __FILE__) + +//////////////////////////////// +//~ dan: Shader Compilation Helpers + +internal GLuint +r_gl_compile_shader(GLenum type, const char *source, String8 name) +{ + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &source, NULL); + r_gl_check_error("glShaderSource"); + glCompileShader(shader); + r_gl_check_error("glCompileShader"); + + GLint success = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if(!success) + { + GLint log_length = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); + char *info_log = malloc(log_length); // Use temp arena if available + if (info_log) { + glGetShaderInfoLog(shader, log_length, NULL, info_log); + fprintf(stderr, "Shader Compilation Failure (%s) '%.*s':\n%s\n", + type == GL_VERTEX_SHADER ? "VS" : "FS", str8_varg(name), info_log); + free(info_log); + } else { + fprintf(stderr, "Shader Compilation Failure (%s) '%.*s': Could not allocate memory for log.\n", + type == GL_VERTEX_SHADER ? "VS" : "FS", str8_varg(name)); + } + glDeleteShader(shader); + return 0; + } + + return shader; +} + +// Updated to optionally take geometry shader source +internal GLuint +r_gl_create_program(const char *vs_source, const char *fs_source, const char *gs_source, String8 name, GLuint *out_vs, GLuint *out_fs, GLuint *out_gs, GLint *out_texture_location) +{ + GLuint vs = 0, fs = 0, gs = 0, program = 0; + GLint link_success = 0; + + // Print first few lines of vertex shader for debugging + fprintf(stderr, "Compiling shader '%.*s':\n", str8_varg(name)); + + vs = r_gl_compile_shader(GL_VERTEX_SHADER, vs_source, str8_lit("vertex")); + if (!vs) goto fail; + fs = r_gl_compile_shader(GL_FRAGMENT_SHADER, fs_source, str8_lit("fragment")); + if (!fs) goto fail; + if (gs_source) { + gs = r_gl_compile_shader(GL_GEOMETRY_SHADER, gs_source, str8_lit("geometry")); + if (!gs) goto fail; + } + + program = glCreateProgram(); + r_gl_check_error("glCreateProgram"); + glAttachShader(program, vs); + r_gl_check_error("glAttachShader VS"); + glAttachShader(program, fs); + r_gl_check_error("glAttachShader FS"); + if (gs) { + glAttachShader(program, gs); + r_gl_check_error("glAttachShader GS"); + } + glLinkProgram(program); + r_gl_check_error("glLinkProgram"); + + glGetProgramiv(program, GL_LINK_STATUS, &link_success); + if(!link_success) + { + GLint log_length = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length); + char *info_log = malloc(log_length); // Use temp arena if available + if (info_log) { + glGetProgramInfoLog(program, log_length, NULL, info_log); + fprintf(stderr, "Shader Linking Failure '%.*s':\n%s\n", str8_varg(name), info_log); + free(info_log); + } else { + fprintf(stderr, "Shader Linking Failure '%.*s': Could not allocate memory for log.\n", str8_varg(name)); + } + goto fail; // Use goto for cleanup + } + + // Validate the program to catch any issues + glValidateProgram(program); + GLint validate_status = 0; + glGetProgramiv(program, GL_VALIDATE_STATUS, &validate_status); + if (validate_status == GL_FALSE) { + GLint log_length = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length); + char *info_log = malloc(log_length); + if (info_log) { + glGetProgramInfoLog(program, log_length, NULL, info_log); + fprintf(stderr, "Shader Program Validation Failed '%.*s':\n%s\n", + str8_varg(name), info_log); + free(info_log); + // Program can still be used even if validation fails, we'll only issue a warning + } + } + + // Detach shaders after successful link (optional but good practice) + glDetachShader(program, vs); + glDetachShader(program, fs); + if(gs) glDetachShader(program, gs); + + // Store shader handles if requested, otherwise delete them + if(out_vs) *out_vs = vs; else glDeleteShader(vs); + if(out_fs) *out_fs = fs; else glDeleteShader(fs); + if(out_gs) *out_gs = gs; else if(gs) glDeleteShader(gs); + + // Get sampler uniform location (assuming name "main_t2d" for texture unit 0) + if (out_texture_location) { + *out_texture_location = glGetUniformLocation(program, "main_t2d"); + fprintf(stderr, "%.*s shader main_t2d location: %d\n", str8_varg(name), *out_texture_location); + } + + r_gl_check_error("Shader Program Creation Success"); + return program; + +fail: + fprintf(stderr, "ERROR: Failed to create shader program '%.*s'\n", str8_varg(name)); + if (vs) glDeleteShader(vs); + if (fs) glDeleteShader(fs); + if (gs) glDeleteShader(gs); + if (program) glDeleteProgram(program); + return 0; +} + +//////////////////////////////// +//~ dan: VAO Helpers (Added) + +// Creates a VAO suitable for drawing a fullscreen quad (no VBO needed) +internal GLuint +r_gl_vao_make_fullscreen_quad(void) +{ + GLuint vao_id; + glGenVertexArrays(1, &vao_id); + // Bind the VAO before checking for errors + glBindVertexArray(vao_id); + r_gl_check_error("Fullscreen VAO Creation"); + glBindVertexArray(0); // Unbind to be safe + return vao_id; +} + +//////////////////////////////// +//~ dan: One-Time Initialization Helpers + +// Initialize GLEW only - called with temp context +internal void +r_gl_init_glew_if_needed(void) +{ + if(r_gl_state->glew_initialized) + { + return; + } + + // Verify we have a valid OpenGL context before trying to initialize GLEW + GLXContext current_ctx = glXGetCurrentContext(); + if (!current_ctx) + { + fprintf(stderr, "ERROR: Attempting to initialize GLEW without a valid OpenGL context!\n"); + return; + } + + fprintf(stderr, "Initializing GLEW with current GLX context: %p\n", (void*)current_ctx); + + // Initialize GLEW + glewExperimental = GL_TRUE; // Enable GLEW experimental features for modern contexts + GLenum err = glewInit(); + + // GLEW initialization can sometimes report GL_INVALID_ENUM but still work correctly + // Clear any error that might have been generated by GLEW + glGetError(); + + if(err != GLEW_OK) + { + fprintf(stderr, "GLEW initialization error: %s\n", glewGetErrorString(err)); + return; // Return without setting glew_initialized if it completely failed + } + + // Mark GLEW as initialized + r_gl_state->glew_initialized = 1; + + // Print OpenGL version information for debugging + const GLubyte* vendor = glGetString(GL_VENDOR); + const GLubyte* renderer = glGetString(GL_RENDERER); + const GLubyte* version = glGetString(GL_VERSION); + const GLubyte* glsl_version = glGetString(GL_SHADING_LANGUAGE_VERSION); + + fprintf(stderr, "OpenGL Vendor: %s\n", vendor ? (const char*)vendor : "unknown"); + fprintf(stderr, "OpenGL Renderer: %s\n", renderer ? (const char*)renderer : "unknown"); + fprintf(stderr, "OpenGL Version: %s\n", version ? (const char*)version : "unknown"); + fprintf(stderr, "GLSL Version: %s\n", glsl_version ? (const char*)glsl_version : "unknown"); + + // Now that GLEW is initialized and we have a valid context, mark the context as valid + // This will trigger processing of any deferred textures + fprintf(stderr, "Setting has_valid_context = 1\n"); + r_gl_set_has_valid_context(1); +} + +// Create global GL resources - called with final context +internal void +r_gl_create_global_resources(void) +{ + if(r_gl_state->global_resources_initialized) + { + return; // Already initialized + } + + log_infof("Creating global OpenGL resources..."); + + // Ensure GLEW is initialized first + r_gl_init_glew_if_needed(); + if (!r_gl_state->has_valid_context) { + log_infof("Skipping global resource creation as there is no valid GL context yet."); + return; + } + + // Make sure we have a valid context before proceeding + if(!r_gl_state->has_valid_context) + { + fprintf(stderr, "Error: Attempted to create OpenGL resources without a valid context.\n"); + return; + } + + // Make sure GLEW is initialized + if(!r_gl_state->glew_initialized) + { + fprintf(stderr, "Error: Attempted to create OpenGL resources before GLEW initialization.\n"); + return; + } + + fprintf(stderr, "Creating OpenGL global resources...\n"); + + // Create fullscreen quad VAO + r_gl_state->fullscreen_vao = r_gl_vao_make_fullscreen_quad(); + + // Create rect VAO + glGenVertexArrays(1, &r_gl_state->rect_vao); + glBindVertexArray(r_gl_state->rect_vao); +r_gl_check_error("UI Bind VAO"); + + // Create shared instance VBO (moved earlier) + glGenBuffers(1, &r_gl_state->instance_vbo); + glBindBuffer(GL_ARRAY_BUFFER, r_gl_state->instance_vbo); +r_gl_check_error("UI Bind Instance VBO"); + // Allocate some initial size, it will be resized later if needed. Use GL_STREAM_DRAW for frequent updates. + glBufferData(GL_ARRAY_BUFFER, MB(1), NULL, GL_STREAM_DRAW); // Example initial size: 1MB + r_gl_check_error("Instance VBO Creation"); + + // Bind the instance VBO before defining attributes that use it + glBindBuffer(GL_ARRAY_BUFFER, r_gl_state->instance_vbo); +r_gl_check_error("UI Bind Instance VBO"); + r_gl_check_error("Rect VAO Bind Instance VBO"); + + // Setup instance attributes for the Rect shader (8 vec4s) + // Assuming R_Rect2DInst packs these 8 vec4s contiguously. + // Stride is sizeof(R_Rect2DInst), which we assume is 8 * 4 * sizeof(float) = 128 bytes. + size_t rect_instance_stride = 8 * 4 * sizeof(float); + for (int i = 0; i < 8; i++) { + glEnableVertexAttribArray(i); + // Each attribute is a vec4 (4 floats) + // The offset is i * sizeof(vec4) + glVertexAttribPointer(i, 4, GL_FLOAT, GL_FALSE, (GLsizei)rect_instance_stride, (void*)(i * 4 * sizeof(float))); + glVertexAttribDivisor(i, 1); // Advance this attribute once per instance + } + r_gl_check_error("Rect VAO Instance Attributes"); + + // Clean up bindings + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); // Unbind VAO as well + + // Create mesh VAO for 3D drawing + glGenVertexArrays(1, &r_gl_state->mesh_vao); + + glBindVertexArray(r_gl_state->mesh_vao); + { + // Per-vertex attributes (binding index 0 implicitly uses currently bound GL_ARRAY_BUFFER) + glEnableVertexAttribArray(0); // Position + glEnableVertexAttribArray(1); // Normal + glEnableVertexAttribArray(2); // TexCoord + glEnableVertexAttribArray(3); // Color + // These will have divisor 0 by default + + // Per-instance attributes (binding index 1 implicitly uses currently bound GL_ARRAY_BUFFER) + // Instance Transform Matrix (mat4 = 4 x vec4) + glEnableVertexAttribArray(4); glVertexAttribDivisor(4, 1); + glEnableVertexAttribArray(5); glVertexAttribDivisor(5, 1); + glEnableVertexAttribArray(6); glVertexAttribDivisor(6, 1); + glEnableVertexAttribArray(7); glVertexAttribDivisor(7, 1); + } + glBindVertexArray(0); // Unbind VAO + r_gl_check_error("Mesh VAO Creation"); + + // Create UBO for each uniform block type + // Note: UBO size must be a multiple of GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT + // For simplicity, we use sizes directly from the struct definitions. + GLuint ubo_sizes[R_GL_UniformTypeKind_COUNT] = { + r_gl_g_uniform_type_kind_size_table[R_GL_UniformTypeKind_Rect], // R_GL_UniformTypeKind_Rect + r_gl_g_uniform_type_kind_size_table[R_GL_UniformTypeKind_Blur], // R_GL_UniformTypeKind_Blur + r_gl_g_uniform_type_kind_size_table[R_GL_UniformTypeKind_Mesh], // R_GL_UniformTypeKind_Mesh + }; + + // Store sizes for later use + for (int i = 0; i < R_GL_UniformTypeKind_COUNT; i++) { + r_gl_state->uniform_type_kind_sizes[i] = ubo_sizes[i]; + } + + glGenBuffers(R_GL_UniformTypeKind_COUNT, r_gl_state->uniform_type_kind_buffers); + for (int i = 0; i < R_GL_UniformTypeKind_COUNT; i++) { + glBindBuffer(GL_UNIFORM_BUFFER, r_gl_state->uniform_type_kind_buffers[i]); + glBufferData(GL_UNIFORM_BUFFER, ubo_sizes[i], NULL, GL_DYNAMIC_DRAW); + } + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + // Create samplers + glGenSamplers(R_Tex2DSampleKind_COUNT, r_gl_state->samplers); + for (R_Tex2DSampleKind kind = 0; kind < R_Tex2DSampleKind_COUNT; kind++) { + GLint filter = r_gl_filter_from_sample_kind(kind); + glSamplerParameteri(r_gl_state->samplers[kind], GL_TEXTURE_MIN_FILTER, filter); + glSamplerParameteri(r_gl_state->samplers[kind], GL_TEXTURE_MAG_FILTER, filter); + // Use consistent wrapping mode: CLAMP_TO_EDGE for all textures + // This ensures consistent behavior between D3D11 and OpenGL + glSamplerParameteri(r_gl_state->samplers[kind], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri(r_gl_state->samplers[kind], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + // Create backup texture (1x1 white pixel) + { + // Create backup texture (1x1 white pixel) + R_GL_Tex2D *tex = r_gl_state->first_free_tex2d; + if(!tex) + { + tex = push_array(r_gl_state->arena, R_GL_Tex2D, 1); + } + else + { + SLLStackPop(r_gl_state->first_free_tex2d); + } + // Minimal reset needed after popping from free list + MemoryZeroStruct(tex); // Ensure struct is clean + tex->generation = (tex == r_gl_state->first_free_tex2d) ? 1 : tex->generation + 1; // Increment gen safely + + tex->kind = R_ResourceKind_Static; + tex->size = v2s32(1, 1); + tex->format = R_Tex2DFormat_RGBA8; + + glGenTextures(1, &tex->texture_id); + r_gl_check_error("glGenTextures Backup"); // Added context + glBindTexture(GL_TEXTURE_2D, tex->texture_id); + r_gl_check_error("glBindTexture Backup"); + U8 white_pixel[4] = {255, 255, 255, 255}; + // Set pixel store alignment for the 1x1 texture + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // RGBA8 is 4 bytes + r_gl_check_error("glPixelStorei Backup"); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, white_pixel); + r_gl_check_error("glTexImage2D Backup"); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Added wrap mode + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Added wrap mode + r_gl_check_error("glTexParameteri Backup"); + glBindTexture(GL_TEXTURE_2D, 0); + + r_gl_state->backup_texture = r_gl_handle_from_tex2d(tex); + } + + // NOTE: Rect shader + { + // Use global variables for shader source + const char *vs_source = (const char *)r_gl_g_vshad_kind_source_ptr_table[R_GL_VShadKind_Rect]->str; + const char *fs_source = (const char *)r_gl_g_pshad_kind_source_ptr_table[R_GL_PShadKind_Rect]->str; + + GLint texture_location = 0; + r_gl_state->rect_shader.program_id = r_gl_create_program( + vs_source, fs_source, NULL, + str8_lit("rect_shader"), + &r_gl_state->rect_shader.vertex_shader, + &r_gl_state->rect_shader.fragment_shader, + NULL, + &texture_location); // Pass location pointer + + // If program creation failed, we'll get a zero ID + if (r_gl_state->rect_shader.program_id == 0) { + fprintf(stderr, "ERROR: Failed to create rect shader program\n"); + } else { + fprintf(stderr, "Successfully created rect shader program: %u\n", r_gl_state->rect_shader.program_id); + r_gl_state->rect_shader.main_texture_uniform_location = texture_location; // Store the location + + // Bind the uniform block to its index point for the rect shader + GLuint block_index = glGetUniformBlockIndex(r_gl_state->rect_shader.program_id, "R_GL_Uniforms_Rect"); + if (block_index != GL_INVALID_INDEX) { + glUniformBlockBinding(r_gl_state->rect_shader.program_id, block_index, R_GL_UNIFORM_BINDING_POINT_RECT); + } else { + fprintf(stderr, "WARNING: Could not find uniform block 'R_GL_Uniforms_Rect' in shader\n"); + } + // Removed manual glGetUniformLocation for "main_texture" + } + } + + // NOTE: Blur shader + { + // Use global variables for shader source + const char *vs_source = (const char *)r_gl_g_vshad_kind_source_ptr_table[R_GL_VShadKind_Blur]->str; + const char *fs_source = (const char *)r_gl_g_pshad_kind_source_ptr_table[R_GL_PShadKind_Blur]->str; + + GLint texture_location = 0; + r_gl_state->blur_shader.program_id = r_gl_create_program( + vs_source, fs_source, NULL, + str8_lit("blur_shader"), + &r_gl_state->blur_shader.vertex_shader, + &r_gl_state->blur_shader.fragment_shader, + NULL, + &texture_location); // Pass location pointer + + // Successfully created blur shader? + if (r_gl_state->blur_shader.program_id == 0) { + fprintf(stderr, "ERROR: Failed to create blur shader program\n"); + } else { + fprintf(stderr, "Successfully created blur shader program: %u\n", r_gl_state->blur_shader.program_id); + r_gl_state->blur_shader.main_texture_uniform_location = texture_location; // Store the location + + // Bind the uniform block to its index point + GLuint block_index = glGetUniformBlockIndex(r_gl_state->blur_shader.program_id, "BlurUniforms"); + if (block_index != GL_INVALID_INDEX) { + glUniformBlockBinding(r_gl_state->blur_shader.program_id, block_index, R_GL_UNIFORM_BINDING_POINT_BLUR); + fprintf(stderr, "Blur UBO block bound to binding point %d\n", R_GL_UNIFORM_BINDING_POINT_BLUR); + } else { + fprintf(stderr, "WARNING: Could not find uniform block 'BlurUniforms' in shader\n"); + } + + // Get and store the main texture location + // r_gl_state->blur_shader.main_texture_uniform_location = texture_location; // Already stored above + } + } + + // NOTE: Mesh shader + { + // Use global variables for shader source + const char *vs_source = (const char *)r_gl_g_vshad_kind_source_ptr_table[R_GL_VShadKind_Mesh]->str; + const char *fs_source = (const char *)r_gl_g_pshad_kind_source_ptr_table[R_GL_PShadKind_Mesh]->str; + + GLint texture_location = 0; + r_gl_state->mesh_shader.program_id = r_gl_create_program( + vs_source, fs_source, NULL, + str8_lit("mesh_shader"), + &r_gl_state->mesh_shader.vertex_shader, + &r_gl_state->mesh_shader.fragment_shader, + NULL, + &texture_location); // Pass location pointer + + if (r_gl_state->mesh_shader.program_id == 0) { + fprintf(stderr, "ERROR: Failed to create mesh shader program\n"); + } else { + fprintf(stderr, "Successfully created mesh shader program: %u\n", r_gl_state->mesh_shader.program_id); + r_gl_state->mesh_shader.main_texture_uniform_location = texture_location; // Store the location + + // Bind the uniform block + GLuint block_index = glGetUniformBlockIndex(r_gl_state->mesh_shader.program_id, "R_GL_Uniforms_Mesh"); + if (block_index != GL_INVALID_INDEX) { + glUniformBlockBinding(r_gl_state->mesh_shader.program_id, block_index, R_GL_UNIFORM_BINDING_POINT_MESH); + } else { + fprintf(stderr, "WARNING: Could not find uniform block 'R_GL_Uniforms_Mesh' in shader\n"); + } + + // Get and store the main texture location + // r_gl_state->mesh_shader.main_texture_uniform_location = texture_location; // Already stored above + // Removed manual glGetUniformLocation for "main_texture" + } + } + + // NOTE: Geo3D Composite shader + { + // Use global variable for shader source (assuming VS is simple and FS is main source) + // NOTE: Assuming a simple pass-through VS or defining r_gl_g_geo3dcomposite_shader_vs_src + const char *vs_source = + "#version 330 core\n" + "out vec2 TexCoord;\n" + "void main()\n" + "{\n" + " vec2 pos = vec2( (gl_VertexID & 1) * 2.0 - 1.0, (gl_VertexID & 2) - 1.0 ); \n" + " gl_Position = vec4(pos, 0.0, 1.0);\n" + " TexCoord = pos * 0.5 + 0.5;\n" + " TexCoord.y = 1.0 - TexCoord.y;\n" + "}\n"; // Using inline VS for now as no global was defined + const char *fs_source = (const char *)r_gl_g_pshad_kind_source_ptr_table[R_GL_PShadKind_Geo3DComposite]->str; + + GLint texture_location = 0; // Get the location via the helper + r_gl_state->geo3d_composite_shader.program_id = r_gl_create_program( + vs_source, fs_source, NULL, + str8_lit("geo3d_composite_shader"), + &r_gl_state->geo3d_composite_shader.vertex_shader, + &r_gl_state->geo3d_composite_shader.fragment_shader, + NULL, + &texture_location); // Pass pointer to get "main_t2d" location + + if (r_gl_state->geo3d_composite_shader.program_id == 0) { + fprintf(stderr, "ERROR: Failed to create geo3d composite shader program\n"); + } else { + fprintf(stderr, "Successfully created geo3d composite shader program: %u\n", r_gl_state->geo3d_composite_shader.program_id); + r_gl_state->geo3d_composite_shader.main_texture_uniform_location = texture_location; // Store the location + + // Removed manual glGetUniformLocation and glUniform1i calls here + // Shader uniforms for composite are now set in the render pass submit logic + } + } + + // NOTE: Finalize shader (simple blit to screen) + { + // Use global variables for shader source + const char *vs_source = (const char *)r_gl_g_vshad_kind_source_ptr_table[R_GL_VShadKind_FullscreenQuad]->str; + const char *fs_source = (const char *)r_gl_g_pshad_kind_source_ptr_table[R_GL_PShadKind_Finalize]->str; + + GLint texture_location = 0; + r_gl_state->finalize_shader.program_id = r_gl_create_program( + vs_source, fs_source, NULL, + str8_lit("finalize_shader"), + &r_gl_state->finalize_shader.vertex_shader, + &r_gl_state->finalize_shader.fragment_shader, + NULL, + &texture_location); // Pass location pointer + + if (r_gl_state->finalize_shader.program_id == 0) { + fprintf(stderr, "ERROR: Failed to create finalize shader program\n"); + } else { + fprintf(stderr, "Successfully created finalize shader program: %u\n", r_gl_state->finalize_shader.program_id); + + // Get and store the main texture location + r_gl_state->finalize_shader.main_texture_uniform_location = texture_location; // Store the location + // Removed manual glGetUniformLocation for "main_texture" + } + } + + fprintf(stderr, "OpenGL resources created successfully.\n"); + + // Mark initialization as complete + r_gl_state->global_resources_initialized = 1; + + //- Create uniform buffer objects (UBOs) + { + // Define UBO sizes for each uniform type + r_gl_state->uniform_type_kind_sizes[R_GL_UniformTypeKind_Rect] = r_gl_g_uniform_type_kind_size_table[R_GL_UniformTypeKind_Rect]; + r_gl_state->uniform_type_kind_sizes[R_GL_UniformTypeKind_Blur] = r_gl_g_uniform_type_kind_size_table[R_GL_UniformTypeKind_Blur]; + r_gl_state->uniform_type_kind_sizes[R_GL_UniformTypeKind_Mesh] = r_gl_g_uniform_type_kind_size_table[R_GL_UniformTypeKind_Mesh]; + + // Create and initialize UBOs for each uniform type + for (R_GL_UniformTypeKind kind = 0; kind < R_GL_UniformTypeKind_COUNT; kind++) + { + GLuint buffer = 0; + glGenBuffers(1, &buffer); + glBindBuffer(GL_UNIFORM_BUFFER, buffer); + + // Calculate size with proper alignment (multiple of 16 bytes) + GLsizeiptr size = r_gl_state->uniform_type_kind_sizes[kind]; + size = (size + 15) & ~15; // Round up to multiple of 16 + + // Initialize the buffer with NULL data (allocate space only) + glBufferData(GL_UNIFORM_BUFFER, size, NULL, GL_DYNAMIC_DRAW); + + // Bind to pre-defined binding point + glBindBufferBase(GL_UNIFORM_BUFFER, kind, buffer); + + // Store the buffer handle + r_gl_state->uniform_type_kind_buffers[kind] = buffer; + + // Unbind the buffer + glBindBuffer(GL_UNIFORM_BUFFER, 0); + } + } + + r_gl_check_error("Post UBO Creation"); // Check errors after UBO setup + + log_infof("Global OpenGL resources created successfully."); + r_gl_state->global_resources_initialized = 1; + + // ADDED: Process deferred textures *after* global resources are ready + r_gl_process_deferred_tex2d_queue(); +} + +// This function initializes GLEW and creates global OpenGL resources. +// It MUST be called after *some* GL context is made current for the first time. +// It's designed to be called idempotently. +internal void +r_gl_init_extensions_if_needed(void) +{ + // Call the split initialization functions for backward compatibility + r_gl_init_glew_if_needed(); + r_gl_create_global_resources(); +} + +//////////////////////////////// +//~ dan: Backend Hooks Implementation + +r_hook void +r_init(CmdLine *cmdln) +{ + ProfBeginFunction(); + Arena *arena = arena_alloc(GB(1)); // Consider arena params + r_gl_state = push_array(arena, R_GL_State, 1); + r_gl_state->arena = arena; + r_gl_state->device_rw_mutex = os_rw_mutex_alloc(); + r_gl_state->global_resources_initialized = 0; // Initialize the flag + r_gl_state->glew_initialized = 0; // Initialize GLEW flag + r_gl_state->has_valid_context = 0; // No valid context yet + + // Initialize deferred texture allocation + r_gl_state->deferred_tex2d_arena = arena_alloc(); + r_gl_state->first_deferred_tex2d = NULL; + r_gl_state->last_deferred_tex2d = NULL; + + // Store UBO sizes (does not require GL context) + r_gl_state->uniform_type_kind_sizes[R_GL_UniformTypeKind_Rect] = r_gl_g_uniform_type_kind_size_table[R_GL_UniformTypeKind_Rect]; + r_gl_state->uniform_type_kind_sizes[R_GL_UniformTypeKind_Blur] = r_gl_g_uniform_type_kind_size_table[R_GL_UniformTypeKind_Blur]; + r_gl_state->uniform_type_kind_sizes[R_GL_UniformTypeKind_Mesh] = r_gl_g_uniform_type_kind_size_table[R_GL_UniformTypeKind_Mesh]; + + // NOTE: All gl* calls are removed from here and moved to r_window_equip / r_gl_create_global_resources + ProfEnd(); +} + +// Add this function after r_init but before other functions +internal void +r_gl_set_has_valid_context(B32 has_context) +{ + log_infof("Setting has_valid_context = %d", has_context); + r_gl_state->has_valid_context = has_context; + // Process deferred textures *only* if the context is now valid + if(has_context) + { + // REMOVED: r_gl_process_deferred_tex2d_queue(); + } +} + +// New function to process the deferred texture allocation queue +internal void +r_gl_process_deferred_tex2d_queue(void) +{ + // Add early return if we don't have a valid context or GLEW isn't initialized + if(!r_gl_state->has_valid_context || !r_gl_state->glew_initialized) + { + fprintf(stderr, "Cannot process deferred textures: has_valid_context=%d, glew_initialized=%d\n", + r_gl_state->has_valid_context, r_gl_state->glew_initialized); + return; + } + + // Check if queue is empty + if(!r_gl_state->first_deferred_tex2d) + { + // No deferred textures to process + return; + } + + fprintf(stderr, "Processing deferred texture queue...\n"); + + // Continue with normal processing now that we have a valid context + for(R_GL_DeferredTex2D *n = r_gl_state->first_deferred_tex2d, *next = 0; + n != 0; + n = next) + { + next = n->next; + + // Create the texture now that we have a valid context + R_GL_Tex2D *tex = n->texture; + tex->kind = n->kind; + tex->size = n->size; + tex->format = n->format; + + // Generate texture - clear any previous errors first + glGetError(); // Clear previous errors + + glGenTextures(1, &tex->texture_id); + GLenum err = glGetError(); + if(err != GL_NO_ERROR) + { + fprintf(stderr, "Error in deferred texture creation (glGenTextures): 0x%x\n", err); + continue; // Skip this texture and move to the next + } + + glBindTexture(GL_TEXTURE_2D, tex->texture_id); + err = glGetError(); + if(err != GL_NO_ERROR) + { + fprintf(stderr, "Error in deferred texture creation (glBindTexture): 0x%x\n", err); + continue; + } + + // Setup texture based on format + GLint internal_format = r_gl_internal_format_from_tex2d_format(n->format); + GLenum format = r_gl_format_from_tex2d_format(n->format); + GLenum type = r_gl_type_from_tex2d_format(n->format); + + // Upload texture data + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, n->size.x, n->size.y, 0, format, type, n->data); + err = glGetError(); + if(err != GL_NO_ERROR) + { + fprintf(stderr, "Error in deferred texture creation (glTexImage2D): 0x%x\n", err); + fprintf(stderr, " format=%d, internal_format=%d, type=%d, size=%dx%d\n", + format, internal_format, type, n->size.x, n->size.y); + continue; + } + + // Set default parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + err = glGetError(); + if(err != GL_NO_ERROR) + { + fprintf(stderr, "Error in deferred texture creation (glTexParameteri): 0x%x\n", err); + } + + // Free our copy of the data if we allocated one + if(n->data_size > 0 && n->data != NULL) + { + // arena_pop(r_gl_state->deferred_tex2d_arena, n->data_size); // INCORRECT: Arena pop is LIFO, loop is FIFO. + } + } + + // Clear the queue + fprintf(stderr, "Deferred texture queue processed\n"); + r_gl_state->first_deferred_tex2d = NULL; + r_gl_state->last_deferred_tex2d = NULL; + + // Clear the entire arena now that all data has been used + if(r_gl_state->deferred_tex2d_arena) + { + arena_clear(r_gl_state->deferred_tex2d_arena); + } +} + +r_hook R_Handle +r_window_equip(OS_Handle window) +{ + // This function equips subsequent windows or the first window *after* context creation. + R_GL_Window *gl_window = 0; + OS_LNX_Window *lnx_window = (OS_LNX_Window *)window.u64[0]; + + fprintf(stderr, "r_window_equip: OS window handle %p, X11 window %lu\n", + (void*)lnx_window, lnx_window ? lnx_window->window : 0); + + OS_MutexScope(r_gl_state->device_rw_mutex) + { + if(r_gl_state->first_free_window) + { + gl_window = r_gl_state->first_free_window; + SLLStackPop(r_gl_state->first_free_window); + MemoryZeroStruct(gl_window); // Reset struct for reuse + } + else + { + gl_window = push_array(r_gl_state->arena, R_GL_Window, 1); + } + + // Store OS_LNX_Window pointer as generation to make context current later + gl_window->generation = (U64)lnx_window; + gl_window->last_resolution = v2s32(0, 0); // Force resize on first frame + + // Store the display pointer in r_gl_state for later use + r_gl_state->display = os_lnx_gfx_state->display; + + // Print OpenGL info + const GLubyte* renderer = glGetString(GL_RENDERER); + const GLubyte* version = glGetString(GL_VERSION); + fprintf(stderr, "OpenGL Renderer: %s\n", renderer ? (const char*)renderer: "unknown"); + fprintf(stderr, "OpenGL Version: %s\n", version ? (const char*)version : "unknown"); + } + + R_Handle result = r_gl_handle_from_window(gl_window); + return result; +} + +// Helper function to delete window FBO resources +internal void +r_gl_delete_window_fbo_resources(R_GL_Window *gl_window) +{ + // Delete FBOs + if (gl_window->stage_fbo) { glDeleteFramebuffers(1, &gl_window->stage_fbo); gl_window->stage_fbo = 0; } + if (gl_window->stage_scratch_fbo) { glDeleteFramebuffers(1, &gl_window->stage_scratch_fbo); gl_window->stage_scratch_fbo = 0; } + if (gl_window->geo3d_fbo) { glDeleteFramebuffers(1, &gl_window->geo3d_fbo); gl_window->geo3d_fbo = 0; } + r_gl_check_error("FBO Deletion"); + + // Delete Textures used as attachments + if (gl_window->stage_color_texture) { glDeleteTextures(1, &gl_window->stage_color_texture); gl_window->stage_color_texture = 0; } + if (gl_window->stage_scratch_color_texture) { glDeleteTextures(1, &gl_window->stage_scratch_color_texture); gl_window->stage_scratch_color_texture = 0; } + if (gl_window->geo3d_color_texture) { glDeleteTextures(1, &gl_window->geo3d_color_texture); gl_window->geo3d_color_texture = 0; } + if (gl_window->geo3d_depth_texture) { glDeleteTextures(1, &gl_window->geo3d_depth_texture); gl_window->geo3d_depth_texture = 0; } + r_gl_check_error("FBO Texture Deletion"); + + // Reset resolution to force recreation + gl_window->last_resolution = v2s32(0, 0); +} + +// Implementation of the make context current function +internal void +r_gl_make_context_current(R_GL_Window *window) +{ + if (window != NULL && window != &r_gl_window_nil) { + OS_LNX_Window *os_window = (OS_LNX_Window *)window->generation; + if (os_window && os_window->gl_context) { + Display *display = os_lnx_gfx_state->display; + + // Check if this context is already current + GLXContext current = glXGetCurrentContext(); + Window current_drawable = glXGetCurrentDrawable(); + + if (current != os_window->gl_context || current_drawable != os_window->window) { + fprintf(stderr, "Making context current: display=%p, window=%lu, context=%p\n", + display, os_window->window, os_window->gl_context); + + if (!glXMakeCurrent(display, os_window->window, os_window->gl_context)) { + fprintf(stderr, "ERROR: Failed to make context current!\n"); + + // Try to diagnose the error + if (!display) { + fprintf(stderr, " - Display is NULL\n"); + } + if (os_window->window == 0) { + fprintf(stderr, " - Window is 0 (None)\n"); + } + if (!os_window->gl_context) { + fprintf(stderr, " - GL Context is NULL\n"); + } + } else { + // Verify the context was made current + GLXContext new_current = glXGetCurrentContext(); + Window new_drawable = glXGetCurrentDrawable(); + + if (new_current == os_window->gl_context && new_drawable == os_window->window) { + fprintf(stderr, "Successfully made context current\n"); + + // Check if we can make GL calls + GLint max_texture_size = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); + GLenum err = glGetError(); + + if (err != GL_NO_ERROR || max_texture_size == 0) { + fprintf(stderr, "WARNING: Context made current but GL calls failing (err=0x%x, max_tex_size=%d)\n", + err, max_texture_size); + } + } else { + fprintf(stderr, "ERROR: Context not current after glXMakeCurrent! current=%p, drawable=%lu\n", + new_current, new_drawable); + } + } + } + } else { + fprintf(stderr, "ERROR: Invalid OS window or GL context in r_gl_make_context_current\n"); + if (!os_window) { + fprintf(stderr, " - OS window is NULL (window->generation=%lu)\n", window->generation); + } else if (!os_window->gl_context) { + fprintf(stderr, " - gl_context is NULL\n"); + } + } + } else { + fprintf(stderr, "ERROR: Invalid R_GL_Window in r_gl_make_context_current\n"); + } +} + +r_hook void +r_window_unequip(OS_Handle window, R_Handle window_equip) +{ + R_GL_Window *gl_window = r_gl_window_from_handle(window_equip); + if(gl_window != &r_gl_window_nil) + { + // Check if we're unequipping the currently active window + if (glXGetCurrentContext() == ((OS_LNX_Window *)gl_window->generation)->gl_context) { + r_gl_make_context_current(NULL); // Switch to another valid context + } + + r_gl_delete_window_fbo_resources(gl_window); + SLLStackPush(r_gl_state->first_free_window, gl_window); + } +} + +r_hook R_Handle +r_tex2d_alloc(R_ResourceKind kind, Vec2S32 size, R_Tex2DFormat format, void *data) +{ + ProfBeginFunction(); + R_Handle result = {0}; + + // Check if we need to defer allocation due to no OpenGL context + if(!r_gl_state->has_valid_context) + { + // Allocate a texture object that will be populated later + R_GL_Tex2D *texture = r_gl_state->first_free_tex2d; + if(texture) + { + SLLStackPop(r_gl_state->first_free_tex2d); + } + else + { + texture = push_array(r_gl_state->arena, R_GL_Tex2D, 1); + } + texture->generation += 1; + + // Create the deferred allocation entry + if(!r_gl_state->deferred_tex2d_arena) + { + r_gl_state->deferred_tex2d_arena = arena_alloc(); + } + + R_GL_DeferredTex2D *deferred = push_array(r_gl_state->deferred_tex2d_arena, R_GL_DeferredTex2D, 1); + deferred->kind = kind; + deferred->size = size; + deferred->format = format; + deferred->texture = texture; + + // Copy the data if provided + if(data) + { + size_t data_size = size.x * size.y * r_tex2d_format_bytes_per_pixel_table[format]; + void *data_copy = push_array(r_gl_state->deferred_tex2d_arena, U8, data_size); + MemoryCopy(data_copy, data, data_size); + deferred->data = data_copy; + deferred->data_size = data_size; + } + + // Add to deferred list + SLLQueuePush(r_gl_state->first_deferred_tex2d, r_gl_state->last_deferred_tex2d, deferred); + + // Return handle that will be valid when processed later + result = r_gl_handle_from_tex2d(texture); + + fprintf(stderr, "Warning: r_tex2d_alloc called with no active GL context. Deferring creation.\n"); + + ProfEnd(); + return result; + } + + // Original implementation for when we have a valid context + // Allocate the texture object + R_GL_Tex2D *texture = r_gl_state->first_free_tex2d; + if(texture) + { + SLLStackPop(r_gl_state->first_free_tex2d); + } + else + { + texture = push_array(r_gl_state->arena, R_GL_Tex2D, 1); + } + texture->generation += 1; + + // Create the OpenGL texture + glGenTextures(1, &texture->texture_id); + r_gl_check_error("r_tex2d_alloc - glGenTextures"); + + glBindTexture(GL_TEXTURE_2D, texture->texture_id); +r_gl_check_error("UI Bind Texture"); + r_gl_check_error("r_tex2d_alloc - glBindTexture"); + + // Get format information + GLenum gl_format = r_gl_format_from_tex2d_format(format); + GLint gl_internal_format = r_gl_internal_format_from_tex2d_format(format); + GLenum gl_type = r_gl_type_from_tex2d_format(format); + + // Configure the texture + glTexImage2D(GL_TEXTURE_2D, 0, gl_internal_format, + size.x, size.y, + 0, gl_format, gl_type, data); + r_gl_check_error("r_tex2d_alloc - glTexImage2D"); + + // Set filtering parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + r_gl_check_error("r_tex2d_alloc - glTexParameteri"); + + glBindTexture(GL_TEXTURE_2D, 0); + + // Store metadata in the texture object + texture->kind = kind; + texture->size = size; + texture->format = format; + + result = r_gl_handle_from_tex2d(texture); + ProfEnd(); + return result; +} + +r_hook void +r_tex2d_release(R_Handle handle) +{ + ProfBeginFunction(); + OS_MutexScopeW(r_gl_state->device_rw_mutex) +{ + R_GL_Tex2D *texture = r_gl_tex2d_from_handle(handle); + SLLStackPush(r_gl_state->first_to_free_tex2d, texture); + } + ProfEnd(); +} + +r_hook R_Handle +r_buffer_alloc(R_ResourceKind kind, U64 size, void *data) +{ + ProfBeginFunction(); + + // NEW: Check if we have a valid OpenGL context + GLXContext current_ctx = glXGetCurrentContext(); + if (!current_ctx) { + // No valid context - can't create buffers yet + fprintf(stderr, "Warning: r_buffer_alloc called with no active GL context. Deferring creation.\n"); + R_Handle result = {0}; + return result; + } + + //- dan: allocate buffer structure + R_GL_Buffer *buffer = 0; + OS_MutexScopeW(r_gl_state->device_rw_mutex) + { + buffer = r_gl_state->first_free_buffer; + if(buffer == 0) + { + buffer = push_array(r_gl_state->arena, R_GL_Buffer, 1); + } + else + { + U64 gen = buffer->generation; + SLLStackPop(r_gl_state->first_free_buffer); + MemoryZeroStruct(buffer); + buffer->generation = gen; + } + buffer->generation += 1; + } + + //- dan: verify static buffer has data + if(kind == R_ResourceKind_Static) + { + Assert(data != 0 && "static buffer must have initial data provided"); + } + + //- dan: get buffer usage from resource kind + GLenum usage = r_gl_usage_from_resource_kind(kind); + + //- dan: create buffer object + GLuint buffer_id = 0; + glGenBuffers(1, &buffer_id); + r_gl_check_error("glGenBuffers"); + + //- dan: bind to appropriate target and upload data + // Default to vertex buffer, but support both targets + GLenum target = GL_ARRAY_BUFFER; + + glBindBuffer(target, buffer_id); + r_gl_check_error("glBindBuffer"); + + // Upload the data or allocate space + glBufferData(target, size, data, usage); + r_gl_check_error("glBufferData"); + + // Unbind the buffer + glBindBuffer(target, 0); + + //- dan: fill buffer structure + buffer->buffer_id = buffer_id; + buffer->kind = kind; + buffer->size = size; + buffer->target = target; + + R_Handle result = r_gl_handle_from_buffer(buffer); + ProfEnd(); + return result; +} + +r_hook void +r_buffer_release(R_Handle handle) +{ + ProfBeginFunction(); + OS_MutexScopeW(r_gl_state->device_rw_mutex) + { + R_GL_Buffer *buffer = r_gl_buffer_from_handle(handle); + SLLStackPush(r_gl_state->first_to_free_buffer, buffer); + } + ProfEnd(); +} + +r_hook void +r_buffer_write(R_Handle handle, Rng1U64 range, void *data) +{ + R_GL_Buffer *buffer = r_gl_buffer_from_handle(handle); + if(buffer != 0 && buffer != &r_gl_buffer_nil) + { + //- dan: check writability + Assert(buffer->kind == R_ResourceKind_Dynamic || buffer->kind == R_ResourceKind_Stream); + + //- dan: clamp range + Rng1U64 buffer_range = r1u64(0, buffer->size); + Rng1U64 write_range = range; + write_range.min = ClampTop(write_range.min, buffer_range.max); + write_range.max = ClampTop(write_range.max, buffer_range.max); + U64 write_size = dim_1u64(write_range); + + if(write_size > 0) + { + //- dan: bind and upload data + // NOTE: Binding to GL_ARRAY_BUFFER here is just for the glBufferSubData call. + glBindBuffer(GL_ARRAY_BUFFER, buffer->buffer_id); + r_gl_check_error("glBindBuffer Write"); + glBufferSubData(GL_ARRAY_BUFFER, write_range.min, write_size, data); + r_gl_check_error("glBufferSubData"); + glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind + } + } +} + +r_hook void +r_begin_frame(void) +{ + OS_MutexScopeW(r_gl_state->device_rw_mutex) + { + // dan: delete queued-up resources + // ... existing code ... + } +} + +r_hook void +r_end_frame(void) +{ + OS_MutexScopeW(r_gl_state->device_rw_mutex) + { + // Clean up textures that were marked for deletion + for(R_GL_Tex2D *tex = r_gl_state->first_to_free_tex2d, *next = 0; + tex != 0; + tex = next) + { + next = tex->next; + if(tex->texture_id != 0) + { + glDeleteTextures(1, &tex->texture_id); + tex->texture_id = 0; + } + tex->generation += 1; + SLLStackPush(r_gl_state->first_free_tex2d, tex); + } + r_gl_state->first_to_free_tex2d = 0; + + // Clean up buffers that were marked for deletion + for(R_GL_Buffer *buf = r_gl_state->first_to_free_buffer, *next = 0; + buf != 0; + buf = next) + { + next = buf->next; + if(buf->buffer_id != 0) + { + glDeleteBuffers(1, &buf->buffer_id); + buf->buffer_id = 0; + } + buf->generation += 1; + SLLStackPush(r_gl_state->first_free_buffer, buf); + } + r_gl_state->first_to_free_buffer = 0; + + // Clean up any framebuffers/renderbuffers marked for deletion + for(R_GL_Framebuffer *fbo = r_gl_state->first_to_free_framebuffer, *fbo_next = 0; + fbo != 0; + fbo = fbo_next) + { + fbo_next = fbo->next; + if(fbo->fbo_id != 0) + { + glDeleteFramebuffers(1, &fbo->fbo_id); + } + SLLStackPush(r_gl_state->first_free_framebuffer, fbo); + } + r_gl_state->first_to_free_framebuffer = 0; + + for(R_GL_Renderbuffer *rbo = r_gl_state->first_to_free_renderbuffer, *rbo_next = 0; + rbo != 0; + rbo = rbo_next) + { + rbo_next = rbo->next; + if(rbo->rbo_id != 0) + { + glDeleteRenderbuffers(1, &rbo->rbo_id); + } + SLLStackPush(r_gl_state->first_free_renderbuffer, rbo); + } + r_gl_state->first_to_free_renderbuffer = 0; + + // Clear buffer flush arena if it exists + if(r_gl_state->buffer_flush_arena) + { + arena_clear(r_gl_state->buffer_flush_arena); + } + + r_gl_check_error("r_end_frame cleanup"); + } + + // debug_log_current_context(); +} + +r_hook void +r_window_begin_frame(OS_Handle window_handle, R_Handle window_equip) // Renamed OS_Handle param +{ + ProfBeginFunction(); + + R_GL_Window *gl_window = r_gl_window_from_handle(window_equip); + if (gl_window == &r_gl_window_nil) { return; } // Early exit if window handle is invalid + + // Ensure context is current for this window + r_gl_make_context_current(gl_window); + if (!r_gl_state->has_valid_context) + { + log_infof("r_window_begin_frame: No valid context, skipping frame for window %p", gl_window); + return; // Cannot proceed without a valid context + } + + // ADDED: Create global resources if they haven't been initialized yet. + // This ensures resources are created *after* a context is successfully made current. + if (!r_gl_state->global_resources_initialized) + { + log_infof("r_window_begin_frame: Creating global resources for the first time."); + r_gl_create_global_resources(); + // Check if resource creation failed (might happen if context became invalid) + if (!r_gl_state->global_resources_initialized) + { + log_infof("r_window_begin_frame: Failed to create global resources, skipping frame."); + return; + } + } + + // Get current window dimensions + Rng2F32 client_rect = os_client_rect_from_window(window_handle); + Vec2S32 resolution = v2s32((S32)client_rect.x1, (S32)client_rect.y1); + + // Ensure minimum resolution + if (resolution.x < 1) resolution.x = 1; + if (resolution.y < 1) resolution.y = 1; + + // Debug info + // fprintf(stderr, "Window resolution: %d x %d\\n", resolution.x, resolution.y); // Less verbose + + //- dan: check / allocate FBO resources if needed + if(gl_window->last_resolution.x != resolution.x || + gl_window->last_resolution.y != resolution.y || + gl_window->stage_fbo == 0) + { + fprintf(stderr, "Creating/resizing FBOs to %d x %d\\n", resolution.x, resolution.y); + + // Delete any existing resources + r_gl_delete_window_fbo_resources(gl_window); + + // Create stage FBO and attachments + glGenTextures(1, &gl_window->stage_color_texture); + glBindTexture(GL_TEXTURE_2D, gl_window->stage_color_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, resolution.x, resolution.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // Set wrap modes explicitly (good practice) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + r_gl_check_error("stage color texture"); + + // REMOVED: Depth texture creation for stage_fbo + // glGenTextures(1, &gl_window->stage_depth_texture); + // glBindTexture(GL_TEXTURE_2D, gl_window->stage_depth_texture); + // glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, resolution.x, resolution.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); + // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + // r_gl_check_error("stage depth texture"); + + glGenFramebuffers(1, &gl_window->stage_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, gl_window->stage_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl_window->stage_color_texture, 0); + // REMOVED: Depth texture attachment for stage_fbo + // glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, gl_window->stage_depth_texture, 0); + + // Check FBO completeness + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + const char* status_str = "Unknown"; + switch(status) { + case GL_FRAMEBUFFER_COMPLETE: status_str = "Complete"; break; + case GL_FRAMEBUFFER_UNDEFINED: status_str = "Undefined"; break; // Added GL_FRAMEBUFFER_UNDEFINED + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: status_str = "Incomplete Attachment"; break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: status_str = "Missing Attachment"; break; + case GL_FRAMEBUFFER_UNSUPPORTED: status_str = "Unsupported"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: status_str = "Incomplete Draw Buffer"; break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: status_str = "Incomplete Read Buffer"; break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: status_str = "Incomplete Multisample"; break; // Added Multisample + case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: status_str = "Incomplete Layer Targets"; break; // Added Layer Targets + } + fprintf(stderr, "stage_fbo status: %s (0x%x)\\n", status_str, status); + + if (status != GL_FRAMEBUFFER_COMPLETE) { + fprintf(stderr, "ERROR: stage_fbo is not complete! Status: 0x%x\\n", status); + // Fall back to default framebuffer or handle error appropriately + glBindFramebuffer(GL_FRAMEBUFFER, 0); + // Potentially delete the incomplete FBO resources? + // r_gl_delete_window_fbo_resources(gl_window); // Careful not to infinite loop if creation always fails + } else { + fprintf(stderr, "stage_fbo is complete: %u\\n", gl_window->stage_fbo); + } + + // Create scratch FBO (if needed for effects like blur) + glGenTextures(1, &gl_window->stage_scratch_color_texture); + glBindTexture(GL_TEXTURE_2D, gl_window->stage_scratch_color_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, resolution.x, resolution.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + r_gl_check_error("scratch color texture"); + + glGenFramebuffers(1, &gl_window->stage_scratch_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, gl_window->stage_scratch_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl_window->stage_scratch_color_texture, 0); + + // Check scratch FBO completeness + status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + fprintf(stderr, "ERROR: stage_scratch_fbo is not complete! Status: 0x%x\n", status); + } else { + fprintf(stderr, "stage_scratch_fbo is complete\n"); + } + + gl_window->last_resolution = resolution; + } + + // Now bind the stage FBO + GLenum bind_target = (gl_window->stage_fbo != 0) ? gl_window->stage_fbo : 0; + glBindFramebuffer(GL_FRAMEBUFFER, bind_target); + r_gl_check_error("binding framebuffer"); + + // Set up viewport + glViewport(0, 0, resolution.x, resolution.y); + + // Clear with a dark gray + glClearColor(0.2f, 0.2f, 0.2f, 1.f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Enable blending for UI + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); + + r_gl_check_error("UI Blend Func"); +} + +r_hook void +r_window_end_frame(OS_Handle window_handle, R_Handle window_equip) // Renamed OS_Handle param +{ + OS_MutexScope(r_gl_state->device_rw_mutex) + { + R_GL_Window *gl_window = r_gl_window_from_handle(window_equip); + if(gl_window == &r_gl_window_nil) { return; } + + // Get OS window + OS_LNX_Window *lnx_window = (OS_LNX_Window *)window_handle.u64[0]; + if (!lnx_window) { + fprintf(stderr, "ERROR: Invalid OS window handle\n"); + return; + } + + // Render final stage texture to screen using finalize shader + { + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Bind default framebuffer + r_gl_check_error("bind default framebuffer finalize"); + + Vec2S32 size = gl_window->last_resolution; + glViewport(0, 0, size.x, size.y); + r_gl_check_error("glViewport finalize"); + + // Set pipeline state for simple blit + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); +r_gl_check_error("UI Depth Test Disable"); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_CULL_FACE); + r_gl_check_error("pipeline state finalize"); + + // Bind shader and VAO + glUseProgram(r_gl_state->finalize_shader.program_id); + r_gl_check_error("glUseProgram finalize"); + glBindVertexArray(r_gl_state->fullscreen_vao); + r_gl_check_error("glBindVertexArray finalize"); + + // Bind texture and sampler + glActiveTexture(GL_TEXTURE0); +r_gl_check_error("UI Active Texture"); + glBindTexture(GL_TEXTURE_2D, gl_window->stage_color_texture); + r_gl_check_error("glBindTexture finalize"); + glBindSampler(0, r_gl_state->samplers[R_Tex2DSampleKind_Nearest]); // Nearest for final blit + r_gl_check_error("glBindSampler finalize"); + + // Set texture uniform location + if(r_gl_state->finalize_shader.main_texture_uniform_location != -1) + { + glUniform1i(r_gl_state->finalize_shader.main_texture_uniform_location, 0); // Texture unit 0 + r_gl_check_error("glUniform1i finalize"); + } + + // Draw fullscreen quad + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + r_gl_check_error("glDrawArrays finalize"); + } + + // Swap buffers + glXSwapBuffers(os_lnx_gfx_state->display, lnx_window->window); + + // Unbind anything we've bound + glUseProgram(0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +} + +r_hook R_ResourceKind +r_kind_from_tex2d(R_Handle handle) +{ + R_GL_Tex2D *texture = r_gl_tex2d_from_handle(handle); + return texture->kind; +} + +r_hook Vec2S32 +r_size_from_tex2d(R_Handle handle) +{ + R_GL_Tex2D *texture = r_gl_tex2d_from_handle(handle); + return texture->size; +} + +r_hook R_Tex2DFormat +r_format_from_tex2d(R_Handle handle) +{ + R_GL_Tex2D *texture = r_gl_tex2d_from_handle(handle); + return texture->format; +} + +r_hook void +r_fill_tex2d_region(R_Handle handle, Rng2S32 subrect, void *data) +{ + OS_MutexScopeW(r_gl_state->device_rw_mutex) +{ + R_GL_Tex2D *texture = r_gl_tex2d_from_handle(handle); + if(texture == &r_gl_tex2d_nil || texture->texture_id == 0) + { + return; + } + + // Ensure the texture kind allows updates + // Assert(texture->kind == R_ResourceKind_Dynamic || texture->kind == R_ResourceKind_Stream); + if (texture->kind == R_ResourceKind_Static) { + // Cannot update static textures + return; + } + + Vec2S32 size = v2s32(subrect.x1 - subrect.x0, subrect.y1 - subrect.y0); + if (size.x <= 0 || size.y <= 0) { + return; // Invalid region + } + + GLenum format = r_gl_format_from_tex2d_format(texture->format); + GLenum type = r_gl_type_from_tex2d_format(texture->format); + U64 bytes_per_pixel = r_tex2d_format_bytes_per_pixel_table[texture->format]; + + // Set appropriate unpack alignment + // Common alignments are 1, 2, 4, 8. Pixel data rows must start on multiples of this alignment. + GLint previous_alignment; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &previous_alignment); + r_gl_check_error("glGetIntegerv GL_UNPACK_ALIGNMENT"); // Check error after get + if (bytes_per_pixel == 1) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + } + else if (bytes_per_pixel == 2) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + } + else { + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // Default for 3, 4, 8, 16 bytes per pixel usually safe + } + r_gl_check_error("glPixelStorei UNPACK"); + + glBindTexture(GL_TEXTURE_2D, texture->texture_id); +r_gl_check_error("UI Bind Texture"); + r_gl_check_error("glBindTexture FillRegion"); + + glTexSubImage2D( + GL_TEXTURE_2D, // target + 0, // level (mipmap level) + subrect.x0, // xoffset + subrect.y0, // yoffset + size.x, // width + size.y, // height + format, // format of pixel data + type, // type of pixel data + data // pointer to image data + ); + r_gl_check_error("glTexSubImage2D"); + + glBindTexture(GL_TEXTURE_2D, 0); + + // Restore previous unpack alignment + glPixelStorei(GL_UNPACK_ALIGNMENT, previous_alignment); + r_gl_check_error("glPixelStorei UNPACK Restore"); + } +} + +////////////////////////////////// +//~ dan: Primitive Topology Mapping + +internal GLenum +r_gl_primitive_from_topology(R_GeoTopologyKind kind) +{ + GLenum result = GL_TRIANGLES; // Default + switch(kind) + { + case R_GeoTopologyKind_Lines: result = GL_LINES; break; + case R_GeoTopologyKind_LineStrip: result = GL_LINE_STRIP; break; + case R_GeoTopologyKind_Triangles: result = GL_TRIANGLES; break; + case R_GeoTopologyKind_TriangleStrip: result = GL_TRIANGLE_STRIP; break; + default: Assert(!"Invalid primitive topology kind"); break; // Should not happen + } + return result; +} + +////////////////////////////////// +//~ dan: UBO Binding Points + +#define R_GL_UNIFORM_BINDING_POINT_RECT 0 +#define R_GL_UNIFORM_BINDING_POINT_BLUR 1 +#define R_GL_UNIFORM_BINDING_POINT_MESH 2 +// #define R_GL_UNIFORM_BINDING_POINT_UI 3 // Define if UI UBO is used + +// Note: The UBO/Instance struct definitions are in render_opengl.h +// Do NOT redefine them here - use those definitions + + +r_hook void +r_window_submit(OS_Handle window_handle, R_Handle window_equip, R_PassList *passes) +{ + OS_MutexScopeW(r_gl_state->device_rw_mutex) + { + //- dan: unpack arguments + R_GL_Window *gl_window = r_gl_window_from_handle(window_equip); + Vec2S32 resolution = gl_window->last_resolution; + + fprintf(stderr, "r_window_submit called - window resolution: %d x %d, FBO: %u\n", + resolution.x, resolution.y, gl_window->stage_fbo); + + // Verify we have valid FBOs + if (gl_window->stage_fbo == 0) { + fprintf(stderr, "ERROR: Invalid stage FBO in r_window_submit!\n"); + } + + // Ensure the correct GL context is current (should be set by r_window_begin_frame) + // TODO: Add check if necessary: if (glXGetCurrentContext() != gl_window->context) { /* make current or error */ } + + //- dan: do passes + for(R_PassNode *pass_n = passes->first; pass_n != 0; pass_n = pass_n->next) + { + R_Pass *pass = &pass_n->v; + fprintf(stderr, "Processing pass kind: %d\n", pass->kind); + + switch(pass->kind) + { + default:{}break; + + //////////////////////// + //- dan: ui rendering pass + // + case R_PassKind_UI: + { + //- dan: unpack params + R_PassParams_UI *params = pass->params_ui; + R_BatchGroup2DList *rect_batch_groups = ¶ms->rects; + + // Count the batch groups manually instead of using node_count + int batch_group_count = 0; + for(R_BatchGroup2DNode *node = rect_batch_groups->first; node != NULL; node = node->next) { + batch_group_count++; + } + fprintf(stderr, "UI pass has %d batch groups\n", batch_group_count); + + //- dan: set common GL state for UI + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); + +r_gl_check_error("UI Blend Func"); + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); +r_gl_check_error("UI Depth Test Disable"); + glEnable(GL_SCISSOR_TEST); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CW); + glViewport(0, 0, resolution.x, resolution.y); // Full window viewport for UI passes + + // Check if VAO and shader program are valid before using them + if (r_gl_state->rect_vao == 0 || r_gl_state->rect_shader.program_id == 0) { + fprintf(stderr, "Error: UI rendering resources not valid (VAO: %u, Shader: %u)\n", + r_gl_state->rect_vao, r_gl_state->rect_shader.program_id); + break; // Skip this pass if resources are invalid + } + + glBindVertexArray(r_gl_state->rect_vao); +r_gl_check_error("UI Bind VAO"); + glUseProgram(r_gl_state->rect_shader.program_id); + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + fprintf(stderr, "GL Error after binding VAO/shader: 0x%x\n", err); + } + + //- dan: draw each batch group + for(R_BatchGroup2DNode *group_n = rect_batch_groups->first; group_n != 0; group_n = group_n->next) + { + R_BatchList *batches = &group_n->batches; + R_BatchGroup2DParams *group_params = &group_n->params; + + // <<< ADD LOGGING HERE >>> + fprintf(stderr, " UI Batch Group:\n"); + fprintf(stderr, " Texture Handle: %lu\n", group_params->tex.u64[0]); // Fixed format specifier + fprintf(stderr, " Sample Kind: %d\n", group_params->tex_sample_kind); + fprintf(stderr, " Transform: [[%.2f, %.2f, %.2f], [%.2f, %.2f, %.2f], [%.2f, %.2f, %.2f]]\n", + group_params->xform.v[0][0], group_params->xform.v[0][1], group_params->xform.v[0][2], + group_params->xform.v[1][0], group_params->xform.v[1][1], group_params->xform.v[1][2], + group_params->xform.v[2][0], group_params->xform.v[2][1], group_params->xform.v[2][2]); + fprintf(stderr, " Clip Rect: (%.1f, %.1f) -> (%.1f, %.1f)\n", + group_params->clip.x0, group_params->clip.y0, group_params->clip.x1, group_params->clip.y1); + fprintf(stderr, " Transparency: %.2f\n", group_params->transparency); + + + // dan: bind sampler + glBindSampler(0, r_gl_state->samplers[group_params->tex_sample_kind]); // Bind to texture unit 0 + r_gl_check_error("glBindSampler UI"); + + // dan: bind texture + R_GL_Tex2D *texture = r_gl_tex2d_from_handle(group_params->tex); + if(texture == &r_gl_tex2d_nil || texture->texture_id == 0) + { + texture = r_gl_tex2d_from_handle(r_gl_state->backup_texture); + } + glActiveTexture(GL_TEXTURE0); +r_gl_check_error("UI Active Texture"); + glBindTexture(GL_TEXTURE_2D, texture->texture_id); +r_gl_check_error("UI Bind Texture"); + if(r_gl_state->rect_shader.main_texture_uniform_location != -1) + { + glUniform1i(r_gl_state->rect_shader.main_texture_uniform_location, 0); // Texture unit 0 + } + else + { + // Try with the known uniform name if location wasn't found during initialization + GLint tex_loc = glGetUniformLocation(r_gl_state->rect_shader.program_id, "main_t2d"); + if (tex_loc != -1) { + glUniform1i(tex_loc, 0); + // Cache it for future use + r_gl_state->rect_shader.main_texture_uniform_location = tex_loc; + } + } + r_gl_check_error("Texture Binding UI"); + + + // dan: upload uniforms + { + R_GL_Uniforms_Rect uniforms = {0}; + uniforms.viewport_size_px = v2f32(resolution.x, resolution.y); + uniforms.opacity = 1.0f - group_params->transparency; + uniforms.texture_t2d_size_px = v2f32(texture->size.x > 0 ? texture->size.x : 1.f, + texture->size.y > 0 ? texture->size.y : 1.f); // Avoid divide by zero + + // Texture channel mapping based on format + // DEFAULT TO IDENTITY for standard RGBA/BGRA formats. + Mat4x4F32 tex_map_matrix = m4x4f32(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1); // Identity + + // EXPLICITLY handle single-channel R8 format. + if(texture->format == R_Tex2DFormat_R8) + { + // Map R channel -> R,G,B,A for grayscale rendering in the shader. + // GLSL matrix multiplication means the first column of the uniform matrix affects the result. + // Target: (R,R,R,R) = Sample(R,G,B,A) * MapMatrix + tex_map_matrix = m4x4f32(1,1,1,1, 0,0,0,0, 0,0,0,0, 0,0,0,0); + } + // OPTIONAL: Add explicit handling for other formats like BGRA if necessary. + // else if (texture->format == R_Tex2DFormat_BGRA8) { ...swizzle matrix... } + + // Transpose C row-major matrix -> GLSL std140 column-major format for the UBO. + Mat4x4F32 transposed_tex_map = transpose_4x4f32(tex_map_matrix); + // Assign the transposed matrix columns to the uniform structure fields. + for (int i = 0; i < 4; i++) { + uniforms.texture_sample_channel_map[i] = v4f32( + transposed_tex_map.v[i][0], + transposed_tex_map.v[i][1], + transposed_tex_map.v[i][2], + transposed_tex_map.v[i][3] + ); + } + + // Transform: C Mat3x3F32 (row-major) to GLSL mat3 (column-major in std140) + Mat3x3F32 c_xform = group_params->xform; + // Manually transpose and copy into the std140 padded structure + // Remember GLSL mat3 is represented as 3 Vec4F32 columns in std140 layout + uniforms.xform[0] = v4f32(c_xform.v[0][0], c_xform.v[1][0], c_xform.v[2][0], 0); // Col 0 + uniforms.xform[1] = v4f32(c_xform.v[0][1], c_xform.v[1][1], c_xform.v[2][1], 0); // Col 1 + uniforms.xform[2] = v4f32(c_xform.v[0][2], c_xform.v[1][2], c_xform.v[2][2], 0); // Col 2 + + // Calculate xform_scale from the GLSL-layout columns (before sending to GPU) + Vec2F32 gl_col0 = v2f32(uniforms.xform[0].x, uniforms.xform[0].y); + Vec2F32 gl_col1 = v2f32(uniforms.xform[1].x, uniforms.xform[1].y); + uniforms.xform_scale.x = length_2f32(gl_col0); + uniforms.xform_scale.y = length_2f32(gl_col1); + uniforms.xform_scale.x = Max(uniforms.xform_scale.x, 0.0001f); // Avoid zero scale + uniforms.xform_scale.y = Max(uniforms.xform_scale.y, 0.0001f); + + // <<< ADD LOGGING HERE >>> + fprintf(stderr, " Uniforms Upload:\n"); + fprintf(stderr, " Viewport: %.1f x %.1f\n", uniforms.viewport_size_px.x, uniforms.viewport_size_px.y); + fprintf(stderr, " Opacity: %.2f\n", uniforms.opacity); + fprintf(stderr, " Tex Size: %.1f x %.1f\n", uniforms.texture_t2d_size_px.x, uniforms.texture_t2d_size_px.y); + // Optional: Log matrices if needed, they can be verbose + // fprintf(stderr, " Tex Map Matrix: ...\n"); + // fprintf(stderr, " Xform Matrix (ColMajor): [[%.2f, %.2f, %.2f], [%.2f, %.2f, %.2f], [%.2f, %.2f, %.2f]]\n", ...); + fprintf(stderr, " Xform Scale: %.2f, %.2f\n", uniforms.xform_scale.x, uniforms.xform_scale.y); + + + // Bind and update UBO + glBindBufferBase(GL_UNIFORM_BUFFER, R_GL_UNIFORM_BINDING_POINT_RECT, r_gl_state->uniform_type_kind_buffers[R_GL_UniformTypeKind_Rect]); + glBindBuffer(GL_UNIFORM_BUFFER, r_gl_state->uniform_type_kind_buffers[R_GL_UniformTypeKind_Rect]); // Rebind for glBufferSubData + r_gl_check_error("glBindBufferBase Rect UBO"); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(uniforms), &uniforms); + r_gl_check_error("UI UBO SubData"); + r_gl_check_error("glBufferSubData Rect UBO"); + //glBindBuffer(GL_UNIFORM_BUFFER, 0); // REMOVED: Unbind UBO after update + } + + // dan: upload instance data + U64 hardcoded_size = 0; // Size for hardcoded data + GLsizei instance_count = 0; // Instance count for draw call + U64 total_instance_bytes = batches->byte_count; + if (total_instance_bytes > 0 && batches->bytes_per_inst > 0) { + // <<< ADD LOGGING HERE (Log first instance of the first batch) >>> + if (batches->first && batches->first->v.byte_count >= sizeof(R_Rect2DInst) && batches->bytes_per_inst == sizeof(R_Rect2DInst)) { + R_Rect2DInst *first_inst = (R_Rect2DInst *)batches->first->v.v; + fprintf(stderr, " First Instance Data:\n"); + fprintf(stderr, " Dst: (%.1f, %.1f) -> (%.1f, %.1f)\n", first_inst->dst.x0, first_inst->dst.y0, first_inst->dst.x1, first_inst->dst.y1); + fprintf(stderr, " Src: (%.1f, %.1f) -> (%.1f, %.1f)\n", first_inst->src.x0, first_inst->src.y0, first_inst->src.x1, first_inst->src.y1); + // Use integer indices instead of Corner_ enum names and x,y,z,w members + fprintf(stderr, " Colors[BL]: (%.2f, %.2f, %.2f, %.2f)\n", first_inst->colors[0].x, first_inst->colors[0].y, first_inst->colors[0].z, first_inst->colors[0].w); + fprintf(stderr, " Colors[TL]: (%.2f, %.2f, %.2f, %.2f)\n", first_inst->colors[1].x, first_inst->colors[1].y, first_inst->colors[1].z, first_inst->colors[1].w); + fprintf(stderr, " Colors[BR]: (%.2f, %.2f, %.2f, %.2f)\n", first_inst->colors[2].x, first_inst->colors[2].y, first_inst->colors[2].z, first_inst->colors[2].w); + fprintf(stderr, " Colors[TR]: (%.2f, %.2f, %.2f, %.2f)\n", first_inst->colors[3].x, first_inst->colors[3].y, first_inst->colors[3].z, first_inst->colors[3].w); + fprintf(stderr, " Radii[BL,TL,BR,TR]: %.1f, %.1f, %.1f, %.1f\n", first_inst->corner_radii[0], first_inst->corner_radii[1], first_inst->corner_radii[2], first_inst->corner_radii[3]); + fprintf(stderr, " Border: %.1f, Softness: %.1f, WhiteOverride: %.1f\n", first_inst->border_thickness, first_inst->edge_softness, first_inst->white_texture_override); + } + + glBindBuffer(GL_ARRAY_BUFFER, r_gl_state->instance_vbo); + r_gl_check_error("glBindBuffer Instance VBO"); + + GLint current_vbo_size = 0; + glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, ¤t_vbo_size); + r_gl_check_error("glGetBufferParameteriv Instance VBO Size"); + + if (total_instance_bytes > (U64)current_vbo_size) { + glBufferData(GL_ARRAY_BUFFER, total_instance_bytes, NULL, GL_STREAM_DRAW); + r_gl_check_error("Instance VBO Resize (glBufferData)"); + } + U64 current_offset = 0; + for(R_BatchNode *batch_n = batches->first; batch_n != 0; batch_n = batch_n->next) + { + U64 copy_size = batch_n->v.byte_count; + if (copy_size > 0) { + Assert(current_offset + copy_size <= total_instance_bytes); + glBufferSubData(GL_ARRAY_BUFFER, (GLintptr)current_offset, (GLsizeiptr)copy_size, batch_n->v.v); + r_gl_check_error("Instance VBO Upload (glBufferSubData)"); + current_offset += copy_size; + } + } + Assert(current_offset == total_instance_bytes); + instance_count = total_instance_bytes / batches->bytes_per_inst; + } else { + instance_count = 0; // No data or invalid size info + } + + // dan: setup scissor rect + { + Rng2F32 clip_rect_f = group_params->clip; + Rng2S32 clip_rect_i = {0}; + // Check for zero rect which means no clipping + if(clip_rect_f.x0 == 0 && clip_rect_f.y0 == 0 && clip_rect_f.x1 == 0 && clip_rect_f.y1 == 0) + { + clip_rect_i = r2s32p(0, 0, resolution.x, resolution.y); + } + else + { + // Clamp and round clip rect provided in top-left coordinates + clip_rect_i = r2s32p(round_f32_s32(clip_rect_f.x0), round_f32_s32(clip_rect_f.y0), round_f32_s32(clip_rect_f.x1), round_f32_s32(clip_rect_f.y1)); + clip_rect_i.x0 = Clamp(0, clip_rect_i.x0, resolution.x); + clip_rect_i.y0 = Clamp(0, clip_rect_i.y0, resolution.y); + clip_rect_i.x1 = Clamp(0, clip_rect_i.x1, resolution.x); + clip_rect_i.y1 = Clamp(0, clip_rect_i.y1, resolution.y); + } + // GL scissor origin is bottom-left. Convert Y. + // y_gl = resolution.y - y_topleft + // height_gl = y_topleft_bottom - y_topleft_top + GLint y_gl = resolution.y - clip_rect_i.y1; + GLsizei height_gl = Max(0, clip_rect_i.y1 - clip_rect_i.y0); + GLsizei width_gl = Max(0, clip_rect_i.x1 - clip_rect_i.x0); + glScissor(clip_rect_i.x0, y_gl, width_gl, height_gl); + r_gl_check_error("glScissor UI"); + } + + // dan: bind target FBO (UI draws to stage FBO) + glBindFramebuffer(GL_FRAMEBUFFER, gl_window->stage_fbo); + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + fprintf(stderr, "Error binding stage FBO for UI drawing: 0x%x\n", err); + } + + // Verify the FBO is actually bound + GLint current_fbo = 0; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤t_fbo); + if (current_fbo != gl_window->stage_fbo) { + fprintf(stderr, "ERROR: Wrong FBO bound! Expected %u, got %d\n", + gl_window->stage_fbo, current_fbo); + } + +r_gl_check_error("glBindFramebuffer UI Stage"); + + // dan: draw + if (instance_count > 0) { // Use the calculated instance_count + // Check scissor settings before draw + GLint scissor[4]; + glGetIntegerv(GL_SCISSOR_BOX, scissor); + // <<< ADD LOGGING HERE >>> + fprintf(stderr, " Draw Call: %d instances, Scissor(%d, %d, %d, %d)\n", + instance_count, scissor[0], scissor[1], scissor[2], scissor[3]); + + // Extra verification of viewport and other states + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + // fprintf(stderr, "UI Draw: Viewport(%d, %d, %d, %d)\n", + // viewport[0], viewport[1], viewport[2], viewport[3]); + + // Verify transform feedback isn't active (can prevent drawing) + glDisable(GL_RASTERIZER_DISCARD); + + // Ensure blend mode is set correctly + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); + + r_gl_check_error("UI Blend Func"); + + // Draw the instances + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, instance_count); // Use calculated instance_count + r_gl_check_error("glDrawArraysInstanced UI"); + } + + // Reset state to avoid affecting subsequent batches + glDisable(GL_SCISSOR_TEST); + glEnable(GL_SCISSOR_TEST); + r_gl_check_error("UI Draw State Reset"); + } // end batch group loop + + // Reset texture/sampler bindings for unit 0 + glActiveTexture(GL_TEXTURE0); +r_gl_check_error("UI Active Texture"); + glBindTexture(GL_TEXTURE_2D, 0); + glBindSampler(0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind instance VBO + glBindVertexArray(0); // Unbind VAO + glUseProgram(0); // Unbind shader program + + }break; + + //////////////////////// + //- dan: blur rendering pass + // + case R_PassKind_Blur: + { + R_PassParams_Blur *params = pass->params_blur; + + //- dan: skip if zero size + if(params->blur_size <= 0.001f) + + //- dan: common blur state + glBindVertexArray(r_gl_state->fullscreen_vao); // VAO for fullscreen quad (no attributes needed) + glUseProgram(r_gl_state->blur_shader.program_id); + glBindSampler(0, r_gl_state->samplers[R_Tex2DSampleKind_Linear]); // Use linear sampling for blur + glDisable(GL_BLEND); // Blur passes usually overwrite destination + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); +r_gl_check_error("UI Depth Test Disable"); + glDisable(GL_SCISSOR_TEST); // Blur affects whole texture (unless masked later) + glDisable(GL_CULL_FACE); + glViewport(0, 0, resolution.x, resolution.y); // Full texture viewport + r_gl_check_error("Blur Pass Setup"); + + // dan: set up uniforms (calculate kernel, fill struct) + R_GL_Uniforms_Blur uniforms = {0}; + { + // Calculate Gaussian weights + F32 weights[ ArrayCount(uniforms.kernel)*2 + 1 ] = {0}; // Space for center + pairs + U64 kernel_radius = Min((U64)ArrayCount(uniforms.kernel), (U64)ceilf((params->blur_size - 1.0f) / 2.0f)); + U64 kernel_size = kernel_radius * 2 + 1; + F32 sigma = Max(0.01f, (F32)kernel_radius / 2.0f); // Sigma heuristic + + F32 weight_sum = 0.0f; + for (S64 i = -(S64)kernel_radius; i <= (S64)kernel_radius; ++i) { + F32 w = expf(-((F32)i * (F32)i) / (2.0f * sigma * sigma)); + weights[i + kernel_radius] = w; + weight_sum += w; + } + // Normalize weights + if (weight_sum > 0.0001f) { + for (U64 i = 0; i < kernel_size; ++i) { + weights[i] /= weight_sum; + } + } else { // Handle zero sum case (e.g., blur_size 1 -> radius 0) + MemoryZeroArray(weights); + weights[0] = 1.0f; + kernel_size = 1; + kernel_radius = 0; + } + + // Prepare kernel for bilinear optimization (combine pairs of weights) + uniforms.kernel[0].x = weights[kernel_radius]; // Center tap weight + uniforms.kernel[0].y = 0.0f; // Center tap offset + uniforms.blur_count = 1; // Start with center tap + for (U64 i = 1; i <= kernel_radius; i += 2) { + if (uniforms.blur_count >= ArrayCount(uniforms.kernel)) break; // Check bounds + F32 w0 = weights[kernel_radius + i]; + F32 w1 = (i + 1 <= kernel_radius) ? weights[kernel_radius + i + 1] : 0.0f; + F32 w_sum = w0 + w1; + if (w_sum > 1e-6f) { + F32 offset = (F32)i + (w1 / w_sum); // Weighted offset from center + uniforms.kernel[uniforms.blur_count].x = w_sum; + uniforms.kernel[uniforms.blur_count].y = offset; + uniforms.blur_count++; + } + } + + // Common blur parameters + uniforms.rect = v4f32(params->rect.x0, params->rect.y0, params->rect.x1, params->rect.y1); + uniforms.viewport_size = v2f32(resolution.x, resolution.y); + MemoryCopy(&uniforms.corner_radii_px, params->corner_radii, sizeof(params->corner_radii)); + } + + // Bind UBO once + glBindBufferBase(GL_UNIFORM_BUFFER, R_GL_UNIFORM_BINDING_POINT_BLUR, r_gl_state->uniform_type_kind_buffers[R_GL_UniformTypeKind_Blur]); + r_gl_check_error("Blur UBO Bind"); + + glActiveTexture(GL_TEXTURE0); +r_gl_check_error("UI Active Texture"); + if (r_gl_state->blur_shader.main_texture_uniform_location != -1) { + glUniform1i(r_gl_state->blur_shader.main_texture_uniform_location, 0); // Use texture unit 0 + } + + // Pass 1: Horizontal Blur (Stage Color -> Scratch Color) + { + glBindFramebuffer(GL_FRAMEBUFFER, gl_window->stage_scratch_fbo); + r_gl_check_error("glBindFramebuffer Blur Scratch"); + uniforms.direction = v2f32(1.0f, 0.0f); // Direction (texel size applied in shader) + glBindBuffer(GL_UNIFORM_BUFFER, r_gl_state->uniform_type_kind_buffers[R_GL_UniformTypeKind_Blur]); // Bind for update + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(uniforms), &uniforms); // Update UBO +r_gl_check_error("UI UBO SubData"); + glBindBuffer(GL_UNIFORM_BUFFER, 0); // Unbind after update + r_gl_check_error("glBufferSubData Blur H"); + glBindTexture(GL_TEXTURE_2D, gl_window->stage_color_texture); // Input texture + r_gl_check_error("glBindTexture Blur H Input"); + glBindVertexArray(r_gl_state->fullscreen_vao); + r_gl_check_error("glBindVertexArray fullscreen_vao Blur"); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // Draw fullscreen quad + r_gl_check_error("glDrawArrays Blur H"); + glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture + r_gl_check_error("glBindTexture Blur H Unbind"); + } + + // Pass 2: Vertical Blur (Scratch Color -> Stage Color) + { + glBindFramebuffer(GL_FRAMEBUFFER, gl_window->stage_fbo); // Target main stage FBO + r_gl_check_error("glBindFramebuffer Blur Stage"); + uniforms.direction = v2f32(0.0f, 1.0f); // Direction + glBindBuffer(GL_UNIFORM_BUFFER, r_gl_state->uniform_type_kind_buffers[R_GL_UniformTypeKind_Blur]); // Explicitly bind before update + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(uniforms), &uniforms); // Update UBO +r_gl_check_error("UI UBO SubData"); + glBindBuffer(GL_UNIFORM_BUFFER, 0); // Unbind after update + r_gl_check_error("glBufferSubData Blur V"); + glBindTexture(GL_TEXTURE_2D, gl_window->stage_scratch_color_texture); // Input texture + r_gl_check_error("glBindTexture Blur V Input"); + glBindVertexArray(r_gl_state->fullscreen_vao); + r_gl_check_error("glBindVertexArray fullscreen_vao Blur"); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // Draw fullscreen quad + r_gl_check_error("glDrawArrays Blur V"); + glBindTexture(GL_TEXTURE_2D, 0); // Unbind texture + r_gl_check_error("glBindTexture Blur V Unbind"); + } + + // Cleanup blur state + glBindTexture(GL_TEXTURE_2D, 0); + glBindSampler(0, 0); + glEnable(GL_BLEND); // Re-enable blend potentially needed by subsequent passes + glEnable(GL_SCISSOR_TEST); // Re-enable scissor + + // Unbind the VAO after both blur passes are done + glBindVertexArray(0); + r_gl_check_error("glBindVertexArray 0 Blur End"); + }break; + + + //////////////////////// + //- dan: 3d geometry rendering pass + // + case R_PassKind_Geo3D: + { + //- dan: unpack params + R_PassParams_Geo3D *params = pass->params_geo3d; + R_BatchGroup3DMap *mesh_group_map = ¶ms->mesh_batches; + + //- dan: common Geo3D state + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CW); // Standard convention + glEnable(GL_BLEND); // Blend fragments onto target + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); + +r_gl_check_error("UI Blend Func"); + glEnable(GL_SCISSOR_TEST); // Use viewport as scissor initially + glBindVertexArray(r_gl_state->mesh_vao); // VAO for mesh vertex & instance attributes + glUseProgram(r_gl_state->mesh_shader.program_id); + r_gl_check_error("Geo3D Pass Setup"); + + //- dan: bind & clear Geo3D FBO + glBindFramebuffer(GL_FRAMEBUFFER, gl_window->geo3d_fbo); + r_gl_check_error("glBindFramebuffer Geo3D"); + Vec4F32 bg_color = {0.1f, 0.1f, 0.1f, 0.0f}; // Example clear color (transparent black or grey) + glClearBufferfv(GL_COLOR, 0, bg_color.v); // Clear color attachment 0 + glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0); // Clear depth to 1.0, stencil to 0 + r_gl_check_error("Geo3D FBO Clear"); + + + //- dan: set viewport & scissor from params + Rng2F32 viewport_rng_f = params->viewport; // Top-left coordinate system + Vec2S32 viewport_pos_tl = v2s32(round_f32_s32(viewport_rng_f.x0), round_f32_s32(viewport_rng_f.y0)); + Vec2S32 viewport_dim = v2s32(Max(0, round_f32_s32(viewport_rng_f.x1 - viewport_rng_f.x0)), + Max(0, round_f32_s32(viewport_rng_f.y1 - viewport_rng_f.y0))); + viewport_pos_tl.x = Clamp(0, viewport_pos_tl.x, resolution.x); + viewport_pos_tl.y = Clamp(0, viewport_pos_tl.y, resolution.y); + // Clamp dimensions based on position + viewport_dim.x = Clamp(0, viewport_dim.x, resolution.x - viewport_pos_tl.x); + viewport_dim.y = Clamp(0, viewport_dim.y, resolution.y - viewport_pos_tl.y); + + // GL viewport/scissor origin is bottom-left. Convert Y. + GLint y_gl = resolution.y - (viewport_pos_tl.y + viewport_dim.y); + glViewport(viewport_pos_tl.x, y_gl, viewport_dim.x, viewport_dim.y); + glScissor(viewport_pos_tl.x, y_gl, viewport_dim.x, viewport_dim.y); + r_gl_check_error("Geo3D Viewport/Scissor"); + + + // dan: setup uniforms (view/projection) + { + R_GL_Uniforms_Mesh uniforms = {0}; + // Combine and transpose projection * view matrix + // Flip Y in projection matrix for OpenGL coordinate system + Mat4x4F32 projection_gl = params->projection; + projection_gl.v[1][1] *= -1.0f; + Mat4x4F32 view_proj_c = mul_4x4f32(projection_gl, params->view); + uniforms.view_proj_matrix = transpose_4x4f32(view_proj_c); // Transpose for GLSL column-major + + glBindBufferBase(GL_UNIFORM_BUFFER, R_GL_UNIFORM_BINDING_POINT_MESH, r_gl_state->uniform_type_kind_buffers[R_GL_UniformTypeKind_Mesh]); + r_gl_check_error("glBindBufferBase Mesh UBO"); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(uniforms), &uniforms); +r_gl_check_error("UI UBO SubData"); + r_gl_check_error("glBufferSubData Mesh UBO"); + } + + + // dan: draw mesh batches (instanced) + for(U64 slot_idx = 0; slot_idx < mesh_group_map->slots_count; slot_idx += 1) + { + for(R_BatchGroup3DMapNode *n = mesh_group_map->slots[slot_idx]; n != 0; n = n->next) + { + R_BatchList *batches = &n->batches; + R_BatchGroup3DParams *group_params = &n->params; + + + R_GL_Buffer *vtx_buffer = r_gl_buffer_from_handle(group_params->mesh_vertices); + R_GL_Buffer *idx_buffer = r_gl_buffer_from_handle(group_params->mesh_indices); + + if (vtx_buffer == &r_gl_buffer_nil || idx_buffer == &r_gl_buffer_nil || vtx_buffer->buffer_id == 0 || idx_buffer->buffer_id == 0) { + log_info(str8_lit("Skipping Geo3D batch group due to invalid vertex or index buffer.")); + } + + // Bind vertex buffer and ensure VAO attributes are set correctly for it + // VAO should already be bound (glBindVertexArray(r_gl_state->mesh_vao)) + glBindBuffer(GL_ARRAY_BUFFER, vtx_buffer->buffer_id); + r_gl_check_error("glBindBuffer Geo3D VBO"); + // Re-specify vertex attrib pointers as VBO binding is part of VAO state in Core Profile *only if* + // GL_VERTEX_ARRAY_BINDING is used, which is not the case here. So we must reset them. + size_t mesh_vert_stride = 11 * sizeof(F32); // Pos(3), Norm(3), Tex(2), Col(3) + glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, mesh_vert_stride, (void*)(0 * sizeof(F32))); // Pos + glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, mesh_vert_stride, (void*)(3 * sizeof(F32))); // Norm + glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, mesh_vert_stride, (void*)(6 * sizeof(F32))); // Tex + glEnableVertexAttribArray(3); glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, mesh_vert_stride, (void*)(8 * sizeof(F32))); // Col + // Ensure vertex attrib divisors are 0 for per-vertex data + glVertexAttribDivisor(0, 0); + glVertexAttribDivisor(1, 0); + glVertexAttribDivisor(2, 0); + glVertexAttribDivisor(3, 0); + r_gl_check_error("glVertexAttribPointer Geo3D Vertex"); + + // Bind index buffer + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idx_buffer->buffer_id); + r_gl_check_error("glBindBuffer Geo3D IBO"); + + // Upload instance data (transforms) to the shared instance VBO + U64 total_instance_bytes = batches->byte_count; // Total bytes already available in batches + if (total_instance_bytes > 0) { + glBindBuffer(GL_ARRAY_BUFFER, r_gl_state->instance_vbo); +r_gl_check_error("UI Bind Instance VBO"); + r_gl_check_error("glBindBuffer Geo3D Instance VBO"); + + // Check if VBO needs resize/reallocation + GLint current_vbo_size = 0; + glGetBufferParameteriv(GL_ARRAY_BUFFER, GL_BUFFER_SIZE, ¤t_vbo_size); + r_gl_check_error("glGetBufferParameteriv Instance VBO Size Geo3D"); + + B32 vbo_resized = 0; + if (total_instance_bytes > (U64)current_vbo_size) { + glBufferData(GL_ARRAY_BUFFER, total_instance_bytes, NULL, GL_STREAM_DRAW); // Orphan & Resize + r_gl_check_error("Instance VBO Resize (glBufferData) Geo3D"); + vbo_resized = 1; + } + + // Map and copy instance data + void *instance_data_ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, total_instance_bytes, GL_MAP_WRITE_BIT | (vbo_resized ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_INVALIDATE_RANGE_BIT) ); + if (instance_data_ptr) { + U64 offset = 0; + for(R_BatchNode *batch_n = batches->first; batch_n != 0; batch_n = batch_n->next) + { + // Assuming batch_n->v.v points to R_Mesh3DInst array + U64 copy_size = batch_n->v.byte_count; + Assert(offset + copy_size <= total_instance_bytes); + MemoryCopy((U8*)instance_data_ptr + offset, batch_n->v.v, copy_size); + offset += copy_size; + } + Assert(offset == total_instance_bytes); + glUnmapBuffer(GL_ARRAY_BUFFER); + r_gl_check_error("Instance VBO Upload (glUnmapBuffer) Geo3D"); + } else { + r_gl_check_error("glMapBufferRange Failed Geo3D"); + glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind on failure + continue; // Skip draw if map failed + } + + // Ensure instance attribute pointers are correctly set *after* binding the instance VBO + // These should point to the r_gl_state->instance_vbo + size_t mesh_inst_stride = sizeof(R_Mesh3DInst); // Assuming R_Mesh3DInst is the instance structure + size_t xform_offset = OffsetOf(R_Mesh3DInst, xform); + glEnableVertexAttribArray(4); glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, mesh_inst_stride, (void*)(xform_offset + 0*sizeof(Vec4F32))); glVertexAttribDivisor(4, 1); + glEnableVertexAttribArray(5); glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, mesh_inst_stride, (void*)(xform_offset + 1*sizeof(Vec4F32))); glVertexAttribDivisor(5, 1); + glEnableVertexAttribArray(6); glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, mesh_inst_stride, (void*)(xform_offset + 2*sizeof(Vec4F32))); glVertexAttribDivisor(6, 1); + glEnableVertexAttribArray(7); glVertexAttribPointer(7, 4, GL_FLOAT, GL_FALSE, mesh_inst_stride, (void*)(xform_offset + 3*sizeof(Vec4F32))); glVertexAttribDivisor(7, 1); + r_gl_check_error("glVertexAttribPointer Geo3D Instance"); + } else { + // Disable instance attributes if not drawing instanced? No, draw with instance count 0? + // Or better, skip the draw call if total_inst_count is 0. + } + + // Draw instanced + GLenum topology = r_gl_primitive_from_topology(group_params->mesh_geo_topology); + U64 index_count = idx_buffer->size / sizeof(U32); // Assuming U32 indices + if (index_count > 0 && batches->byte_count > 0 && batches->bytes_per_inst > 0) { + glDrawElementsInstanced(topology, index_count, GL_UNSIGNED_INT, (void*)0, batches->byte_count / batches->bytes_per_inst); + r_gl_check_error("glDrawElementsInstanced Geo3D"); + } + } + } // End Geo3D group map loop + + // Unbind element array buffer before compositing if needed + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind the instance VBO now + + //- dan: composite geo3d result into stage fbo + glBindFramebuffer(GL_FRAMEBUFFER, gl_window->stage_fbo); // Target main stage FBO + r_gl_check_error("glBindFramebuffer Geo3D Composite"); + glUseProgram(r_gl_state->geo3d_composite_shader.program_id); + r_gl_check_error("glUseProgram Geo3D Composite"); + glBindVertexArray(r_gl_state->fullscreen_vao); // Use fullscreen quad VAO + r_gl_check_error("glBindVertexArray Geo3D Composite"); + + // Reset viewport and scissor to cover the composite area (usually full window or target rect) + // Use the clip rect from the Geo3D params for compositing area? Or always full stage? Assume full stage. + glViewport(0, 0, resolution.x, resolution.y); // Full window viewport + glScissor(0, 0, resolution.x, resolution.y); + r_gl_check_error("glScissor"); + glDisable(GL_SCISSOR_TEST); // Ensure scissor test is off for fullscreen blit + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + r_gl_check_error("UI Blend Func"); + // Or potentially use GL_ONE, GL_ZERO if result is opaque and overwrites? Assume alpha blend. + // Bind the Geo3D color texture as input + glActiveTexture(GL_TEXTURE0); +r_gl_check_error("UI Active Texture"); + glBindTexture(GL_TEXTURE_2D, gl_window->geo3d_color_texture); + r_gl_check_error("glBindTexture Geo3D Composite Input"); + glBindSampler(0, r_gl_state->samplers[R_Tex2DSampleKind_Nearest]); // Nearest neighbor for composite + r_gl_check_error("glBindSampler Geo3D Composite"); + if (r_gl_state->geo3d_composite_shader.main_texture_uniform_location != -1) { + glUniform1i(r_gl_state->geo3d_composite_shader.main_texture_uniform_location, 0); // Texture unit 0 + } + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // Draw fullscreen quad + r_gl_check_error("Geo3D Composite Draw"); + + // Cleanup composite state + glBindTexture(GL_TEXTURE_2D, 0); + glBindSampler(0, 0); + glUseProgram(0); // Unbind shader + glBindVertexArray(0); // Unbind VAO + }break; + } // end switch pass->kind + } // end pass loop + + //- dan: unbind resources / reset state after all passes + glBindVertexArray(0); + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Bind default framebuffer + glDisable(GL_SCISSOR_TEST); // Disable scissor if default state requires it + + } // end mutex scope +} + +// Debug function to draw a simple test pattern +internal void +r_gl_debug_draw_test_pattern(void) +{ + // Create a simple 2x2 checkerboard texture + GLuint test_texture; + glGenTextures(1, &test_texture); + glBindTexture(GL_TEXTURE_2D, test_texture); + + // Create a black and white checkerboard + unsigned char pattern[] = { + 255, 255, 255, 255, 0, 0, 0, 255, + 0, 0, 0, 255, 255, 255, 255, 255 + }; + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, pattern); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Create a simple fullscreen quad to display it + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + // Draw fullscreen quad + glUseProgram(r_gl_state->finalize_shader.program_id); + glActiveTexture(GL_TEXTURE0); +r_gl_check_error("UI Active Texture"); + glBindTexture(GL_TEXTURE_2D, test_texture); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Clean up + glBindVertexArray(0); + glUseProgram(0); + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &test_texture); + glDeleteVertexArrays(1, &vao); +} + +#define r_gl_check_error(op) r_gl_check_error_line_file(op, __LINE__, __FILE__) + +// Add this function to help debug context issues +internal void +debug_log_current_context(void) +{ + // IMPORTANT: Ensure a valid context exists before calling GL functions. + if(!r_gl_state || !r_gl_state->has_valid_context) + { + fprintf(stderr, "Debug Log: No valid GL context detected by r_gl_state->has_valid_context.\n"); + return; + } + + // Use the correct display handle stored in the state + Display *display = r_gl_state->display; + if (!display) { + fprintf(stderr, "Debug Log: No X11 display found in r_gl_state.\n"); + return; + } + + GLXContext current = glXGetCurrentContext(); + fprintf(stderr, "Debug Log: Current GLX context: %p (Display: %p, Window: %lu)\n", + current, glXGetCurrentDisplay(), (unsigned long)glXGetCurrentDrawable()); + + // Check OpenGL version (can help identify context switches) + const char* version = (const char*)glGetString(GL_VERSION); + fprintf(stderr, "Debug Log: Current OpenGL version: %s\n", version ? version : "NULL"); + + // Check for any pending GL errors + GLenum err; + while((err = glGetError()) != GL_NO_ERROR) { + fprintf(stderr, "Debug Log: GL error AFTER previous operation: 0x%x\n", err); + } +} + +// Add to your texture allocation system +internal void +r_debug_texture_info(GLuint texture_id) +{ + if (!glIsTexture(texture_id)) + { + fprintf(stderr, "Texture %u is not a valid texture object\n", texture_id); + return; + } + + GLint current_texture_binding = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, ¤t_texture_binding); + + GLint width = 0, height = 0, internal_format = 0; + glBindTexture(GL_TEXTURE_2D, texture_id); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format); + + fprintf(stderr, "Texture %u: %dx%d, internal format: 0x%x\n", + texture_id, width, height, internal_format); + + // Restore previous binding + glBindTexture(GL_TEXTURE_2D, current_texture_binding); +} + diff --git a/src/render/opengl/render_opengl.h b/src/render/opengl/render_opengl.h new file mode 100644 index 000000000..b50856743 --- /dev/null +++ b/src/render/opengl/render_opengl.h @@ -0,0 +1,290 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef RENDER_OPENGL_H +#define RENDER_OPENGL_H + +//////////////////////////////// +//~ dan: Generated Code + +#include "generated/render_opengl.meta.h" + + +// Need R_GL_UniformTypeKind enum if not already included via render_core.h +// Assuming render_core.h includes generated/render.meta.h which defines R_PassKind etc. +// We will need a similar generation step for OpenGL specific types like UniformTypeKind if separate +// For now, let's manually define the enum count based on d3d11's mdesk. + +// Uniform Binding Points +#define R_GL_UNIFORM_BINDING_POINT_RECT 0 +#define R_GL_UNIFORM_BINDING_POINT_BLUR 1 +#define R_GL_UNIFORM_BINDING_POINT_MESH 2 +// ... add more if needed + +////////////////////////////////// +//~ dan: C-side Shader Uniform Structures (Matching GLSL std140 layout) + +// NOTE(Memory Layout): GLSL std140 layout rules: +// - Scalars (float, int, bool): aligned to 4 bytes. +// - vec2: aligned to 8 bytes. +// - vec3, vec4: aligned to 16 bytes. +// - mat3: column 0 aligned to 16, col 1 aligned to 16, col 2 aligned to 16 (treated as 3 vec4s essentially for alignment). +// - mat4: column 0 aligned to 16, col 1 aligned to 16, col 2 aligned to 16, col 3 aligned to 16. +// - Arrays: Each element aligned according to its type (as above), potentially padded up to vec4 alignment. +// - Structs: Aligned to the largest alignment of its members, potentially padded up to a multiple of 16 bytes. + +// GLSL vec2 aligns to 8 bytes +// GLSL vec3/vec4 align to 16 bytes +// GLSL mat3 aligns as 3 vec4 columns (total 48 bytes) +// GLSL mat4 aligns as 4 vec4 columns (total 64 bytes) +// GLSL struct aligned to largest member alignment (often 16 bytes) + +// Matches GLSL uniform block "R_GL_Uniforms_Rect" +typedef struct R_GL_Uniforms_Rect R_GL_Uniforms_Rect; +struct R_GL_Uniforms_Rect +{ + // Aligned(8) + Vec2F32 viewport_size_px; // offset 0, size 8 + // Aligned(4), needs padding after + F32 opacity; // offset 8, size 4 + // --- Explicit std140 Padding --- + F32 _padding0; // offset 12, size 4 (Ensure alignment for mat4) + // Aligned(16) per column -> Mat4x4F32 is 4*16 = 64 bytes + Vec4F32 texture_sample_channel_map[4]; // offset 16, size 64 + // Aligned(8) + Vec2F32 texture_t2d_size_px; // offset 80, size 8 + // --- Explicit std140 Padding --- + Vec2F32 _padding1; // offset 88, size 8 (Ensure alignment for mat3) + // Aligned(16) per column -> Mat3x3F32 needs 3*16 = 48 bytes + Vec4F32 xform[3]; // offset 96, size 48 + // Aligned(8) + Vec2F32 xform_scale; // offset 144, size 8 + // --- Explicit std140 Padding --- + Vec2F32 _padding2; // offset 152, size 8 (Ensure total size is multiple of 16, MUST be Vec2F32) +}; // Total size: 160 bytes +StaticAssert(sizeof(R_GL_Uniforms_Rect) == 160, R_GL_Uniforms_Rect_SizeCheck); + +// Matches GLSL uniform block "R_GL_Uniforms_Blur" +typedef struct R_GL_Uniforms_Blur R_GL_Uniforms_Blur; +struct R_GL_Uniforms_Blur +{ + // Aligned(16) + Vec4F32 rect; // offset 0 + // Aligned(16) + Vec4F32 corner_radii_px; // offset 16 + // Aligned(8) + Vec2F32 direction; // offset 32 + // Aligned(8) + Vec2F32 viewport_size; // offset 40 + // Aligned(4) + U32 blur_count; // offset 48 (Using U32 as GLSL bool might be int) + // --- Explicit std140 Padding --- + U32 _padding0[3]; // offset 52, size 12 (Ensure kernel starts at offset 64) + // Aligned(16) per element + Vec4F32 kernel[32]; // offset 64 (Array stride is 16) +}; // Total size: 64 + 32 * 16 = 576 bytes +StaticAssert(sizeof(R_GL_Uniforms_Blur) == 576, R_GL_Uniforms_Blur_SizeCheck); + +// Matches GLSL uniform block "MeshUniforms" +// Name MUST match the GLSL block name for UBO binding to work. +typedef struct R_GL_Uniforms_Mesh R_GL_Uniforms_Mesh; +struct R_GL_Uniforms_Mesh +{ + // IMPORTANT: Remember layout(std140) rules for alignment/padding in GLSL. + // Mat4x4F32 is typically 4x vec4. + Mat4x4F32 view_proj_matrix; // Combined view * projection matrix, transposed for GLSL. + // Instance transform is now passed via vertex attributes. +}; + +//////////////////////////////// +//~ dan: Main State Types + +typedef struct R_GL_Tex2D R_GL_Tex2D; +struct R_GL_Tex2D +{ + R_GL_Tex2D *next; + U64 generation; + GLuint texture_id; + R_ResourceKind kind; + Vec2S32 size; + R_Tex2DFormat format; +}; + +typedef struct R_GL_Buffer R_GL_Buffer; +struct R_GL_Buffer +{ + R_GL_Buffer *next; + U64 generation; + GLuint buffer_id; + R_ResourceKind kind; + U64 size; + GLenum target; +}; + +// Added helper structs for delayed deletion +typedef struct R_GL_Framebuffer R_GL_Framebuffer; +struct R_GL_Framebuffer +{ + R_GL_Framebuffer *next; + GLuint fbo_id; +}; + +typedef struct R_GL_Renderbuffer R_GL_Renderbuffer; +struct R_GL_Renderbuffer +{ + R_GL_Renderbuffer *next; + GLuint rbo_id; +}; + +typedef struct R_GL_Window R_GL_Window; +struct R_GL_Window +{ + R_GL_Window *next; + U64 generation; + + // GL context related (might be managed by OS layer) + // void *gl_context; // Example + + // Framebuffers (FBOs) & Attachments - Need cleanup on unequip/resize + GLuint stage_fbo; + GLuint stage_color_texture; // Texture used as color attachment + GLuint stage_depth_texture; // Using texture for depth + + GLuint stage_scratch_fbo; + GLuint stage_scratch_color_texture; + + GLuint geo3d_fbo; + GLuint geo3d_color_texture; + GLuint geo3d_depth_texture; + + // Last known state for resize detection + Vec2S32 last_resolution; +}; + +typedef struct R_GL_ShaderProgram R_GL_ShaderProgram; +struct R_GL_ShaderProgram +{ + GLuint program_id; + GLuint vertex_shader; + GLuint fragment_shader; + GLint main_texture_uniform_location; // Added for sampler uniforms + // TODO(graphics): Add Geometry, Compute shader handles if needed +}; + +// Added: Structure to hold information for deferred texture allocations +typedef struct R_GL_DeferredTex2D R_GL_DeferredTex2D; +struct R_GL_DeferredTex2D +{ + R_GL_DeferredTex2D *next; + R_ResourceKind kind; + Vec2S32 size; + R_Tex2DFormat format; + void *data; // Will need to copy this data + U64 data_size; // Track data size for allocation + R_Handle result; // Handle to return to caller early + R_GL_Tex2D *texture; // Pre-allocated texture object +}; + +typedef struct R_GL_State R_GL_State; +struct R_GL_State +{ + Arena *arena; + OS_Handle os_window_handle; // Added: Store the OS handle for context management + Display *display; // Added for Linux: store the display pointer + OS_Handle device_rw_mutex; + + // dan: resources + R_GL_Window *first_free_window; + R_GL_Tex2D *first_free_tex2d; + R_GL_Buffer *first_free_buffer; + R_GL_Framebuffer *first_free_framebuffer; // Added free list + R_GL_Renderbuffer *first_free_renderbuffer; // Added free list + + // Queued Deletions (Processed in r_begin_frame) + R_GL_Tex2D *first_to_free_tex2d; + R_GL_Buffer *first_to_free_buffer; + R_GL_Framebuffer *first_to_free_framebuffer; // Added deletion queue + R_GL_Renderbuffer *first_to_free_renderbuffer; // Added deletion queue + // Note: Window FBO attachment textures are deleted directly in resize/unequip queue logic + + Arena *buffer_flush_arena; + R_Handle backup_texture; + + // Added: Queue for deferred texture allocations + R_GL_DeferredTex2D *first_deferred_tex2d; + R_GL_DeferredTex2D *last_deferred_tex2d; + Arena *deferred_tex2d_arena; + B32 has_valid_context; // Flag to track if we have a valid OpenGL context + + // dan: pipeline state object cache + GLuint rect_vao; + GLuint mesh_vao; + GLuint fullscreen_vao; // Added: VAO for fullscreen passes + R_GL_ShaderProgram rect_shader; + R_GL_ShaderProgram blur_shader; + R_GL_ShaderProgram mesh_shader; + R_GL_ShaderProgram geo3d_composite_shader; + R_GL_ShaderProgram finalize_shader; + GLuint samplers[R_Tex2DSampleKind_COUNT]; + GLuint uniform_type_kind_buffers[R_GL_UniformTypeKind_COUNT]; + U64 uniform_type_kind_sizes[R_GL_UniformTypeKind_COUNT]; // Added: store UBO sizes + + GLuint instance_vbo; // Shared VBO for instance data + + B32 glew_initialized; // Added: Track GLEW initialization separately + B32 global_resources_initialized; // Renamed from opengl_extensions_initialized for clarity + B32 create_context_arb_available; // Added: Flag for GLX context creation extension +}; + +//////////////////////////////// +//~ dan: Globals + +global R_GL_State *r_gl_state = 0; +// Define NIL handles robustly to avoid accidental use of address +global R_GL_Window r_gl_window_nil = { .next = &r_gl_window_nil }; +global R_GL_Tex2D r_gl_tex2d_nil = { .next = &r_gl_tex2d_nil }; +global R_GL_Buffer r_gl_buffer_nil = { .next = &r_gl_buffer_nil }; + +//////////////////////////////// +//~ dan: Helpers + +// Maps R_GeoTopologyKind to GLenum primitive type +internal GLenum r_gl_primitive_from_topology(R_GeoTopologyKind kind); + +internal R_GL_Window *r_gl_window_from_handle(R_Handle handle); +internal R_Handle r_gl_handle_from_window(R_GL_Window *window); +internal R_GL_Tex2D *r_gl_tex2d_from_handle(R_Handle handle); +internal R_Handle r_gl_handle_from_tex2d(R_GL_Tex2D *texture); +internal R_GL_Buffer *r_gl_buffer_from_handle(R_Handle handle); +internal R_Handle r_gl_handle_from_buffer(R_GL_Buffer *buffer); +internal GLenum r_gl_usage_from_resource_kind(R_ResourceKind kind); +internal GLenum r_gl_format_from_tex2d_format(R_Tex2DFormat format); +internal GLint r_gl_internal_format_from_tex2d_format(R_Tex2DFormat format); +internal GLenum r_gl_type_from_tex2d_format(R_Tex2DFormat format); // Added +internal GLint r_gl_filter_from_sample_kind(R_Tex2DSampleKind kind); +internal void r_gl_check_error_line_file(const char *op, int line, char *file); // Added prototype +internal void r_gl_make_context_current(R_GL_Window *window); // Add this to fix declaration error +internal void r_gl_delete_window_fbo_resources(R_GL_Window *gl_window); // Add this too +internal void r_gl_debug_draw_test_pattern(void); // Add debug test pattern function +#define r_gl_check_error(op) r_gl_check_error_line_file(op, __LINE__, __FILE__) // Added define + +// Buffer Update Helpers +internal void r_gl_buffer_update_sub_data(R_Handle handle, U64 offset, U64 size, void *data); +internal void* r_gl_buffer_map_range(R_Handle handle, U64 offset, U64 size, GLbitfield access_flags); +internal B32 r_gl_buffer_unmap(R_Handle handle); + +// One-Time Initialization Helpers (Split) +internal void r_gl_init_glew_if_needed(void); +internal void r_gl_create_global_resources(void); +// Leave this function for backward compatibility +internal void r_gl_init_extensions_if_needed(void); + +// Add new function declarations +internal void r_gl_process_deferred_tex2d_queue(void); +internal void r_gl_set_has_valid_context(B32 has_context); + +// Debugging Helpers +internal void debug_log_current_context(void); // Add this +internal void r_debug_texture_info(GLuint texture_id); // Add declaration + +#endif // RENDER_OPENGL_H \ No newline at end of file diff --git a/src/render/opengl/render_opengl.mdesk b/src/render/opengl/render_opengl.mdesk new file mode 100644 index 000000000..c55f16b1e --- /dev/null +++ b/src/render/opengl/render_opengl.mdesk @@ -0,0 +1,529 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ dan: Pipeline Tables + +// NOTE: Input layouts are handled by VAO setup in GL, not directly tied to shaders like D3D11 ILAY. +// We'll keep the table structure similar but omit ILAY tables for now. +@table(name, vs_source, fs_source) +R_GL_ShaderPairTable: +{ + {Rect r_gl_g_rect_shader_vs_src r_gl_g_rect_shader_fs_src } + {Blur r_gl_g_blur_shader_vs_src r_gl_g_blur_shader_fs_src } + {Mesh r_gl_g_mesh_shader_vs_src r_gl_g_mesh_shader_fs_src } + // NOTE: Geo3DComposite and Finalize use a generic VS in the C code, embed FS only? Or embed the simple VS too? + // For now, let's assume a generic VS might be defined elsewhere or inline. + {Geo3DComposite r_gl_g_finalize_shader_vs_src r_gl_g_geo3dcomposite_shader_fs_src } // Using finalize VS for composite + {Finalize r_gl_g_finalize_shader_vs_src r_gl_g_finalize_shader_fs_src } +} + +// Separate tables for VS/FS might still be useful if some shaders are reused. +@table(name, source) +R_GL_VShadTable: +{ + {Rect r_gl_g_rect_shader_vs_src} + {Blur r_gl_g_blur_shader_vs_src} + {Mesh r_gl_g_mesh_shader_vs_src} + {FullscreenQuad r_gl_g_finalize_shader_vs_src} // Generic fullscreen VS used by Composite/Finalize +} + +@table(name, source) +R_GL_PShadTable: +{ + {Rect r_gl_g_rect_shader_fs_src} + {Blur r_gl_g_blur_shader_fs_src} + {Mesh r_gl_g_mesh_shader_fs_src} + {Geo3DComposite r_gl_g_geo3dcomposite_shader_fs_src} + {Finalize r_gl_g_finalize_shader_fs_src} +} + +@table(name) +R_GL_UniformTypeTable: +{ + {Rect} + {Blur} + {Mesh} +} + +//////////////////////////////// +//~ dan: UI Rectangle Shaders + +@embed_string r_gl_g_rect_shader_vs_src: +""" +#version 330 core +// Instance inputs matching VAO setup (8 vec4s) +layout(location = 0) in vec4 dst_rect_px_in; // {x0, y0, x1, y1} +layout(location = 1) in vec4 src_rect_px_in; // {x0, y0, x1, y1} +layout(location = 2) in vec4 color00_in; // {r, g, b, a} BL +layout(location = 3) in vec4 color01_in; // {r, g, b, a} TL +layout(location = 4) in vec4 color10_in; // {r, g, b, a} BR +layout(location = 5) in vec4 color11_in; // {r, g, b, a} TR +layout(location = 6) in vec4 corner_radii_px_in; // {bl, tl, br, tr} - CHECK ORDER vs D3D11 +layout(location = 7) in vec4 style_params_in; // {border, soft, omit_tex, unused} + +// Uniforms (Unchanged) +layout (std140) uniform R_GL_Uniforms_Rect { // Binding Point 0 + vec2 viewport_size_px; + float opacity; + mat4 texture_sample_channel_map; + vec2 texture_t2d_size_px; + mat3 xform; // Column-major + vec2 xform_scale; +}; + +// Outputs to Fragment Shader (Matches D3D11 rect shader outputs) +out VS_OUT { + vec2 rect_half_size_px; + vec2 texcoord_pct; + vec2 sdf_sample_pos; + vec4 tint; + float corner_radius_px; + flat float border_thickness_px; + flat float softness_px; + flat float omit_texture; +} vs_out; + +void main() { + // Unpack instance data + vec2 dst_p0_px = dst_rect_px_in.xy; + vec2 dst_p1_px = dst_rect_px_in.zw; + vec2 src_p0_px = src_rect_px_in.xy; + vec2 src_p1_px = src_rect_px_in.zw; + vec2 dst_size_px = abs(dst_p1_px - dst_p0_px); + + float border_thickness_px = style_params_in.x; + float softness_px = style_params_in.y; + float omit_texture = style_params_in.z; + + // Generate standard CCW triangle strip quad vertex percentages (0,0), (1,0), (0,1), (1,1) + // gl_VertexID 0 -> (0,0) -> BL + // gl_VertexID 1 -> (1,0) -> BR + // gl_VertexID 2 -> (0,1) -> TL + // gl_VertexID 3 -> (1,1) -> TR + vec2 vertex_pct = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1)); + + // Map vertex percentage to destination rect coordinates + vec2 current_dst_pos_px = mix(dst_p0_px, dst_p1_px, vertex_pct); + + // Calculate output position + vec3 xformed_pos = xform * vec3(current_dst_pos_px, 1.0); + vec2 ndc_pos = (xformed_pos.xy / viewport_size_px) * 2.0 - 1.0; + ndc_pos.y = -ndc_pos.y; // Flip Y for GL NDC + gl_Position = vec4(ndc_pos, 0.0, 1.0); + + // Map vertex percentage to source rect coordinates for texture coords + vec2 current_src_pos_px = mix(src_p0_px, src_p1_px, vertex_pct); + vs_out.texcoord_pct = current_src_pos_px / texture_t2d_size_px; + + // Select color based on vertex ID (matching D3D11 logic for vertex IDs 0..3 -> TL, BL, TR, BR) + vec4 color_bl = color00_in; // Location 2 + vec4 color_br = color10_in; // Location 4 + vec4 color_tl = color01_in; // Location 3 + vec4 color_tr = color11_in; // Location 5 + vec4 colors[4] = vec4[4](color_bl, color_br, color_tl, color_tr); // GLSL array constructor syntax + vs_out.tint = colors[gl_VertexID]; + + // Select corner radius based on vertex ID (matching D3D11 logic) + // GL Vertex IDs 0, 1, 2, 3 correspond to BL, BR, TL, TR based on vertex_pct calc + // Input corner_radii_px_in (loc 6) is {bl, tl, br, tr} -> {x, y, z, w} + // D3D11 mapping (see thought process): ID 0 (BL)->x, ID 1 (BR)->z, ID 2 (TL)->y, ID 3 (TR)->w + float radii[4] = float[4](corner_radii_px_in.x, corner_radii_px_in.z, corner_radii_px_in.y, corner_radii_px_in.w); // GLSL array constructor syntax + vs_out.corner_radius_px = radii[gl_VertexID]; + + // Calculate SDF sample position based on vertex percentage relative to center + vs_out.rect_half_size_px = dst_size_px / 2.0 * xform_scale; + vs_out.sdf_sample_pos = (2.0 * vertex_pct - 1.0) * vs_out.rect_half_size_px; + + // Pass through flat values + vs_out.border_thickness_px = border_thickness_px; + vs_out.softness_px = softness_px; + vs_out.omit_texture = omit_texture; +} +""" +@embed_string r_gl_g_rect_shader_fs_src: +""" +#version 330 core +in VS_OUT { + vec2 rect_half_size_px; + vec2 texcoord_pct; + vec2 sdf_sample_pos; + vec4 tint; + float corner_radius_px; + flat float border_thickness_px; + flat float softness_px; + flat float omit_texture; +} fs_in; + +layout (std140) uniform R_GL_Uniforms_Rect // Binding Point 0 +{ + vec2 viewport_size_px; + float opacity; + // float _padding0_; + mat4 texture_sample_channel_map; + vec2 texture_t2d_size_px; + // vec2 translate; + mat3 xform; + vec2 xform_scale; + // vec2 _padding1_; +}; + +uniform sampler2D main_t2d; // Texture Unit 0 + +out vec4 FragColor; + +// Corrected SDF function +float rect_sdf(vec2 sample_pos, vec2 rect_half_size, float r) +{ + vec2 d = abs(sample_pos) - rect_half_size + r; + return length(max(d, 0.0)) - r; +} + +void main() +{ + // Tint is already interpolated + vec4 tint = fs_in.tint; + + // Sample texture + vec4 albedo_sample = vec4(1.0); + if(fs_in.omit_texture < 0.5) // Use < 0.5 for bool comparison + { + // REMOVED explicit V flip added previously. Use coords directly from VS. + albedo_sample = texture(main_t2d, fs_in.texcoord_pct) * texture_sample_channel_map; + } + + // Determine SDF sample position (already interpolated) + vec2 sdf_sample_pos = fs_in.sdf_sample_pos; // Keep for potential future use + + // Sample for borders + float border_sdf_t = 1.0; // Default to 1 (no border effect) + float softness_px = max(fs_in.softness_px, 0.01); // Ensure softness is positive + float border_thickness_px = fs_in.border_thickness_px; + if(border_thickness_px > 0.0) + { + // Adjust half size by softness before calculating SDF, matching D3D11 + vec2 inner_half_size = fs_in.rect_half_size_px - vec2(softness_px * 2.0) - border_thickness_px; + float inner_radius = max(fs_in.corner_radius_px - border_thickness_px, 0.0); + float border_sdf_s = rect_sdf(sdf_sample_pos, inner_half_size, inner_radius); + + float border_smooth_range = max(2.0 * softness_px, 1.0); + // Calculate coverage *outside* the inner edge (0 inside fill, ramps to 1 outside) + border_sdf_t = smoothstep(0.0, border_smooth_range, border_sdf_s); + } + + // Sample for corners (outer edge) + float corner_sdf_t = 1.0; // Default to 1 (no corner effect) + // Check radius OR softness > 0.75 (D3D11 logic) + if(fs_in.corner_radius_px > 0.0 || softness_px > 0.75) + { + // Adjust half size by softness before calculating SDF, matching D3D11 + vec2 outer_half_size = fs_in.rect_half_size_px - vec2(softness_px * 2.0); + float outer_radius = max(fs_in.corner_radius_px, 0.0); + float corner_sdf_s = rect_sdf(sdf_sample_pos, outer_half_size, outer_radius); + + float corner_smooth_range = max(2.0 * softness_px, 1.0); + // Calculate coverage *inside* the outer edge (1 inside, ramps to 0 outside) + corner_sdf_t = 1.0 - smoothstep(0.0, corner_smooth_range, corner_sdf_s); + } + + // Form final color - Multiply tint and texture colors, combine alphas + FragColor.rgb = tint.rgb * albedo_sample.rgb; // Multiply RGB components + FragColor.a = tint.a * albedo_sample.a; // Multiply Alpha components + + // Apply global opacity and SDF alpha factors + FragColor.a *= opacity; + FragColor.a *= corner_sdf_t; // Multiply by corner coverage + FragColor.a *= border_sdf_t; // Multiply by border coverage + + // Final discard if alpha is near zero + if (FragColor.a < 0.001) + { + discard; + } +} +""" + +//////////////////////////////// +//~ dan: Blur Shaders + +@embed_string r_gl_g_blur_shader_vs_src: +""" +#version 330 core +// Define uniforms matching the FS for rect/corners +layout (std140) uniform BlurUniforms // Binding Point 1 +{ + vec4 rect; // vec4(min.x, min.y, max.x, max.y) + vec4 corner_radii_px; // vec4(bl, tl, br, tr) + vec2 direction; // vec2(dx, dy) unit vector for blur pass + vec2 viewport_size; // vec2(width, height) of render target + uint blur_count; // Number of samples (adjusted for bilinear) + // padding... + vec4 kernel[32]; // vec4(weight, offset, unused, unused) +}; + +// Define output block to pass SDF data to FS +out VS_OUT { + vec2 sdf_sample_pos; + flat vec2 rect_half_size; + float corner_radius; +} vs_out; + +out vec2 TexCoord; + +void main() +{ + // Fullscreen quad vertices (-1,-1), (1,-1), (-1, 1), (1, 1) using triangle strip + // VertexID: 0 -> (-1,-1), 1 -> (1,-1), 2 -> (-1, 1), 3 -> (1, 1) + vec2 pos = vec2( (gl_VertexID << 1) - 1.0, (gl_VertexID & 1) * 2.0 - 1.0 ); + gl_Position = vec4(pos, 0.0, 1.0); + + // Texcoord (0,0) bottom-left to (1,1) top-right + TexCoord = pos * 0.5 + 0.5; + // TexCoord.y = 1.0 - TexCoord.y; // Y is already correct for fullscreen quad + + // Calculate SDF inputs + vs_out.rect_half_size = (rect.zw - rect.xy) * 0.5; + + // Determine cornercoords based on vertex ID (0: BL, 1: BR, 2: TL, 3: TR) + vec2 cornercoords_pct = vec2(float(gl_VertexID & 1), float((gl_VertexID >> 1) & 1)); // Renamed from cornercoords__pct + vs_out.sdf_sample_pos = (2.0 * cornercoords_pct - 1.0) * vs_out.rect_half_size; + + // Select corner radius based on vertex ID + // ID 0 (BL) -> radii.x + // ID 1 (BR) -> radii.z + // ID 2 (TL) -> radii.y + // ID 3 (TR) -> radii.w + if(gl_VertexID == 0) vs_out.corner_radius = corner_radii_px.x; + else if(gl_VertexID == 1) vs_out.corner_radius = corner_radii_px.z; + else if(gl_VertexID == 2) vs_out.corner_radius = corner_radii_px.y; + else /* ID 3 */ vs_out.corner_radius = corner_radii_px.w; + + // D3D11 vertex shader passes rect_half_size - 2.0. Match this. + vs_out.rect_half_size -= 2.0; +} +""" + +@embed_string r_gl_g_blur_shader_fs_src: +""" +#version 330 core +in vec2 TexCoord; +// Added input block matching the VS structure (even if VS isn't updated yet) +in VS_OUT { + vec2 sdf_sample_pos; + flat vec2 rect_half_size; + float corner_radius; +} fs_in; +out vec4 FragColor; + +uniform sampler2D main_t2d; // Texture Unit 0 + +layout (std140) uniform BlurUniforms // Binding Point 1 +{ + vec4 rect; // vec4(min.x, min.y, max.x, max.y) + vec4 corner_radii_px; // vec4(bl, tl, br, tr) + vec2 direction; // vec2(dx, dy) unit vector for blur pass + vec2 viewport_size; // vec2(width, height) of render target + uint blur_count; // Number of samples (adjusted for bilinear) + // padding... + vec4 kernel[32]; // vec4(weight, offset, unused, unused) +}; + +// SDF for rounded rectangle +float rect_sdf(vec2 sample_pos, vec2 rect_half_size, float r) +{ + vec2 d = abs(sample_pos) - rect_half_size + r; + return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r; +} + +void main() +{ + vec4 sum = vec4(0.0); + vec2 uv = TexCoord; + vec2 texelSize = 1.0 / viewport_size; + + // Apply first weight (at offset 0) + sum += texture(main_t2d, uv) * kernel[0].x; + + // Apply remaining weights using bilinear optimization + for(uint i = 1u; i < blur_count; ++i) + { + vec2 offset = direction * kernel[i].y * texelSize; + sum += texture(main_t2d, uv + offset) * kernel[i].x; + sum += texture(main_t2d, uv - offset) * kernel[i].x; + } + + // Clip to rounded rectangle using SDF + float corner_sdf_s = rect_sdf(fs_in.sdf_sample_pos, fs_in.rect_half_size, fs_in.corner_radius); + // D3D11 uses smoothstep(0, 2*softness, ...), let's use a small fixed softness for now + float softness = 1.0; + float corner_sdf_t = 1.0 - smoothstep(-softness, softness, corner_sdf_s); + + // Discard fragments outside the rounded rectangle (similar to D3D11) + if (corner_sdf_t < 0.001) // Use a small threshold instead of 0.9 check + { + discard; + } + + FragColor = vec4(sum.rgb, 1.0); // Output color, force alpha to 1 + + // Optional: Apply coverage to alpha if needed later + // FragColor.a = corner_sdf_t; +} +""" + +//////////////////////////////// +//~ dan: Mesh Shaders + +@embed_string r_gl_g_mesh_shader_vs_src: +""" +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoord; +layout (location = 3) in vec3 aColor; +layout (location = 4) in mat4 instance_xform; // Per-instance transform + +layout (std140) uniform R_GL_Uniforms_Mesh { // Use correct uniform block name matching C struct + mat4 view_proj_matrix; // Combined view * projection +}; + +out vec3 FragPos; +out vec3 Normal; +out vec2 TexCoords; +out vec3 VertexColor; + +void main() +{ + vec4 worldPos = instance_xform * vec4(aPos, 1.0); + gl_Position = view_proj_matrix * worldPos; + FragPos = vec3(worldPos); + Normal = mat3(transpose(inverse(instance_xform))) * aNormal; // Correct normal transformation + TexCoords = aTexCoord; + VertexColor = aColor; +} +""" + +@embed_string r_gl_g_mesh_shader_fs_src: +""" +#version 330 core +out vec4 FragColor; + +in vec3 FragPos; +in vec3 Normal; +in vec2 TexCoords; +in vec3 VertexColor; + +uniform sampler2D main_t2d; // Texture Unit 0 + +void main() +{ + // Simple: Output vertex color modulated by texture (if available) + // vec4 texColor = texture(main_t2d, TexCoords); + FragColor = vec4(VertexColor, 1.0); // * texColor; // Modulate later if needed + // Add lighting calculations here if needed +} +""" + +//////////////////////////////// +//~ dan: Geo3D Composition Shaders + +// NOTE: Geo3D Composite uses the same fullscreen VS as Finalize in the C code. +// We will embed the FS here and reference the Finalize VS in the tables. +@embed_string r_gl_g_geo3dcomposite_shader_fs_src: +""" +#version 330 core +in vec2 TexCoord; +uniform sampler2D main_t2d; // Renamed from stage_t2d +out vec4 FragColor; +void main() +{ + FragColor = texture(main_t2d, TexCoord); // Use renamed uniform +} +""" + +//////////////////////////////// +//~ dan: Finalize Shaders + +@embed_string r_gl_g_finalize_shader_vs_src: +""" +#version 330 core +out vec2 TexCoord; +void main() +{ + // Correct way for (-1,-1), (1,-1), (-1, 1), (1, 1) triangle strip + vec2 pos = vec2( (gl_VertexID & 1) * 2.0 - 1.0, (gl_VertexID & 2) - 1.0 ); + gl_Position = vec4(pos, 0.0, 1.0); + // Texcoord (0,0) bottom-left to (1,1) top-right + TexCoord = pos * 0.5 + 0.5; + // TexCoord.y = 1.0 - TexCoord.y; // Flip V if input texture requires it +} +""" + +@embed_string r_gl_g_finalize_shader_fs_src: +""" +#version 330 core +in vec2 TexCoord; +uniform sampler2D main_t2d; // Renamed from stage_t2d +out vec4 FragColor; +void main() +{ + FragColor = texture(main_t2d, TexCoord); // Use renamed uniform + FragColor.a = 1.0; // Force alpha to 1 for final output +} +""" + +//////////////////////////////// +//~ dan: Table Generators + +// NOTE: Input layouts (VAOs) are configured separately in OpenGL C code. +// The D3D11 `ilay_table` concept doesn't directly map here. +// We'll generate tables for shader sources only. + +@enum R_GL_VShadKind: +{ + @expand(R_GL_VShadTable a) `$(a.name)`, + COUNT, +} + +@enum R_GL_PShadKind: +{ + @expand(R_GL_PShadTable a) `$(a.name)`, + COUNT, +} + +@enum R_GL_UniformTypeKind: +{ + @expand(R_GL_UniformTypeTable a) `$(a.name)`, + COUNT, +} + +@c_file @data(`String8 *`) // Pointers to String8 global variables +r_gl_g_vshad_kind_source_ptr_table: +{ + @expand(R_GL_VShadTable a) `&$(a.source)`; +} + +@c_file @data(String8) +r_gl_g_vshad_kind_source_name_table: +{ + @expand(R_GL_VShadTable a) `str8_lit_comp("$(a.source)")`; +} + +@c_file @data(`String8 *`) // Pointers to String8 global variables +r_gl_g_pshad_kind_source_ptr_table: +{ + @expand(R_GL_PShadTable a) `&$(a.source)`; +} + +@c_file @data(String8) +r_gl_g_pshad_kind_source_name_table: +{ + @expand(R_GL_PShadTable a) `str8_lit_comp("$(a.source)")`; +} + +// Generate size table based on C struct definitions (defined in render_opengl.h) +@c_file @data(U64) +r_gl_g_uniform_type_kind_size_table: +{ + @expand(R_GL_UniformTypeTable a) `sizeof(R_GL_Uniforms_$(a.name))`; +} \ No newline at end of file diff --git a/src/render/render_inc.c b/src/render/render_inc.c index 84612fa8b..d25efbe2a 100644 --- a/src/render/render_inc.c +++ b/src/render/render_inc.c @@ -7,6 +7,8 @@ # include "stub/render_stub.c" #elif R_BACKEND == R_BACKEND_D3D11 # include "d3d11/render_d3d11.c" +#elif R_BACKEND == R_BACKEND_OPENGL +# include "opengl/render_opengl.c" #else # error Renderer backend not specified. #endif diff --git a/src/render/render_inc.h b/src/render/render_inc.h index 64802e5c5..263db9ad5 100644 --- a/src/render/render_inc.h +++ b/src/render/render_inc.h @@ -9,12 +9,14 @@ #define R_BACKEND_STUB 0 #define R_BACKEND_D3D11 1 - +#define R_BACKEND_OPENGL 2 //////////////////////////////// //~ rjf: Decide On Backend #if !defined(R_BACKEND) && OS_WINDOWS -# define R_BACKEND R_BACKEND_D3D11 +#define R_BACKEND R_BACKEND_D3D11 +#elif !defined(R_BACKEND) && OS_LINUX +#define R_BACKEND R_BACKEND_OPENGL #endif //////////////////////////////// @@ -29,6 +31,8 @@ # include "stub/render_stub.h" #elif R_BACKEND == R_BACKEND_D3D11 # include "d3d11/render_d3d11.h" +#elif R_BACKEND == R_BACKEND_OPENGL +# include "opengl/render_opengl.h" #else # error Renderer backend not specified. #endif