diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..16c450a93 --- /dev/null +++ b/build.sh @@ -0,0 +1,245 @@ +#!/usr/bin/env bash + +# TODO(mallchad): This code is really ugly especially with the stupid && branching tricks. Please fix. + +# --- Usage Notes (2024/1/10) ------------------------------------------------ +# +# This is a central build script for the RAD Debugger project. It takes a list +# of simple alphanumeric-only arguments which control (a) what is built, (b) +# which compiler & linker are used, and (c) extra high-level build options. By +# default, if no options are passed, then the main "raddbg" graphical debugger +# is built. +# +# Below is a non-exhaustive list of possible ways to use the script: +# `build raddbg` +# `build raddbg clang` +# `build raddbg release` +# `build raddbg asan telemetry` +# `build rdi_from_pdb` +# `build gcodeview` +# +# For a full list of possible build targets and their build command lines, +# search for @build_targets in this file. +# +# Below is a list of all possible non-target command line options: +# +# - `asan`: enable address sanitizer +# - `telemetry`: enable RAD telemetry profiling support +# - `gcodeview`: generate codeview symbols instead of DRWARF for clang +# +# Also you can set the environment variable RAD_DEBUG=1 to enable some extra debug output + +# --- Random Init ----------------------------------------------------------- +ansi_red="\x1b[31m" +ansi_cyanbold="\x1b[36;1m" +ansi_reset="\e[0m" +function rad_log () +{ + echo -e "$ansi_cyanbold[Rad Build]$ansi_reset ${@}" +} + +function rad_debug_log () +{ + if [[ -n "${RAD_DEBUG}" ]] ; then + # -e enable escape sequences + rad_log "${@}" + fi +} + +# cd to script directory +self_directory="$(dirname $(readlink -f $0))" +rad_log "cd to '${self_directory}'" +cd "${self_directory}" +RAD_BUILD_DEBUG="${RAD_DEBUG}" +if [[ -n ${RAD_BUILD_DEBUG} ]] ; then + shellcheck $0 +fi +# RAD_META_COMPILE_ONLY=1 + +# --- Unpack Command Line Build Arguments ------------------------------------ +# NOTE: With this setup you can use environment variables or arguments and +# they'll do roughly the same thing. +for x_arg in $@ ; do declare ${x_arg}=1 ; done +if [[ -z "${msvc}" && -z "${clang}" ]] ; then clang=1 ; fi +if [[ -z "${release}" ]] ; then debug=1 ; fi +if [[ -n "${debug}" ]] ; then release= && rad_log "[debug mode]" ; fi +if [[ -n "${release}" ]] ; then debug= && rad_log "[release mode]" ; fi +if [[ -n "${msvc}" ]] ; then clang= && rad_log "[msvc compile]" ; fi +if [[ -n "${clang}" ]] ; then msvc= && rad_log "[clang compile]" ; fi +if [[ -z "$1" ]] ; then echo "[default mode, assuming 'raddbg' build]" && raddbg=1 ; fi +if [[ "$1" == "release" && -z "$2" ]] ; then + rad_log "[default mode, assuming 'raddbg' build]" && raddbg=1 ; +fi +if [[ -n "${msvc}" ]] ; then + clang=0 + rad_log "You're going to have to figure out something else if you want to use \ +anything even remotely MSVC related on Linux. Bailing." + exit 1 +fi + +set auto_compile_flags= +[[ -n "${telemetry}" ]] && auto_compile_flags="${auto_compile_flags} -DPROFILE_TELEMETRY=1" && + rad_log "[telemetry profiling enabled]" +[[ -n "${asan}" ]] && auto_compile_flags="${auto_compile_flags} -fsanitize=address" && + rad_log "[asan enabled]" + +# --- Compile/Link Line Definitions ------------------------------------------ + cl_common="/I../src/ /I../local/ /nologo /FC /Z7" + clang_common="-I../src/ -I../local -I/usr/include/freetype2/ -fdiagnostics-absolute-paths -Wall -Wno-unknown-warning-option -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 -Xclang -flto-visibility-public-std -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf -Wl,-z,notext -std=c11 -D_DEFAULT_SOURCE=1" + clang_dynamic="-lpthread -ldl -lrt -latomic -lm -lfreetype -lEGL -lX11 -lGL -lXrandr -luuid" + clang_errors="-Werror=atomic-memory-ordering -Wno-parentheses" + cl_debug="cl /Od /Ob1 /DBUILD_DEBUG=1 ${cl_common} ${auto_compile_flags}" + cl_release="cl /O2 /DBUILD_DEBUG=0 ${cl_common} ${auto_compile_flags}" + clang_debug="clang -g -O0 -DBUILD_DEBUG=1 ${clang_common} ${clang_dynamic} ${clang_errors} ${auto_compile_flags}" +clang_release="clang -g -O2 -DBUILD_DEBUG=0 ${clang_common} ${clang_dynamic} ${clang_errors} ${auto_compile_flags}" + cl_link="/link /MANIFEST:EMBED /INCREMENTAL:NO \ +/natvis:'${self_directory}/src/natvis/base.natvis' logo.res" + clang_link="-fuse-ld=ld" + cl_out="/out:" + clang_out="-o" + +# --- Per-Build Settings ----------------------------------------------------- +link_dll="-DLL" +[[ -n "${msvc}" ]] && only_compile="/c" +[[ -n "${clang}" ]] && only_compile="-c" +[[ -n "${msvc}" ]] && EHsc="/EHsc" +[[ -n "${clang}" ]] && EHsc="" +[[ -n "${msvc}" ]] && no_aslr="/DYNAMICBASE:NO" +[[ -n "${clang}" ]] && no_aslr="-Wl,/DYNAMICBASE:NO" +[[ -n "${msvc}" ]] && rc="rc" +[[ -n "${clang}" ]] && rc="llvm-rc" +[[ -n "${gcodeview}" ]] && clang_common="${clang_common} -gcodeview" + +# --- Choose Compile/Link Lines ---------------------------------------------- +if [[ -n "${msvc}" ]] ; then compile_debug="${cl_debug}" ; fi +if [[ -n "${msvc}" ]] ; then compile_release="${cl_release}" ; fi +if [[ -n "${msvc}" ]] ; then compile_link="${cl_link}" ; fi +if [[ -n "${msvc}" ]] ; then out="${cl_out}" ; fi +if [[ -n "${clang}" ]] ; then compile_debug="${clang_debug}" ; fi +if [[ -n "${clang}" ]] ; then compile_release="${clang_release}" ; fi +if [[ -n "${clang}" ]] ; then compile_link="${clang_link}" ; fi +if [[ -n "${clang}" ]] ; then out="${clang_out}" ; fi +if [[ -n "${debug}" ]] ; then compile="${compile_debug}" ; fi +if [[ -n "${release}" ]] ; then compile="${compile_release}" ; fi + +# --- Prep Directories ------------------------------------------------------- +# Si +mkdir -p "build" +mkdir -p "local" + +# --- Produce Logo Icon File ------------------------------------------------- +cd build +# TODO(mallchad): "Linux cannot embed icons in exes :( build a .desktop file instead +cd "${self_directory}" + +# --- Get Current Git Commit Id ---------------------------------------------- +# for /f ${}i in ('call git describe --always --dirty') do set compile=${compile} -DBUILD_GIT_HASH=\"${}i\" +# NOTE(mallchad): I don't really understand why this written has a loop. Is it okay without? +compile="${compile} -DBUILD_GIT_HASH=\"$(git describe --always --dirty)\"" + +rad_debug_log "Compile Command: " +rad_debug_log "${compile}" + +# --- Build & Run Metaprogram ------------------------------------------------ +if [[ -n "${no_meta}" ]] ; then + rad_log "[skipping metagen]" +else + cd build + ${compile_debug} "../src/metagen/metagen_main.c" ${compile_link} "${out}metagen.exe" || exit 1 + # Skip if compile only + [[ -z "${RAD_META_COMPILE_ONLY}" ]] && (./metagen.exe || exit 1) + cd ${self_directory} +fi + +# --- Build Everything (@build_targets) -------------------------------------- + +function finish() +{ + rad_log "Some unexpected command failed unexpectedly. ${ansi_red}Line: $1 ${ansi_reset}" + exit 1 +} + +function get_epoch() +{ + echo "$(date +%s)" +} + +# @param $1 - name of file to compile +# @param $2 - name of executable to output as +# @param $@ - rest is any arguments provided +function build_single() +{ + local binary=$2 + rad_log "Building '${binary}'" + didbuild=1 + + build_start=$(get_epoch) + ${compile} "$1" ${@:3:100} ${compile_link} "${out}$2" + status=$? + build_end=$(get_epoch) + time_elapsed=$((build_end - build_start)) + + rad_log "Compilation Time: $ansi_cyanbold \ +$((time_elapsed / 60))min $((time_elapsed % 60))s $ansi_reset" + if [[ "${status}" != 0 ]] ; then + rad_log "A standalone build command didn't complete successfully. \ +${ansi_red}Line: ${BASH_LINENO[$i]} ${ansi_reset}" ; + exit ${status} ; + fi + return ${status} +} + +function build_dll() +{ + local binary=$2 + rad_log "Building '${binary}'" + didbuild=1 + ${compile} "$1" ${compile_link} ${@:3:100} ${link_dll} "${out}$2" + return $? +} + +cd build +[[ -n "${raddbg}" ]] && build_single ../src/raddbg/raddbg_main.c raddbg.exe +[[ -n "${rdi_from_pdb}" ]] && build_single ../src/rdi_from_pdb/rdi_from_pdb_main.c rdi_from_pdb.exe +[[ -n "${rdi_from_dwarf}" ]] && build_single ../src/rdi_from_dwarf/rdi_from_dwarf.c rdi_from_dwarf.exe +[[ -n "${rdi_dump}" ]] && build_single ../src/rdi_dump/rdi_dump_main.c rdi_dump.exe +[[ -n "${rdi_breakpad_from_pdb}" ]] && build_single ../src/rdi_breakpad_from_pdb/rdi_breakpad_from_pdb_main.c rdi_breakpad_from_pdb.exe +[[ -n "${ryan_scratch}" ]] && build_single ../src/scratch/ryan_scratch.c ryan_scratch.exe +[[ -n "${cpp_tests}" ]] && build_single ../src/scratch/i_hate_c_plus_plus.cpp cpp_tests.exe +[[ -n "${look_at_raddbg}" ]] && build_single ../src/scratch/look_at_raddbg.c look_at_raddbg.exe +if [[ -n "${mule_main}" ]] ; then + didbuild=1 + rm -v vc*.pdb mule*.pdb + ${compile_release} ${only_compile} ../src/mule/mule_inline.cpp && + ${compile_release} ${only_compile} ../src/mule/mule_o2.cpp && + ${compile_debug} ${EHsc} ../src/mule/mule_main.cpp ../src/mule/mule_c.c \ + "mule_inline.o" "mule_o2.o" ${compile_link} ${no_aslr} \ + ${out} mule_main.exe || exit 1 +fi + +# Continue building the rest line normal +[[ -n "${mule_module}" ]] && $(build_dll ../src/mule/mule_module.cpp mule_module.dll || finish ${LINENO} ) +[[ -n "${mule_hotload}" ]] && $(build_single ../src/mule/mule_hotload_main.c mule_hotload.exe && + build_dll ../src/mule/mule_hotload_module_main.c mule_hotload_module.dll || finish ${LINENO}) + +if [[ -n "${mule_peb_trample}" ]] ; then + didbuild=1 + [[ -f "mule_peb_trample.exe" ]] && mv "mule_peb_trample.exe" "mule_peb_trample_old_${random}.exe" + [[ -f "mule_peb_trample_new.pdb" ]] && mv "mule_peb_trample_new.pdb" "mule_peb_trample_old_${random}.pdb" + [[ -f "mule_peb_trample_new.rdi" ]] && mv "mule_peb_trample_new.rdi" "mule_peb_trample_old_${random}.rdi" + build_single "../src/mule/mule_peb_trample.c" "mule_peb_trample_new.exe" + mv "mule_peb_trample_new.exe" "mule_peb_trample.exe" +fi +cd "${self_directory}" + +# --- Unset ------------------------------------------------------------------ +# NOTE: Shouldn't need to unset, bash variables are volatile, even environment variables + +# --- Warn On No Builds ------------------------------------------------------ +if [[ -z "${didbuild}" ]] ; then + rad_log "[WARNING] no valid build target specified; must use build target names as arguments to this script, like `build raddbg` or `build rdi_from_pdb`." + exit 1 +else + rad_log "Build Finished!" + exit 0 +fi diff --git a/documentation/bugs.md b/documentation/bugs.md new file mode 100644 index 000000000..2fdae67e3 --- /dev/null +++ b/documentation/bugs.md @@ -0,0 +1,7 @@ +# #1 - codegen bug with clang +For some reason I encountered a spurious bug where clang tries to use aligned +instructions like `movaps` on addresses that are unaligned, but only when you +are unfortunate enough to change the program entry point. As well as just +generally destroying the program stack. Adding the argument `-mrealignstack` +seems to alleviate it somehow, so this should be added if segfaults are seen in +a suspicious location again diff --git a/documentation/build_system.md b/documentation/build_system.md new file mode 100644 index 000000000..c2d7dcf82 --- /dev/null +++ b/documentation/build_system.md @@ -0,0 +1,42 @@ +# Build System +Each of the `build.bat` and `build.sh ` files have a bit of extra documentation +in the header of the file. Please read that for more info. + +# Linux Version +All of the Linux build.sh arguments have been set up in a way where you can +specify it as command line arguments or environment variables. This is by pure +coincidence, but it does work, and it is nice. + +This version has as close to feature parity with the build.bat as possible, even +though a lot of the commands are semi-nonsense unless using something niche like +mingw, but its being left that way as to not make any assumptions for the time +being to allow any major changes to be left to somebody better informed in the +future. One notable change is the removal of the logo embed because +unfortunately Linux does not have an equivilent of that, however in the future +this can be turned into a `.desktop` file. + +## TODO +- [ ] Make a `.desktop` file on build + +## Compiler Flags +* -lpthread | POSIX threading library +* -ldl | dynamic linking library +* -lrt | realtime time library for clock functionality +* -lfreetype | freetype font provider library +* -latomic | GLIBC atomic intrinsics +* -m | Standard Math Library +* -Wno=parenthesis | Checks surprising gotchas with operator precedence, seems + mostly like basic noob trap mistakes, think we can ignore it here. +* -Werror=atomic-memory-ordering | This is probably an actual major bug if it appears. +* -std=c11 | pin version to some oldish C standard to try to increase version support +* -D_DEFAULT_SOURCE=1 provides missing definitions like 'futimes' that vanish when '-std=c11' is used + It's not entirely clear why this works this way but _GNU_SOURCE=1 was providing it previously + +## Linker Flags +* `-Wl,-z,notext` this linker flag was given to allow metagen to relocate data in the read only segment, it gave the option between that and "-fPIC". This is the exact compiler output. +``` +ld.lld: error: can't create dynamic relocation R_X86_64_64 against local symbol in readonly segment; recompile object files with -fPIC or pass '-Wl,-z,notext' to allow text relocations in the output +>>> defined in /tmp/metagen_main-705025.o +>>> referenced by metagen_main.c +>>> /tmp/metagen_main-705025.o:(mg_str_expr_op_symbol_string_table) +``` diff --git a/src/base/base_core.c b/src/base/base_core.c index 2d40fe343..ac4b099af 100644 --- a/src/base/base_core.c +++ b/src/base/base_core.c @@ -560,3 +560,66 @@ ring_read(U8 *ring_base, U64 ring_size, U64 ring_pos, void *dst_data, U64 read_s } return read_size; } +//////////////////////////////// +// Generic Array Functions + +B32 +_ArrayAllocate(GenericArray* target, Arena* arena, U32 size, U32 item_size) +{ + Assert(arena != NULL); // The arena parameter is incorrect + target->arena = arena; + target->size = size; + target->item_size = item_size; + target->data = arena_push(arena, (size * item_size)); + + return 1; +} + +B32 _ArrayZero(GenericArray* target, U64 item_size) +{ + MemoryZero(target->data, (target->size * item_size)); + return 1; +} + +// Candidate for inline +U64 +_ArrayGetTailIndex(GenericArray* target) +{ + return (target->head + target->head_size - 1); +} + +B32 _ArrayGetTail(GenericArray* target, void* out, U32 item_size) +{ + if (target->fast_unbounded && target->head_size <= 0) { return 0; } + U8* tail = target->data; + tail += (target->head + ((target->head_size - 1) * item_size)); + MemoryCopy(out, tail, item_size); + return 1; +} + +void* +_ArrayPushTail(GenericArray* target, void* item, U32 item_size) +{ + if (target->fast_unbounded && (target->head + target->head_size >= target->size) && item) + { return 0; } + U8* new_tail = target->data; + new_tail += (target->head + (target->head_size * item_size)); + + // Return a new object + if (item != NULL) + { MemoryCopy(new_tail, item, item_size); } + ++(target->head_size); + return new_tail; +} + +B32 +_ArrayPopTail(GenericArray* target, void* out, U32 item_size) +{ + if (target->fast_unbounded && (target->head_size <= 0)) + { return 0; } + U8* tail = target->data; + tail += (target->head + ((target->head_size -1) * item_size)); + MemoryCopy(out, tail, item_size); + --(target->head_size); + return 1; +} diff --git a/src/base/base_core.h b/src/base/base_core.h index cb8904bc7..540b18cac 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -136,9 +136,15 @@ //~ rjf: Asserts #if COMPILER_MSVC -# define Trap() __debugbreak() -#elif COMPILER_CLANG || COMPILER_GCC -# define Trap() __builtin_trap() +# define Trap() __debugbreak() +#elif COMPILER_CLANG || COMPILER_GC +// Check support for debugger-continue break +// Alternatively use raise(SIGTRAP) if you wish because its more portable +# if __has_builtin(__builtin_debugtrap) +# define Trap() __builtin_debugtrap() +# else +# define Trap() __builtin_trap() +# endif #else # error Unknown trap intrinsic for this compiler. #endif @@ -178,7 +184,39 @@ # endif #elif OS_LINUX # if ARCH_X64 -# define ins_atomic_u64_inc_eval(x) __sync_fetch_and_add((volatile U64 *)(x), 1) +/* NOTE: Supposedly '__sync' is the old and deprecated intrinsics, '__atomic' is the new one + and '__sync' is implimented with '__atomic' now. + https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html + NOTE: the __atomic built-ins are compatible with any integral/pointer type of + 1, 2, 3, 4, 8 bytes length + NOTE: weak ordering in '__atomic_compare_exchange_n' is mostly ignored by platforms */ +U64 +ins_atomic_u64_eval_cond_assign__impl(volatile U64* x, U64 k, U64 c) +{ + U64 _temp_expected = k; + return __atomic_compare_exchange_n( x, &_temp_expected, c, + 0, __ATOMIC_ACQ_REL, __ATOMIC_RELEASE ); +} + +U32 +ins_atomic_u32_eval_cond_assign__impl(volatile U32* x, U32 k, U32 c) +{ + U32 _temp_expected = k; + return __atomic_compare_exchange_n( x, &_temp_expected, c, + 0, __ATOMIC_ACQ_REL, __ATOMIC_RELEASE ); +} + +// TODO(mallchad): I so do not understand how this is supposed to work. Plz send halp. +#define ins_atomic_u64_eval(x) __atomic_load_n( (volatile U64 *)(x), __ATOMIC_ACQUIRE ) +#define ins_atomic_u64_inc_eval(x) __atomic_add_fetch( (volatile U64 *)(x), 1, __ATOMIC_ACQ_REL ) +#define ins_atomic_u64_dec_eval(x) __atomic_add_fetch( (volatile U64 *)(x), -1, __ATOMIC_ACQ_REL ) +#define ins_atomic_u64_eval_assign(x,c) __atomic_store_n( (volatile U64 *)(x), c, __ATOMIC_RELEASE ) +#define ins_atomic_u64_add_eval(x,c) __atomic_add_fetch( (volatile U64 *)(x), c, __ATOMIC_ACQ_REL ) +#define ins_atomic_u64_eval_cond_assign(x,k,c) ins_atomic_u64_eval_cond_assign__impl(x, k, c) +#define ins_atomic_u32_eval(x,c) __atomic_load_n( (volatile U32 *)(x), __ATOMIC_ACQUIRE ) +#define ins_atomic_u32_eval_assign(x,c) __atomic_store_n( (volatile U32 *)(x), c, __ATOMIC_RELEASE ) +#define ins_atomic_u32_eval_cond_assign(x,k,c) ins_atomic_u32_eval_cond_assign__impl(x, k, c) +#define ins_atomic_ptr_eval_assign(x,c)__atomic_store( (volatile void *)(x), c, __ATOMIC_RELEASE ) # else # error Atomic intrinsics not defined for this operating system / architecture combination. # endif @@ -672,6 +710,7 @@ struct DateTime U32 year; // 1 = 1 CE, 0 = 1 BC }; +/// Time in microseconds typedef U64 DenseTime; //////////////////////////////// @@ -779,4 +818,71 @@ internal U64 ring_read(U8 *ring_base, U64 ring_size, U64 ring_pos, void *dst_dat #define ring_write_struct(ring_base, ring_size, ring_pos, ptr) ring_write((ring_base), (ring_size), (ring_pos), (ptr), sizeof(*(ptr))) #define ring_read_struct(ring_base, ring_size, ring_pos, ptr) ring_read((ring_base), (ring_size), (ring_pos), (ptr), sizeof(*(ptr))) +//////////////////////////////// +// Array Functionality + +// Foward Declare +struct Arena; + +typedef struct GenericArray GenericArray; +struct GenericArray +{ + U8* data; + struct Arena* arena; + U64 size; + U64 head; + U64 head_size; + U32 item_size; + B32 fast_unbounded; +}; + +/// Declare a typed dynamic array class +#define DeclareArray(NAME, ELEMENT_TYPE) \ + typedef struct NAME NAME; \ + struct NAME \ + { \ + ELEMENT_TYPE* data; \ + struct Arena* arena; \ + U64 size; \ + U64 head; \ + U64 head_size; \ + U32 item_size; \ + B32 fast_unbounded; \ + }; + +B32 ArrayAllocate(GenericArray* target, struct Arena* arena, U64 size, U32 item_size); +#define ArrayAllocate(target, arena, size) \ + _ArrayAllocate((GenericArray*)(target), (arena), (size), sizeof((target)->data[0])) + +B32 _ArrayZero(GenericArray* target, U64 item_size); +#define ArrayZero(target) \ + _ArrayZero( (GenericArray*)(target), sizeof((target)->data[0]) ) + +U64 _ArrayGetTailIndex(GenericArray* target); +#define ArrayGetTailIndex(target) \ + _ArrayGetTailIndex((GenericArray*)(target)); + +B32 _ArrayGetTail(GenericArray* target, void* out, U32 item_size); +#define ArrayGetTail(target, out) \ + _ArrayGetTail((GenericArray*)(target), (out), sizeof((target)->data[0])); + +/// Push a new 'item' after the tail index and increments head_size +/// if 'item' is NULL just return an empty object after the tail +void* _ArrayPushTail(GenericArray* target, void* item, U32 item_size); +#define ArrayPushTail(target, item) \ + _ArrayPushTail((GenericArray*)(target), (item), sizeof((target)->data[0])) + +/// Pops an item at the tail index tail and decrements head_size +B32 _ArrayPopTail(GenericArray* target, void* out, U32 item_size); +#define ArrayPopTail(target, out) \ + _ArrayPopTail((GenericArray*)(target), (out), sizeof((target)->data[0])) + + +// Create commonly used generic array types +DeclareArray(U8Array, U8); +DeclareArray(U32Array, U32); +DeclareArray(U64Array, U64); +DeclareArray(F32Array, F32); +DeclareArray(F64Array, F64); + #endif // BASE_CORE_H diff --git a/src/base/base_log.h b/src/base/base_log.h index 52a297d82..8abd671fc 100644 --- a/src/base/base_log.h +++ b/src/base/base_log.h @@ -49,12 +49,12 @@ internal void log_select(Log *log); 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_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 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")) +#define LogInfoNamedBlockF(fmt, ...) DeferLoop((log_infof(fmt, ##__VA_ARGS__), log_infof(":\n{\n")), log_infof("}\n")) //////////////////////////////// //~ rjf: Log Scopes diff --git a/src/base/base_strings.c b/src/base/base_strings.c index 355cc62ef..dc1b2378e 100644 --- a/src/base/base_strings.c +++ b/src/base/base_strings.c @@ -290,6 +290,47 @@ str8_match(String8 a, String8 b, StringMatchFlags flags){ return(result); } +internal Rng1U64 +str8_match_substr(String8 target, String8 expression, StringMatchFlags flags) +{ + Rng1U64 result; + result.min = 0; + result.max = 0; + B32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive); + B32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive); + U8 first_char = target.str[0]; + + // Can't match an expression larger than the target (obviously) + if (expression.size > target.size) return result; + + U8 x_char = 0; + U8 x_expr_char = 0; + U64 i_target = 0; + for (; i_target integer (base64 & base16) + +internal U64 +base64_size_from_data_size(U64 size_in_bytes){ + U64 bits = size_in_bytes*8; + U64 base64_size = (bits + 5)/6; + return(base64_size); +} + +internal U64 +base64_from_data(U8 *dst, U8 *src, U64 src_size){ + U8 *dst_base = dst; + U8 *opl = src + src_size; + U32 bit_num = 0; + if (src < opl){ + U8 byte = *src; + for (;;){ + U32 x = 0; + for (U32 i = 0; i < 6; i += 1){ + x |= ((byte >> bit_num) & 1) << i; + bit_num += 1; + if (bit_num == 8){ + bit_num = 0; + src += 1; + byte = (src < opl)?(*src):0; + } + } + *dst = base64[x]; + dst += 1; + if (src >= opl){ + break; + } + } + } + return(dst - dst_base); +} + +internal U64 +base16_size_from_data_size(U64 size_in_bytes){ + U64 base16_size = size_in_bytes*2; + return(base16_size); +} + +internal U64 +base16_from_data(U8 *dst, U8 *src, U64 src_size){ + U8 *dst_base = dst; + U8 *opl = src + src_size; + for (;src < opl;){ + U8 byte = *src; + *dst = integer_symbols[byte & 0xF]; + dst += 1; + *dst = integer_symbols[byte >> 4]; + dst += 1; + src += 1; + } + return(dst - dst_base); +} + + //- rjf: integer -> string internal String8 diff --git a/src/base/base_strings.h b/src/base/base_strings.h index c42b35088..f93926a1c 100644 --- a/src/base/base_strings.h +++ b/src/base/base_strings.h @@ -204,6 +204,9 @@ internal String8 backslashed_from_str8(Arena *arena, String8 string); //~ rjf: String Matching internal B32 str8_match(String8 a, String8 b, StringMatchFlags flags); +/* Matches a substring in a target substring and returns a range of a match, or [0, 0] + Supports StringMatchFlag_CaseInsensitive and StringMatchFlag_SlashInsensitive */ +internal Rng1U64 str8_match_substr(String8 target, String8 expression, StringMatchFlags flags); internal U64 str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags); internal B32 str8_ends_with(String8 string, String8 end, StringMatchFlags flags); @@ -236,6 +239,12 @@ internal S64 s64_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: string -> integer (base64 & base16) +internal U64 base64_size_from_data_size(U64 size_in_bytes); +internal U64 base64_from_data(U8 *dst, U8 *src, U64 src_size); +internal U64 base16_size_from_data_size(U64 size_in_bytes); +internal U64 base16_from_data(U8 *dst, U8 *src, U64 src_size); + //- rjf: integer -> string internal String8 str8_from_memory_size(Arena *arena, U64 z); internal String8 str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator); diff --git a/src/ctrl/ctrl_core.c b/src/ctrl/ctrl_core.c index 631821b79..6088b05ef 100644 --- a/src/ctrl/ctrl_core.c +++ b/src/ctrl/ctrl_core.c @@ -5176,7 +5176,7 @@ ctrl_mem_stream_thread__entry_point(void *p) { if(MemoryMatchStruct(&range_n->vaddr_range, &vaddr_range) && range_n->zero_terminated == zero_terminated) { - got_task = !ins_atomic_u32_eval_cond_assign(&range_n->is_taken, 1, 0); + got_task = !ins_atomic_u32_eval_cond_assign((U32*)&range_n->is_taken, 1, 0); preexisting_mem_gen = range_n->mem_gen; preexisting_hash = range_n->hash; vaddr_range_clamped = range_n->vaddr_range_clamped; diff --git a/src/dasm_cache/dasm_cache.c b/src/dasm_cache/dasm_cache.c index a0f462558..f78d0d552 100644 --- a/src/dasm_cache/dasm_cache.c +++ b/src/dasm_cache/dasm_cache.c @@ -395,7 +395,7 @@ dasm_parse_thread__entry_point(void *p) { if(u128_match(n->hash, hash) && dasm_params_match(&n->params, ¶ms)) { - got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); + got_task = !ins_atomic_u32_eval_cond_assign((U32*)&n->is_working, 1, 0); break; } } diff --git a/src/demon/demon_core.h b/src/demon/demon_core.h index 53866179c..72ca57708 100644 --- a/src/demon/demon_core.h +++ b/src/demon/demon_core.h @@ -154,6 +154,8 @@ struct DMN_ProcessInfo U32 pid; }; +// == OS Independent Layer == + //////////////////////////////// //~ rjf: Basic Type Functions (Helpers, Implemented Once) @@ -180,6 +182,8 @@ internal DMN_Event *dmn_event_list_push(Arena *arena, DMN_EventList *list); internal U64 dmn_rip_from_thread(DMN_Handle thread); internal U64 dmn_rsp_from_thread(DMN_Handle thread); +// == OS Layer == + //////////////////////////////// //~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) diff --git a/src/demon/demon_inc.c b/src/demon/demon_inc.c index daab75e23..41bfb0c73 100644 --- a/src/demon/demon_inc.c +++ b/src/demon/demon_inc.c @@ -4,7 +4,9 @@ #include "demon_core.c" #if OS_WINDOWS -# include "win32/demon_core_win32.c" + #include "win32/demon_core_win32.c" +#elif OS_LINUX + #include "linux/demon_core_linux.c" #else -# error Demon layer backend not defined for this operating system. + #error Demon layer backend not defined for this operating system. #endif diff --git a/src/demon/demon_inc.h b/src/demon/demon_inc.h index 3328f9b66..dd0625e54 100644 --- a/src/demon/demon_inc.h +++ b/src/demon/demon_inc.h @@ -7,9 +7,11 @@ #include "demon_core.h" #if OS_WINDOWS -# include "win32/demon_core_win32.h" + #include "win32/demon_core_win32.h" +#elif OS_LINUX + #include "linux/demon_core_linux.h" #else -# error Demon layer backend not defined for this operating system. + #error Demon layer backend not defined for this operating system. #endif #endif // DEMON_INC_H diff --git a/src/demon/linux/demon_core_linux.c b/src/demon/linux/demon_core_linux.c new file mode 100644 index 000000000..cbbf4b655 --- /dev/null +++ b/src/demon/linux/demon_core_linux.c @@ -0,0 +1,240 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + + + +//////////////////////////////// +//~ @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) + +internal void +dmn_init(void) +{ + dmn_lnx_arena = arena_alloc(); + dmn_lnx = push_array(dmn_lnx_arena, DMN_LNX_Shared, 1); + dmn_lnx->arena = dmn_lnx_arena; + dmn_lnx->mutex_access = os_mutex_alloc(); +} + +//////////////////////////////// +//~ @dmn_os_hooks Blocking Control Thread Operations (Implemented Per-OS) + +internal DMN_CtrlCtx +*dmn_ctrl_begin(void) +{ + // NOTE: Boolean return, just says the context is valid + DMN_CtrlCtx* ctx = (DMN_CtrlCtx* )1; + dmn_lnx_ctrl_thread = 1; + return ctx; +} + +internal void +dmn_ctrl_exclusive_access_begin(void) +{ + os_mutex_take(dmn_lnx->mutex_access); +} + +internal void +dmn_ctrl_exclusive_access_end(void) +{ + os_mutex_drop(dmn_lnx->mutex_access); +} + +internal U32 +dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_LaunchOptions *options) +{ + NotImplemented; + U32 result = 0; + Temp scratch = scratch_begin(0, 0); + AssertAlways(options->inherit_env); // NOTE(mallchad): Figure out no-inherit later + OS_Handle process = {0}; + B32 success = 0; + + DMN_AccessScope + { + success = os_launch_process(options, &process); + } + /* NOTE(mallchad): I don't believe there is a neccessary Linux equivilent to AllocConsole. + If you wanted to redirect stdin/stdout here would probably be the best place */ + if (success) { result = *process.u64; } + scratch_end(scratch); + + return result; +} + +/* TODO: This behaviour is governed by kernel.yama.ptrace_scope kernel parameter + This information should probably be relayed to the user in future. */ +internal B32 +dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) +{ + S32 error = ptrace(PTRACE_ATTACH, pid); + return (error == 0); +} + +internal B32 +dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code) +{ + NotImplemented; + return 0; +} + +internal B32 +dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process) +{ + NotImplemented; + return 0; +} + +internal DMN_EventList +dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) +{ + DMN_EventList result = {0}; + NotImplemented; + return result; +} + +//////////////////////////////// +//~ @dmn_os_hooks Halting (Implemented Per-OS) + +internal void +dmn_halt(U64 code, U64 user_data) +{ + NotImplemented; +} + +//////////////////////////////// +//~ @dmn_os_hooks Introspection Functions (Implemented Per-OS) + +//- run/memory/register counters +internal U64 +dmn_run_gen(void) +{ + return ins_atomic_u64_eval(&dmn_lnx->counter_run); +} + +internal U64 +dmn_mem_gen(void) +{ + return ins_atomic_u64_eval(&dmn_lnx->counter_mem); +} + +internal U64 +dmn_reg_gen(void) +{ + return ins_atomic_u64_eval(&dmn_lnx->counter_reg); +} + +//- non-blocking-control-thread access barriers + +internal B32 +dmn_access_open(void) +{ + // TODO(mallchad): I feel like this needs to be improved but I don't know how + if (dmn_lnx_ctrl_thread) + { } + else + { os_mutex_take(dmn_lnx->mutex_access); } + return 1; +} +internal void +dmn_access_close(void) +{ + if (dmn_lnx_ctrl_thread == 0) + { os_mutex_drop(dmn_lnx->mutex_access); } +} + +//- processes +internal U64 +dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) +{ + NotImplemented; + return 0; +} + +internal void +dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size) +{ + NotImplemented; +} + +internal void +dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size) +{ + NotImplemented; +} + +internal void +dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size) +{ + NotImplemented; +} + +internal void +dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, OS_AccessFlags flags) +{ + NotImplemented; +} + +internal U64 +dmn_process_read(DMN_Handle process, Rng1U64 range, void *dst) +{ + NotImplemented; + return 0; +} + +internal B32 +dmn_process_write(DMN_Handle process, Rng1U64 range, void *src) +{ + NotImplemented; + return 0; +} + +//- threads +internal Architecture dmn_arch_from_thread(DMN_Handle handle) +{ + Architecture result = 0; + NotImplemented; + return 0; +} +internal U64 dmn_stack_base_vaddr_from_thread(DMN_Handle handle) +{ + NotImplemented; + return 0; +} +internal U64 dmn_tls_root_vaddr_from_thread(DMN_Handle handle) +{ + NotImplemented; + return 0; +} +internal B32 +dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block) +{ + NotImplemented; + return 0; +} + +internal B32 +dmn_thread_write_reg_block(DMN_Handle handle, void *reg_block) +{ + NotImplemented; + return 0; +} + +//- system process listing +internal void +dmn_process_iter_begin(DMN_ProcessIter *iter) +{ + NotImplemented; +} + +internal B32 +dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out) +{ + NotImplemented; + return 0; +} + +internal void +dmn_process_iter_end(DMN_ProcessIter *iter) +{ + NotImplemented; +} diff --git a/src/demon/linux/demon_core_linux.h b/src/demon/linux/demon_core_linux.h new file mode 100644 index 000000000..926e44e7e --- /dev/null +++ b/src/demon/linux/demon_core_linux.h @@ -0,0 +1,23 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DEMON_CORE_LINUX_H +#define DEMON_CORE_LINUX_H + +#include + +typedef struct DMN_LNX_Shared DMN_LNX_Shared; +struct DMN_LNX_Shared +{ + Arena* arena; + U64 counter_mem; + U64 counter_reg; + U64 counter_run; + OS_Handle mutex_access; +}; + +global Arena* dmn_lnx_arena = NULL; +global DMN_LNX_Shared* dmn_lnx = NULL; +thread_static B32 dmn_lnx_ctrl_thread = 0; + +#endif // DEMON_CORE_LINUX_H diff --git a/src/demon/linux/demon_os_linux.c b/src/demon/linux/demon_os_linux.c index 0c92f461f..9e0c2c25b 100644 --- a/src/demon/linux/demon_os_linux.c +++ b/src/demon/linux/demon_os_linux.c @@ -1,6 +1,63 @@ // Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// --- Old Content // TODO(allen): run controls: ignore_previous_exception //////////////////////////////// diff --git a/src/demon/win32/demon_core_win32.c b/src/demon/win32/demon_core_win32.c index 39ac97bbe..cfaf1ffae 100644 --- a/src/demon/win32/demon_core_win32.c +++ b/src/demon/win32/demon_core_win32.c @@ -1123,6 +1123,7 @@ dmn_init(void) internal DMN_CtrlCtx * dmn_ctrl_begin(void) { + // Boolean return, just says the context is valid DMN_CtrlCtx *ctx = (DMN_CtrlCtx *)1; dmn_w32_ctrl_thread = 1; return ctx; diff --git a/src/df/gfx/df_gfx.c b/src/df/gfx/df_gfx.c index ee7bfa06f..5b37194e8 100644 --- a/src/df/gfx/df_gfx.c +++ b/src/df/gfx/df_gfx.c @@ -13151,13 +13151,6 @@ df_gfx_request_frame(void) //////////////////////////////// //~ rjf: Main Layer Top-Level Calls -#if !defined(STBI_INCLUDE_STB_IMAGE_H) -# define STB_IMAGE_IMPLEMENTATION -# define STBI_ONLY_PNG -# define STBI_ONLY_BMP -# include "third_party/stb/stb_image.h" -#endif - internal void df_gfx_init(OS_WindowRepaintFunctionType *window_repaint_entry_point, DF_StateDeltaHistory *hist) { @@ -14187,7 +14180,7 @@ df_gfx_end_frame(void) //- rjf: simulate lag if(DEV_simulate_lag) { - Sleep(300); + os_sleep_milliseconds(300); } //- rjf: entities with a death timer -> keep animating diff --git a/src/file_stream/file_stream.c b/src/file_stream/file_stream.c index 6f1e63be2..4b06d19cf 100644 --- a/src/file_stream/file_stream.c +++ b/src/file_stream/file_stream.c @@ -83,7 +83,7 @@ fs_hash_from_path(String8 path, U64 endt_us) SLLQueuePush(slot->first, slot->last, node); node->path = push_str8_copy(stripe->arena, path); } - if(!ins_atomic_u32_eval_cond_assign(&node->is_working, 1, 0) && + if(!ins_atomic_u32_eval_cond_assign((U32*)&node->is_working, 1, 0) && !fs_u2s_enqueue_path(path, endt_us)) { ins_atomic_u32_eval_assign(&node->is_working, 0); @@ -270,7 +270,7 @@ fs_detector_thread__entry_point(void *p) FileProperties props = os_properties_from_file_path(n->path); if(props.modified != n->timestamp) { - if(!ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0) && + if(!ins_atomic_u32_eval_cond_assign((U32*)&n->is_working, 1, 0) && !fs_u2s_enqueue_path(n->path, os_now_microseconds()+100000)) { ins_atomic_u32_eval_assign(&n->is_working, 0); diff --git a/src/font_provider/font_provider_inc.c b/src/font_provider/font_provider_inc.c index f2dc49aec..79acd669d 100644 --- a/src/font_provider/font_provider_inc.c +++ b/src/font_provider/font_provider_inc.c @@ -4,7 +4,9 @@ #include "font_provider.c" #if FP_BACKEND == FP_BACKEND_DWRITE -# include "dwrite/font_provider_dwrite.c" + #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/font_provider_inc.h b/src/font_provider/font_provider_inc.h index a79c66fcc..c586796a2 100644 --- a/src/font_provider/font_provider_inc.h +++ b/src/font_provider/font_provider_inc.h @@ -8,12 +8,15 @@ //~ rjf: Backend Constants #define FP_BACKEND_DWRITE 1 +#define FP_BACKEND_FREETYPE 2 //////////////////////////////// //~ rjf: Decide On Backend #if !defined(FP_BACKEND) && OS_WINDOWS # define FP_BACKEND FP_BACKEND_DWRITE +#elif !defined(FP_BACKEND) && OS_LINUX +#define FP_BACKEND FP_BACKEND_FREETYPE #endif //////////////////////////////// @@ -25,7 +28,9 @@ //~ rjf: Backend Includes #if FP_BACKEND == FP_BACKEND_DWRITE -# include "dwrite/font_provider_dwrite.h" + #include "dwrite/font_provider_dwrite.h" +#elif FP_BACKEND == FP_BACKEND_FREETYPE + #include "freetype/font_provider_freetype.h" #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 new file mode 100644 index 000000000..891ccea2b --- /dev/null +++ b/src/font_provider/freetype/font_provider_freetype.c @@ -0,0 +1,446 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +global FT_Library freetype_instance = NULL; +global Arena* freetype_arena = NULL; +global U32 freetype_glyph_size = 32; + +FreeType_FontFace +freetype_face_from_handle(FP_Handle face) +{ + FreeType_FontFace result = PtrFromInt(*face.u64); + return result; +} +FP_Handle +freetype_handle_from_face(FreeType_FontFace face) +{ + FP_Handle result = {0}; + *result.u64 = IntFromPtr(face); + return result; +} + +String8 freetype_error_string(U32 error) +{ + U32 x_code = freetype_errors[0].err_code; + char* x_str = (char*)(freetype_errors[0].err_msg); + for (int i=0; x_str != NULL; ++i) + { + if (x_code == error) { return str8_cstring(x_str); } + x_code = freetype_errors[i].err_code; + x_str = (char*)(freetype_errors[i].err_msg); + } + return str8_lit("no error code found"); +} + +fp_hook void +fp_init(void) +{ + // NOTE: There is an alternative function to manage memory manually with freetype + freetype_arena = arena_alloc(); + FT_Init_FreeType(&freetype_instance); +} + +fp_hook FP_Handle +fp_font_open(String8 path) +{ + /* NotImplemented; */ + FP_Handle result = {0}; + FT_Face face = {0}; + S64 face_index = 0x0; + FT_Size_RequestRec sizing_request = {0}; + // Pt Fractional Scaling + sizing_request.width = 30*64; + sizing_request.height = 30*64; + // DPI + sizing_request.horiResolution = 30; + sizing_request.vertResolution = 30; + S32 error = FT_New_Face( freetype_instance, (char*)path.str, face_index, &face); + B32 error_sizing = FT_Request_Size(face, &sizing_request); + + Assert(error == 0); + Assert(error_sizing == 0); + + + // TODO: DELETE + U32 test_index = FT_Get_Char_Index(face, (FT_ULong)'A'); + B32 error_attach = FT_Attach_File(face, (char*)path.str); + B32 error_char = FT_Load_Char(face, (FT_ULong)'A', 0x0); + B32 error_glyph = FT_Load_Glyph(face, test_index, FT_LOAD_DEFAULT); + + result = freetype_handle_from_face(face); + return result; +} + +fp_hook FP_Handle +fp_font_open_from_static_data_string(String8 *data_ptr) +{ + // TODO: TEST IF FINSIHED + FP_Handle result = {0}; + FT_Face face = {0}; + FT_Open_Args args = {0}; + S64 face_index = 0x0; + FT_Size_RequestRec sizing_request = {0}; + if (data_ptr->size == 0) { return result; } + + // Pt Fractional Scaling + sizing_request.width = 300* freetype_glyph_size; + sizing_request.height = 300 * freetype_glyph_size; + + // DPI + sizing_request.horiResolution = 30; + sizing_request.vertResolution = 30; + + // Set memory file bit + args.flags = FT_OPEN_MEMORY; + args.memory_base = data_ptr->str; + args.memory_size = data_ptr->size; + args.driver = NULL; // Try every font driver + + S32 error = FT_Open_Face( freetype_instance, &args, face_index, &face); + // Setup font scaling + B32 error_sizing = FT_Request_Size(face, &sizing_request); + + Assert(error == 0); + Assert(error_sizing == 0); + + result = freetype_handle_from_face(face); + return result; +} + +fp_hook void +fp_font_close(FP_Handle handle) +{ + FreeType_FontFace face = freetype_face_from_handle(handle); + FT_Done_Face(face); +} + +fp_hook FP_Metrics +fp_metrics_from_font(FP_Handle font) +{ + FP_Metrics result = {0}; + if (*font.u64 == 0x0) return result; + FreeType_FontFace face = freetype_face_from_handle(font); + + // NOTE(mallchad): Note I don't know which is more useful so leaving OS2 header for now + // result.design_units_per_em = face->units_per_EM; + // result.ascent = face->ascender; + // result.descender = face->descender; + // result.line_gap = 0; + // result.capital_height = face->ascender; // NOTE: It's the same thing. + + /* Get the horizontal header for horizontally written fonts + NOTE: Should work for OTF and TrueType fonts + NOTE: From freetype documentation: You should use the `sTypoAscender` field + of the 'OS/2' table instead if you want the correct one. + NOTE(mallchad): These OS2 tables are supposedly version dependet, might some + fail? who knows. + */ + TT_Header* header_tt = (TT_Header*)FT_Get_Sfnt_Table(face, FT_SFNT_HEAD); + TT_OS2* header_os2 = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2); + AssertAlways(header_os2 != NULL); + result.ascent = header_os2->sTypoAscender; + result.descent = header_os2->sTypoDescender; + result.line_gap = header_os2->sTypoLineGap; + result.capital_height = header_os2->sCapHeight; + + return result; +} + +fp_hook NO_ASAN +FP_RasterResult +fp_raster(Arena *arena, + FP_Handle font, + F32 size, + FP_RasterMode mode, + String8 string) +{ + /* NotImplemented; */ + /* NOTE(mallchad): I am assuming this function will only ever run for 1 glyph + at a time because that is the API usage and how it works in the dwrite + section. If anything else occurs the code may not behave properly */ + Assert(string.size <= 1); + FP_RasterResult result = {0}; + FreeType_FontFace face = freetype_face_from_handle(font); + if (face == 0) { return result; } + + Temp scratch = scratch_begin(0, 0); + + U32 glyph_count = string.size; + U32* charmap_indices = push_array(scratch.arena, U32, glyph_count + 4); + + // Error counting + U32 errors_char = 0; + U32 errors_glyph = 0; + U32 errors_render = 0; + // Last loop iteration had error + B32 err_char = 0; + B32 err_glyph = 0; + B32 err_render = 0; + + FT_Matrix matrix; + FT_Vector pen; + F64 tau = 6.283185307; + F64 angle = 1 * tau; + /* set up matrix + Magic fixed point math */ + matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L ); + matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L ); + matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L ); + matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L ); + // Flip font y + // matrix.yy = -matrix.yy; + + // the pen position in 26.6 cartesian space coordinates; + // start at (300,200) relative to the upper left corner + pen.x = 0; + pen.y = face->ascender; + // Max glyph width and height + /* NOTE(mallchad): 'bbox' is the minimum bounding box that must fit all rendered glyphs, + the glyph metrics are stored is 26.6 fractional format. Meaning 1 unit is 1/64th of a pixel + https://freetype.org/freetype2/docs/tutorial/step2.html + So divide by 64 to get pixels */ + U32 glyph_width = FT_MulFix((face->bbox.xMax - face->bbox.xMin), face->size->metrics.x_scale ) / 64; + U32 glyph_height = FT_MulFix((face->bbox.yMax - face->bbox.yMin), face->size->metrics.y_scale ) / 64; + + U32 total_advance = 0; + Vec2S16 atlas_dim = {0}; + + U32 atlas_padding = (glyph_width % 4) * glyph_height * glyph_count; + U8* atlas = push_array_no_zero( scratch.arena, U8, + (glyph_width * glyph_height) + atlas_padding ); + + U8 x_char = 0; + U32 x_charmap = 0; + U32 i = 0; + for (; i < glyph_count; ++i) + { + x_char = string.str[i]; + x_charmap = FT_Get_Char_Index(face, (FT_ULong)x_char); + charmap_indices[i] = x_charmap; + + // Undefined character code or NULL face + /* err_char += FT_Load_Char(face, (FT_ULong)x_char, 0x0); */ // Load char and index in oneshot + err_glyph += FT_Load_Glyph(face, x_charmap, FT_LOAD_DEFAULT); + FT_Set_Transform( face, &matrix, &pen ); // Set transforms + /* FT_Set_Transform( face, NULL, NULL ); // Reset transforms */ + err_render += FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + + Assert(face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY); + // TODO(mallchad): Untested section + freetype_rectangle_copy( atlas, + face->glyph->bitmap.buffer, + vec_2s32(face->glyph->bitmap.pitch, face->glyph->bitmap.rows), + vec_2s32(0, 0), + vec_2s32(total_advance, glyph_height), + face->glyph->bitmap.pitch, + glyph_width); + + // Section compied from dwrite + total_advance += glyph_width; + atlas_dim.x = Max(atlas_dim.x, (S16)(1+ total_advance)); + err_char += (errors_glyph > 0); + err_glyph += (errors_glyph > 0); + err_render += (errors_render > 0); + } + Assert(!errors_glyph); + Assert(!errors_glyph); + Assert(!errors_render); + /* atlas_dim.x += 7; + atlas_dim.x -= atlas_dim.x%8; + atlas_dim.x += 4; + atlas_dim.y += 4; */ + // Fill raster basics + result.atlas_dim.x = total_advance; + result.atlas_dim.y = glyph_height; + result.advance = glyph_width; + result.atlas = push_array(arena, U8, 4* result.atlas_dim.x* result.atlas_dim.y); + + static U32 null_errors = 0; + /* WTF. Even if no errors came back??? + leaving here as a guard against 3rd party NULL shenanigans */ + if (face->glyph->bitmap.buffer != NULL) + { + // Debug Stuff + String8 debug_name = str8_lit("glyph_test.bmp"); + freetype_write_bmp_monochrome_file(debug_name, + face->glyph->bitmap.buffer, + face->glyph->bitmap.pitch, + face->glyph->bitmap.rows); + + + } else { ++null_errors; } + scratch_end(scratch); + + return result; +} + +// Converts RGB to BGR format +B32 +freetype_write_bmp_file(String8 name, U8* data, U32 width, U32 height) +{ + if (width == 0 || + height == 0 || + data == 0) + { return 0; } + + Temp scratch = scratch_begin(0, 0); + U32 greedy_filesize = sizeof(FreeType_BitmapHeader) + (4* width*height) + 200; + U8* buffer = push_array(scratch.arena, U8, greedy_filesize); + FreeType_BitmapHeader* header = (FreeType_BitmapHeader*)buffer; + U32 pixbuf_offset = (40+ sizeof(FreeType_BitmapHeader)); + U8* pixbuf = buffer+ pixbuf_offset; + + MemoryCopy(&header->signature, "BM\0\0\0\0", 2); + header->file_size = greedy_filesize; + header->_reserved = 0; + header->data_offset = pixbuf_offset; + header->info_header_size = 40; + header->image_width = width; + header->image_height = height; + header->image_planes = 1; + header->bit_depth = 24; + header->compression_type = 0x0; // No compression + header->x_pixels_per_meter = 2835; // Does this even matter? + header->y_pixels_per_meter = 2835; + header->compressed_image_size = 4* width*height; + + // Copy Data + U8* tmp_buffer = push_array(scratch.arena, U8, width*height*4 ); + U64 bmp_stride = (width* 3); + U64 data_stride = (width* 3); + U32 mod4 = (bmp_stride%4); + bmp_stride = (mod4 ? bmp_stride + (4- mod4) : bmp_stride); // Fix stride padding + U32 x = 0; + for (int iy=0; iy < height; ++iy) + { + for (int ix=0; ix < width +1; ++ix) + { + x = ix*3; + // Reverse RGB to BGR Format + tmp_buffer[2+ x + (iy* bmp_stride)] = data[ 0+ x+ (iy *data_stride) ]; + tmp_buffer[1+ x + (iy* bmp_stride)] = data[ 1+ x+ (iy *data_stride) ]; + tmp_buffer[0+ x + (iy* bmp_stride)] = data[ 2+ x+ (iy *data_stride) ]; + } + } + // Invert Image + for (U64 i_height=0; i_height < height; ++i_height) + { + MemoryCopy(pixbuf+ ((height-i_height-1)* bmp_stride), + tmp_buffer+ (i_height* bmp_stride), + data_stride); + } + + OS_Handle file = os_file_open(OS_AccessFlag_Write, name); + os_file_write(file, rng_1u64(0, greedy_filesize), buffer); + os_file_close(file); + scratch_end(scratch); + + return (*file.u64 > 0); +} + +B32 +freetype_write_bmp_monochrome_file(String8 name, U8* data, U32 width, U32 height) +{ + if (width == 0 || + height == 0 || + data == 0) + { return 0; } + + Temp scratch = scratch_begin(0, 0); + U32 greedy_filesize = sizeof(FreeType_BitmapHeader) + (4* width*height) + 200; + U8* filebuf = push_array(scratch.arena, U8, greedy_filesize); + FreeType_BitmapHeader* header = (FreeType_BitmapHeader*)filebuf; + U32 pixbuf_offset = (40+ sizeof(FreeType_BitmapHeader)*2); + U8* pixbuf = filebuf+ pixbuf_offset; + + MemoryZero(filebuf, greedy_filesize); + MemoryCopy(&header->signature, "BM\0\0\0\0", 2); + header->file_size = greedy_filesize; + header->_reserved = 0; + header->data_offset = pixbuf_offset; + header->info_header_size = 40; + header->image_width = width; + header->image_height = height; + header->image_planes = 1; + header->bit_depth = 24; + header->compression_type = 0x0; // No compression + header->x_pixels_per_meter = 2835; + header->y_pixels_per_meter = 2835; + header->compressed_image_size = 4* width*height; + + U8* buffer = push_array(scratch.arena, U8, 4* width*height * 128); + // Error printing + /* MemorySet(buffer, 0xAF, width * height * 4); */ + /* MemorySet(pixbuf, 0xAF, width * height * 4); */ + U8 x_color = 0; + U64 x = 0; + // Monochromize + for (U64 i=0; i<(width*(height)); ++i) + { + x_color = data[i]; + x = 3* i; + + buffer[0+ x] = x_color; + buffer[1+ x] = x_color; + buffer[2+ x] = x_color; + } + // Invert + U64 bitmap_stride = (width* 3); + U64 data_stride = (width* 3); + U32 mod4 = (bitmap_stride) % 4; + bitmap_stride = (mod4 ? bitmap_stride + (4- mod4) : bitmap_stride); // Fix stride padding + for (U64 i_height=0; i_height = 0); + MemoryCopy(pixbuf+ ((i_height)* bitmap_stride), + buffer+ ((height - i_height - 1)* data_stride), + data_stride); + } + MemorySet(pixbuf + (bitmap_stride * height-1), 0xA0, bitmap_stride); + + OS_Handle file = os_file_open(OS_AccessFlag_Write, name); + os_file_write(file, rng_1u64(0, greedy_filesize), filebuf); + os_file_close(file); + scratch_end(scratch); + + return (*file.u64 > 0); +} + +U8* +freetype_rectangle_copy(U8* dest, + U8* source, + Vec2S32 copy_size, + Vec2S32 src_coord, + Vec2S32 dest_coord, + U32 src_stride, + U32 dest_stride) +{ + // TODO(mallchad): Untested + U8* result = (dest+ dest_coord.x + dest_stride); + U8* copy_begin = 0x0; + U8* result_begin = 0x0; + for (int iy = src_coord.y; iy < copy_size.y; ++iy) + { + copy_begin = source+ src_coord.x + (iy* src_stride); + result_begin = dest+ dest_coord.x + (iy* dest_stride); + MemoryCopy(result_begin, copy_begin, copy_size.x); + } + return result; +} + +U8* +freetype_rectangle_slice( Arena* arena, U8* source, U32 x, U32 y, U32 width, U32 height) +{ + U8* result = push_array(arena, U8, 4+ (width*height)); + U8* copy_begin; + U8* result_begin; + U32 amount = width; + for (int iy = y; iy < height; ++iy) + { + copy_begin = source+ x + (iy* width); + result_begin = result+ (iy* width); + MemoryCopy(result_begin, copy_begin, amount); + } + + return result; +} diff --git a/src/font_provider/freetype/font_provider_freetype.h b/src/font_provider/freetype/font_provider_freetype.h new file mode 100644 index 000000000..4e6158437 --- /dev/null +++ b/src/font_provider/freetype/font_provider_freetype.h @@ -0,0 +1,77 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef FONT_PROVIDER_FREETYPE_H +#define FONT_PROVIDER_FREETYPE_H + +// Breaks part of freetype headers +#undef internal +/* #include */ +#include +#include + +// Setup error strings for debugging +#undef FTERRORS_H_ +#define FT_ERRORDEF( e, v, s ) { e, s }, +#define FT_ERROR_START_LIST { +#define FT_ERROR_END_LIST { 0, NULL } }; + +const struct +{ + int err_code; + const char* err_msg; +} freetype_errors[] = + +#include FT_ERRORS_H + +#include FT_FREETYPE_H + +// Restore internal macro +#define internal static + +// Types +#pragma pack(push, 1) +typedef struct FreeType_BitmapHeader FreeType_BitmapHeader; +struct FreeType_BitmapHeader { + U16 signature; + U32 file_size; + U32 _reserved; + U32 data_offset; + // InfoHeader + U32 info_header_size; + U32 image_width; + U32 image_height; + U16 image_planes; + U16 bit_depth; + U32 compression_type; + U32 compressed_image_size; + U32 x_pixels_per_meter; + U32 y_pixels_per_meter; + U32 colors_used; + U32 colors_important; + U32 _repeating_color_table; +}; +#pragma pack(pop) + + +// Convenience typedefs +/** Managing object for a font set */ +typedef FT_Face FreeType_FontFace; + +FreeType_FontFace freetype_face_from_handle(FP_Handle face); +FP_Handle freetype_handle_from_face(FreeType_FontFace face); +String8 freetype_error_string(U32 error); + +B32 freetype_write_bmp_file(String8 name, U8* data, U32 width, U32 height); +B32 freetype_write_bmp_monochrome_file(String8 name, U8* data, U32 width, U32 height); +// Assumes the same size source and dest +U8* freetype_rectangle_slice( Arena* arena, U8* source, U32 x, U32 y, U32 width, U32 height); +U8* freetype_rectangle_copy(U8* dest, + U8* source, + Vec2S32 copy_size, + Vec2S32 src_coord, + Vec2S32 dest_coord, + U32 src_stride, + U32 dest_stride); + +#endif // FONT_PROVIDER_FREETYPE_H diff --git a/src/geo_cache/geo_cache.c b/src/geo_cache/geo_cache.c index 587a76fda..f03461981 100644 --- a/src/geo_cache/geo_cache.c +++ b/src/geo_cache/geo_cache.c @@ -284,7 +284,7 @@ geo_xfer_thread__entry_point(void *p) { if(u128_match(n->hash, hash)) { - got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); + got_task = !ins_atomic_u32_eval_cond_assign((U32*)&n->is_working, 1, 0); break; } } diff --git a/src/metagen/metagen_base/metagen_base_arena.c b/src/metagen/metagen_base/metagen_base_arena.c index d7297f712..6e1a234fb 100644 --- a/src/metagen/metagen_base/metagen_base_arena.c +++ b/src/metagen/metagen_base/metagen_base_arena.c @@ -22,7 +22,7 @@ arena_alloc__sized(U64 init_res, U64 init_cmt) #if OS_WINDOWS cmt = res; #else - cmt AlignPow2(init_cmt, page_size); + cmt = AlignPow2(init_cmt, page_size); #endif memory = os_reserve_large(res); diff --git a/src/metagen/metagen_base/metagen_base_string.c b/src/metagen/metagen_base/metagen_base_string.c index ac9745998..f54e277cb 100644 --- a/src/metagen/metagen_base/metagen_base_string.c +++ b/src/metagen/metagen_base/metagen_base_string.c @@ -290,6 +290,47 @@ str8_match(String8 a, String8 b, StringMatchFlags flags){ return(result); } +internal Rng1U64 +str8_match_substr(String8 target, String8 expression, StringMatchFlags flags) +{ + Rng1U64 result; + result.min = 0; + result.max = 0; + B32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive); + B32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive); + U8 first_char = target.str[0]; + + // Can't match an expression larger than the target (obviously) + if (expression.size > target.size) return result; + + U8 x_char = 0; + U8 x_expr_char = 0; + U64 i_target = 0; + for (; i_target - + /* TODO(mallchad): want to add asserts for 'kind' of LNX_Entity */ //////////////////////////////// //~ rjf: Globals @@ -14,12 +13,37 @@ global LNX_Entity *lnx_entity_free = 0; global String8 lnx_initial_path = {0}; thread_static LNX_SafeCallChain *lnx_safe_call_chain = 0; +global U64 lnx_page_size = 4096; +// TODO: This can't be used until the huge page allocation count is checked +global B32 lnx_huge_page_enabled = 0; +global B32 lnx_huge_page_use_1GB = 0; +global U16 lnx_ring_buffers_created = 0; +global U16 lnx_ring_buffers_limit = 65000; +global String8List lnx_environment = {0}; + +global String8 lnx_hostname = {0}; +global LNX_version lnx_kernel_version = {0}; +global String8 lnx_architecture = {0}; +global String8 lnx_kernel_type = {0}; + +////////////////////////////////n +// Forward Declares +int +memfd_create (const char *__name, unsigned int __flags) __THROW; +ssize_t +copy_file_range (int __infd, __off64_t *__pinoff, + int __outfd, __off64_t *__poutoff, + size_t __length, unsigned int __flags); +extern int +creat(const char *__file, mode_t __mode); + + //////////////////////////////// //~ rjf: Helpers internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list){ - B32 success = true; + B32 success = 1; String8Node *node = list.first; if (node != 0){ @@ -42,7 +66,7 @@ lnx_write_list_to_file_descriptor(int fd, String8List list){ node = node->next; if (node == 0){ if (p < list.total_size){ - success = false; + success = 0; } break; } @@ -77,6 +101,16 @@ lnx_tm_from_date_time(struct tm *out, DateTime *in){ out->tm_year = in->year - 1900; } +internal void lnx_timespec_from_date_time(LNX_timespec* out, DateTime* in) +{ + NotImplemented; +} + +internal void lnx_timeval_from_date_time(LNX_timeval* out, DateTime* in) +{ + NotImplemented; +} + internal void lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in){ struct tm tm_time = {0}; @@ -86,6 +120,42 @@ lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in){ *out = dense_time_from_date_time(date_time); } +internal void +lnx_timeval_from_dense_time(LNX_timeval* out, DenseTime* in) +{ + // Miliseconds to Microseconds, should be U64 to long + out->tv_sec = 0; + out->tv_usec = 1000* (*in); +} + +internal void +lnx_timespec_from_dense_time(LNX_timespec* out, DenseTime* in) +{ + // Miliseconds to Seconds, should be U64 to long + out->tv_sec = (*in / 1000); + out->tv_nsec = 0; +} + +void +lnx_timespec_from_timeval(LNX_timespec* out, LNX_timeval* in ) +{ + out->tv_sec = in->tv_sec; + out->tv_nsec = in->tv_usec / 1000; +} + +void +lnx_timeval_from_timespec(LNX_timeval* out, LNX_timespec* in ) +{ + out->tv_sec = in->tv_sec; + out->tv_usec = in->tv_nsec * 1000; +} + +/* NOTE: ctime has very little to do with "creation time"- it's more about + inode modifications -but for this purpose it's usually considered the closest + analogue. Manage your own file-creation data if you actually want that info. + + There's way more info to draw from but leaving for now + https://man7.org/linux/man-pages/man3/stat.3type.html */ internal void lnx_file_properties_from_stat(FileProperties *out, struct stat *in){ MemoryZeroStruct(out); @@ -97,6 +167,60 @@ lnx_file_properties_from_stat(FileProperties *out, struct stat *in){ } } +internal U32 +lnx_prot_from_os_flags(OS_AccessFlags flags) +{ + U32 result = 0x0; + if (flags & OS_AccessFlag_Read) { result |= PROT_READ; } + if (flags & OS_AccessFlag_Write) { result |= PROT_WRITE; } + if (flags & OS_AccessFlag_Execute) { result |= PROT_EXEC; } + return result; +} + +internal U32 +lnx_open_from_os_flags(OS_AccessFlags flags) +{ + U32 result = 0x0; + // read/write flags are mutually exclusie on Linux `open()` + if (flags & OS_AccessFlag_Write & OS_AccessFlag_Read) { result |= O_RDWR | O_CREAT; } + else if (flags & OS_AccessFlag_Read) { result |= O_RDONLY; } + else if (flags & OS_AccessFlag_Write) { result |= O_WRONLY | O_CREAT; } + + // Doesn't make any sense on Linux, use os_file_map_open for execute permissions + // else if (flags & OS_AccessFlag_Execute) {} + // Shared doesn't make sense on Linux, file locking is explicit not set at open + // if(flags & OS_AccessFlag_Shared) {} + + return result; +} + +internal U32 + lnx_fd_from_handle(OS_Handle file) +{ + return *file.u64; +} + +internal OS_Handle +lnx_handle_from_fd(U32 fd) +{ + OS_Handle result = {0}; + *result.u64 = fd; + return result; +} + +internal LNX_Entity* +lnx_entity_from_handle(OS_Handle handle) +{ + LNX_Entity* result = (LNX_Entity*)PtrFromInt(*handle.u64); + return result; +} +internal OS_Handle lnx_handle_from_entity(LNX_Entity* entity) +{ + OS_Handle result = {0}; + *result.u64 = IntFromPtr(entity); + return result; +} + internal String8 lnx_string_from_signal(int signum){ String8 result = str8_lit(""); @@ -788,8 +912,9 @@ lnx_thread_base(void *ptr){ TCTX tctx_; tctx_init_and_equip(&tctx_); - + func(thread_ptr); + // tctx_release(); // remove my bit U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x2); @@ -801,7 +926,7 @@ lnx_thread_base(void *ptr){ } internal void -lnx_safe_call_sig_handler(int){ +lnx_safe_call_sig_handler(int _){ LNX_SafeCallChain *chain = lnx_safe_call_chain; if (chain != 0 && chain->fail_handler != 0){ chain->fail_handler(chain->ptr); @@ -809,6 +934,23 @@ lnx_safe_call_sig_handler(int){ abort(); } +internal LNX_timespec +lnx_now_precision_timespec() +{ + LNX_timespec result; + clock_gettime(CLOCK_MONOTONIC_RAW, &result); + + return result; +} + +internal LNX_timespec lnx_now_system_timespec() +{ + LNX_timespec result; + clock_gettime(CLOCK_REALTIME, &result); + return result; +} + + //////////////////////////////// //~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) @@ -839,11 +981,59 @@ os_init(int argc, char **argv) Arena *perm_arena = arena_alloc(); lnx_perm_arena = perm_arena; - // NOTE(allen): Initialize Paths - lnx_initial_path = os_get_path(lnx_perm_arena, OS_SystemPath_Current); - + // Initialize Paths + // Don't make assumptions just let the path be as big as it needs to be + U8 _initial_path[1000]; + S32 _initial_size = readlink("/proc/self/exe", (char*)_initial_path, 1000); + + if (_initial_size > 0) + { + String8 _initial_tmp = str8(_initial_path, _initial_size); + str8_chop_last_slash(_initial_tmp); + lnx_initial_path = push_str8_copy(lnx_perm_arena, _initial_tmp); + } + // Give empty string for relative path on the offchance it fails + else { lnx_initial_path = str8_lit(""); } + // NOTE(rjf): Setup command line args lnx_cmd_line_args = os_string_list_from_argcv(lnx_perm_arena, argc, argv); + + // Environment initialization + Temp scratch = scratch_begin(0, 0); + String8 env; + OS_Handle environ_handle = os_file_open(OS_AccessFlag_Read, str8_lit("/proc/self/environ")); + Rng1U64 limit = rng_1u64(0, 100000); + U8* environ_buffer = push_array(scratch.arena, U8, 100000); + U64 read_success = os_file_read(environ_handle, limit, environ_buffer); + os_file_close(environ_handle); + + if (read_success) + { + for (U8* x_string=(U8*)environ_buffer; + (x_string != NULL && x_string (1e9f * huge_threshold); + U32 page_size = huge_use_1GB ? MAP_HUGE_1GB : MAP_HUGE_2MB; + void *result; + if (lnx_huge_page_enabled) + { + /* NOTE: Huge pages are permanantly in memory unless paged out under high + memory pressure, MAP_POPULATE is probably not relevant, but you can mlock + it anyway if you want to be sure */ + result = mmap(0, size, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | page_size, + -1, 0); + } else { result = os_reserve(size); } + + return result; } internal B32 os_commit_large(void *ptr, U64 size){ - NotImplemented; - return 0; + U32 error = mprotect(ptr, size, PROT_READ|PROT_WRITE); + mlock(ptr, size); + munlock(ptr, size); + return (error == 0); } internal void @@ -885,37 +1097,83 @@ os_release(void *ptr, U64 size){ munmap(ptr, size); } -internal void +// Enable huge pages +// NOTE: Linux has huge pages instead of larges pages but there's no "nice" way to +// enable them from an unprivillaged application, that is unless you want to +// spawn a subprocess just for performing privileged actions +internal B32 os_set_large_pages(B32 flag) { - NotImplemented; + lnx_huge_page_enabled = os_large_pages_enabled(); + return lnx_huge_page_enabled; } internal B32 os_large_pages_enabled(void) { - NotImplemented; + // This is aparently the reccomended way to check for hugepage support. Do not ask... + // TODO(mallchad): This is an annoying way to do it, query for nr_hugepages instead + OS_Handle meminfo_handle = os_file_open(OS_AccessFlag_Read, str8_lit("/proc/meminfo/")); + U8 buffer[5000]; + String8 meminfo = {0}; + meminfo.str = buffer; + meminfo.size = os_file_read( meminfo_handle, rng_1u64(0, 5000), buffer ); + os_file_close(meminfo_handle); + + Rng1U64 match = str8_match_substr( meminfo, str8_cstring("Huge"), 0x0 ); + + // return (match.max > 0); return 0; } +/* NOTE: The size seems to be consistent across Linux systems, it's configurable + but the use case seems to be niche enough and the interface weird enough that + most people won't do it. The `/proc/meminfo` virtual file can be queried for + hugepage size. */ internal U64 os_large_page_size(void) { - NotImplemented; - return 0; + U64 size = (lnx_huge_page_use_1GB ? GB(1) : MB(2)); + return size; } internal void* os_alloc_ring_buffer(U64 size, U64 *actual_size_out) { - NotImplemented; - return 0; + Temp scratch = scratch_begin(0, 0); + void* result = NULL; + + // Make fle descriptor + String8 base = str8_lit("metagen_ring_xxxx"); + String8 filename_anonymous = push_str8_copy(scratch.arena, base); + base16_from_data(filename_anonymous.str + filename_anonymous.size -4, + (U8*)&lnx_ring_buffers_created, + sizeof(lnx_ring_buffers_created)); + if (lnx_ring_buffers_created < lnx_ring_buffers_limit) + { + B32 use_huge = (size > os_large_page_size() && lnx_huge_page_enabled); + U32 flag_huge1 = (use_huge ? MFD_HUGETLB : 0x0); + U32 flag_huge2 = (use_huge ? MAP_HUGETLB : 0x0); + U32 flag_prot = PROT_READ | PROT_WRITE; + + /* mmap circular buffer trick, create twice the buffer and double map it + with a shared file descriptor. Make sure to prevent mmap from changing + location with MAP_FIXED */ + S32 fd = memfd_create((const char*)filename_anonymous.str, flag_huge1); + result = mmap(NULL, 2* size, flag_prot, flag_huge2 | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); + mmap(result, size, flag_prot, flag_huge2 | MAP_FIXED, fd, 0); + mmap(result + size, size, flag_prot, flag_huge2 | MAP_FIXED, fd, 0); + // Closing fd doesn't invalidate mapping + close(fd); + } + scratch_end(scratch); + return result; } internal void os_free_ring_buffer(void *ring_buffer, U64 actual_size) { - NotImplemented; + munmap(ring_buffer, 2* actual_size); } //////////////////////////////// @@ -923,27 +1181,26 @@ os_free_ring_buffer(void *ring_buffer, U64 actual_size) internal String8 os_machine_name(void){ - local_persist B32 first = true; + local_persist B32 first = 1; local_persist String8 name = {0}; // TODO(allen): let's just pre-compute this at init and skip the complexity pthread_mutex_lock(&lnx_mutex); if (first){ Temp scratch = scratch_begin(0, 0); - first = false; + first = 0; // get name - B32 got_final_result = false; + B32 got_final_result = 0; U8 *buffer = 0; int size = 0; for (S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1){ - scratch.restore(); buffer = push_array_no_zero(scratch.arena, U8, cap); size = gethostname((char*)buffer, cap); if (size < cap){ - got_final_result = true; + got_final_result = 1; break; } } @@ -963,17 +1220,23 @@ os_machine_name(void){ return(name); } +/* Precomptued at init + NOTE: This setting can actually be changed at runtime, its not a super +important thing but it might be a better user experience if they can fix/improve +it without restarting. */ internal U64 -os_page_size(void){ - int size = getpagesize(); - return((U64)size); +os_page_size(void) +{ + return lnx_page_size; } internal U64 os_allocation_granularity(void) { - // On linux there is no equivalent of "dwAllocationGranularity" + /* On linux there is no equivalent of "dwAllocationGranularity" + NOTE: I suppose you could write into the sysfs but you need root privillages. */ os_page_size(); + return os_page_size(); } internal U64 @@ -1012,9 +1275,7 @@ os_get_tid(void){ internal String8List os_get_environment(void) { - NotImplemented; - String8List result = {0}; - return result; + return lnx_environment; } internal U64 @@ -1024,27 +1285,26 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o switch (path){ case OS_SystemPath_Binary: { - local_persist B32 first = true; + local_persist B32 first = 1; local_persist String8 name = {0}; // TODO(allen): let's just pre-compute this at init and skip the complexity pthread_mutex_lock(&lnx_mutex); if (first){ Temp scratch = scratch_begin(&arena, 1); - first = false; + first = 0; // get self string - B32 got_final_result = false; + 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.restore(); buffer = push_array_no_zero(scratch.arena, U8, cap); size = readlink("/proc/self/exe", (char*)buffer, cap); if (size < cap){ - got_final_result = true; + got_final_result = 1; break; } } @@ -1052,7 +1312,7 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o // save string if (got_final_result && size > 0){ String8 full_name = str8(buffer, size); - String8 name_chopped = string_path_chop_last_slash(full_name); + String8 name_chopped = str8_chop_last_slash(full_name); name = push_str8_copy(lnx_perm_arena, name_chopped); } @@ -1116,60 +1376,127 @@ internal OS_Handle os_file_open(OS_AccessFlags flags, String8 path) { OS_Handle file = {0}; - NotImplemented; + U32 access_flags = lnx_open_from_os_flags(flags); + + /* NOTE(mallchad): openat is supposedly meant to help prevent race conditions + with file moving or possibly a better way to put it is it helps resist + tampering with the referenced through relinks (assumption), ie symlink / mv . + Hopefully its more robust- we can close the dirfd it's kernel managed + now. The working directory can be changed but I don't know how best to + utiliez it so leaving it for now*/ + + // String8 file_dir = str8_chop_last_slash(path); + // S32 dir_fd = open(file_dir, O_PATH); + // S32 fd = openat(fle_dir, path.str, access_flags); + S32 fd = openat(AT_FDCWD, (char*)path.str, access_flags); + + // close(dirfd); + + // No Error + if (fd != -1) + { + *file.u64 = fd; + } return file; } internal void os_file_close(OS_Handle file) { - NotImplemented; + S32 fd = *file.u64; + close(fd); } +// Aparently there is a race condition in relation to `stat` and `lstat` so +// using fstat instead +// https://cwe.mitre.org/data/definitions/367.html internal U64 os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) { - NotImplemented; - return 0; + S32 fd = *file.u64; + struct stat file_info; + fstat(fd, &file_info); + U64 filesize = file_info.st_size; + + // Make sure not to read more than the size of the file + Rng1U64 clamped = r1u64(ClampTop(rng.min, filesize), ClampTop(rng.max, filesize)); + U64 read_amount = clamped.max - clamped.min; + lseek(fd, clamped.min, SEEK_SET); + S64 read_bytes = read(fd, out_data, read_amount); + + // Return 0 instead of -1 on error + return ClampBot(0, read_bytes); } internal void os_file_write(OS_Handle file, Rng1U64 rng, void *data) { - NotImplemented; + S32 fd = lnx_fd_from_handle(file); + LNX_fstat file_info; + fstat(fd, &file_info); + U64 filesize = file_info.st_size; + U64 write_size = rng.max - rng.min; + + AssertAlways(write_size > 0); + lseek(fd, rng.min, SEEK_SET); + // Expands file size if written off file end + U64 bytes_written = write(fd, data, write_size); + Assert("Zero or less written bytes is usually inticative of a bug" && bytes_written > 0); } internal B32 os_file_set_times(OS_Handle file, DateTime time) { - NotImplemented; + S32 fd = *file.u64; + LNX_timeval access_and_modification[2]; + DenseTime tmp = dense_time_from_date_time(time); + lnx_timeval_from_dense_time(access_and_modification, &tmp); + access_and_modification[1] = access_and_modification[0]; + B32 error = futimes(fd, access_and_modification); + + return (error != -1); } internal FileProperties os_properties_from_file(OS_Handle file) { - FileProperties props = {0}; - NotImplemented; - return props; + FileProperties result = {0}; + S32 fd = *file.u64; + LNX_fstat props = {0}; + B32 error = fstat(fd, &props); + + if (error == 0) + { + lnx_file_properties_from_stat(&result, &props); + } + return result; } internal OS_FileID os_id_from_file(OS_Handle file) { - // TODO(nick): querry struct stat with fstat(2) and use st_dev and st_ino as ids - OS_FileID id = {0}; - NotImplemented; - return id; + OS_FileID result = {0}; + U32 fd = *file.u64; + LNX_fstat props = {0}; + B32 error = fstat(fd, &props); + + if (error == 0) + { + result.v[0] = props.st_dev; + result.v[1] = props.st_ino; + result.v[2] = 0; + } + return result; } internal B32 os_delete_file_at_path(String8 path) { Temp scratch = scratch_begin(0, 0); - B32 result = false; - String8 name_copy = push_str8_copy(scratch.arena, name); + B32 result = 0; + String8 name_copy = push_str8_copy(scratch.arena, path); if (remove((char*)name_copy.str) != -1){ - result = true; + result = 1; } scratch_end(scratch); return(result); @@ -1178,53 +1505,134 @@ os_delete_file_at_path(String8 path) internal B32 os_copy_file_path(String8 dst, String8 src) { - NotImplemented; - return 0; + S32 source_fd = open((char*)src.str, 0x0, O_RDONLY); + S32 dest_fd = creat((char*)dst.str, O_WRONLY); + LNX_fstat props = {0}; + + S32 filesize = 0; + S32 bytes_written = 0; + B32 success = 0; + + fstat(source_fd, &props); + filesize = props.st_size; + + if (source_fd == 0 && dest_fd == 0) + { + bytes_written = copy_file_range(source_fd, NULL, dest_fd, NULL, filesize, 0x0); + success = (bytes_written == filesize); + } + close(source_fd); + close(dest_fd); + return success; } internal String8 os_full_path_from_path(Arena *arena, String8 path) { - // TODO: realpath can be used to resolve full path - String8 result = {0}; - NotImplemented; - return result; + String8 tmp = {0}; + char buffer[PATH_MAX+10]; + MemoryZeroArray(buffer); + char* success = realpath((char*)path.str, buffer); + if (success) + { + tmp = str8_cstring(buffer); + } + return (push_str8_copy(lnx_perm_arena, tmp)); } internal B32 os_file_path_exists(String8 path) { - NotImplemented; - return 0; + LNX_fstat _stub; + B32 exists = (0 == stat((char*)path.str, &_stub)); + return exists; } //- rjf: file maps +/* Does not map a view of the file into memory until a view is opened */ internal OS_Handle os_file_map_open(OS_AccessFlags flags, OS_Handle file) { - NotImplemented; - OS_Handle handle = {0}; - return handle; + // TODO: Implement access flags + LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_MemoryMap); + S32 fd = *file.u64; + entity->map.fd = fd; + OS_Handle result = {0}; + *result.u64 = IntFromPtr(entity); + + return result; } +/* NOTE(mallchad): munmap needs sizing data and I didn't know how to impliment + it without introducing a bunch of unnecesary complexity or changing the API, + hopefully it's okay but it doesn't really qualify as "threading entities" + anymore :/ */ internal void os_file_map_close(OS_Handle map) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(map); + msync(entity->map.data, entity->map.size, MS_SYNC); + B32 failure = munmap(entity->map.data, entity->map.size); + /* NOTE: It shouldn't be that important if filemap fails but it ideally shouldn't + happen particularly when dealing with gigabytes of memory. */ + Assert(failure == 0); + lnx_free_entity(entity); } +/* NOTE(mallcahd): It looks this was supposed to a way to make a sub-portion of a + file, presumably to make very large files reasonably sensible to manage. But right now +the usage seems to just read from 0 starting point always, so I'm just leaving it that way +for now. Both the win32 and linux backend will break if you try to use this differently for some reason at time of writing [2024-07-11 Thu 17:22] + +If you would like to complete this function to work the way outlined above, then take the first +page-boundary aligned boundary before offset */ internal void * os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) { - NotImplemented; - return 0; + LNX_Entity* entity = lnx_entity_from_handle(map); + S32 fd = entity->map.fd; + struct stat file_info; + fstat(fd, &file_info); + U64 filesize = file_info.st_size; + U64 range_size = range.max - range.min; + /* TODO(mallchad): I can't figure out how to get the exact huge page size, the + mmap offset will just error if its not exact + B32 use_huge = (size > os_large_page_size() && lnx_huge_page_enabled); + F32 huge_threshold = 0.5f; + F32 huge_use_1GB = (size > (1e9f * huge_threshold) &&use_huge); + U64 page_size = (huge_use_1GB ? huge_size : + use_huge ?lnx_huge_sze : lnx_page_size ); + U64 page_flag = huge_use_1GB ? MAP_HUGE_1GB : MAP_HUGE_2MB; + */ + U64 page_size = lnx_page_size; + U32 page_flag = 0x0; + U32 map_flags = page_flag | (flags & OS_AccessFlag_Shared ? MAP_SHARED : MAP_PRIVATE) | + MAP_POPULATE; + + U32 prot_flags = lnx_prot_from_os_flags(flags); + U64 aligned_offset = AlignDownPow2(range.min, lnx_page_size); + U64 view_start_from_offset = (aligned_offset % lnx_page_size); + U64 map_size = (view_start_from_offset + range_size); + + void *address = mmap(NULL, map_size, prot_flags, map_flags, fd, aligned_offset); + Assert("Mapping file into memory failed" && address > 0); + entity->map.data = address; + entity->map.size = map_size; + void* result = (address + view_start_from_offset); + + return result; } internal void os_file_map_view_close(OS_Handle map, void *ptr) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(map); + AssertAlways( entity->map.data && (entity->map.data != (void*)-1) ); + /* NOTE: Make sure contents are synced with OS on the off chance the backing + file isn't POSIX compliant. Use MS_ASYNC if you want performance. */ + msync(ptr, entity->map.size, MS_SYNC); + munmap(entity->map.data, entity->map.size); } //- rjf: directory iteration @@ -1232,21 +1640,72 @@ os_file_map_view_close(OS_Handle map, void *ptr) internal OS_FileIter * os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) { - NotImplemented; - return 0; + OS_FileIter* result = push_array(arena, OS_FileIter, 1); + result->flags = flags; + // String8 tmp = push_str8_copy(arena, path); + LNX_dir* directory = opendir((char*)path.str); + LNX_fd dir_fd = open((char*)path.str, 0x0, 0x0); + LNX_file_iter* out_iter = (LNX_file_iter*)result->memory; + out_iter->dir_stream = directory; + out_iter->dir_fd = dir_fd; + + // Never fail, just let the iterator return false. + return result; } + /* NOTE(mallchad): I have no idea what the return is for so it always returns true + unless the iterator is done */ internal B32 os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) { - NotImplemented; - return 0; + MemoryZeroStruct(info_out); + if (iter == NULL) { return 0; } + + LNX_dir* directory = NULL; + LNX_dir_entry* file = NULL; + LNX_fd working_path = -1; + FileProperties props = {0}; + LNX_fstat stats = {0}; + B32 no_file = 0; + + LNX_file_iter* iter_data = (LNX_file_iter*)iter->memory; + directory = iter_data->dir_stream; + working_path = iter_data->dir_fd; + if (directory == NULL || working_path == -1) { return 0; } + + // Should hopefully never infinite loop because readdir reaches NULL quickly + for (;;) + { + file = readdir(directory); + no_file = (file == NULL); + if (no_file) { iter->flags |= OS_FileIterFlag_Done; return 0; } + + if (iter->flags & OS_FileIterFlag_SkipFiles && file->d_type == DT_REG ) { continue; } + if (iter->flags & OS_FileIterFlag_SkipFolders && file->d_type == DT_DIR ) { continue; } + // Aparently this part of the API is in an indeterminate state. So hardcoding the behavior. + // if (iter->flags & OS_FileIterFlag_SkipHiddenFiles && file->d_name[0] == '.' ) { continue; } + if (file->d_name[0] == '.') { continue; } + break; + } + LNX_fd fd = openat(working_path, file->d_name, 0x0, 0x0); + Assert(fd != -1); if (fd == -1) { return 0; } + S32 stats_err = fstat(fd, &stats); + Assert(stats_err == 0); if (stats_err != 0) { return 0; } + close(fd); + + info_out->name = push_str8_copy(arena, str8_cstring(file->d_name)); + lnx_file_properties_from_stat(&info_out->props, &stats); + + return 1; } internal void os_file_iter_end(OS_FileIter *iter) { - NotImplemented; + LNX_file_iter* iter_info = (LNX_file_iter*)iter->memory; + int stream_failure = closedir(iter_info->dir_stream); + int fd_failure = close(iter_info->dir_fd); + Assert(stream_failure == 0 && fd_failure == 0); } //- rjf: directory creation @@ -1254,13 +1713,12 @@ os_file_iter_end(OS_FileIter *iter) internal B32 os_make_directory(String8 path) { - Temp scratch = scratch_begin(0, 0); - B32 result = false; - String8 name_copy = push_str8_copy(scratch.arena, name); - if (mkdir((char*)name_copy.str, 0777) != -1){ - result = true; - } - scratch_end(scratch); + B32 result = 0; + mkdir((char*)path.str, 0770); + DIR* dirfd = opendir((char*)path.str); + result = (ENOENT != errno); + closedir(dirfd); + return(result); } @@ -1271,7 +1729,21 @@ internal OS_Handle os_shared_memory_alloc(U64 size, String8 name) { OS_Handle result = {0}; - NotImplemented; + LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_MemoryMap); + // Create, fail if already open, rw-rw---- + S32 fd = shm_open((char*)name.str, O_RDWR | O_CREAT | O_EXCL, 660); + Assert("Failed to alloc shared memory" && fd != -1); + + B32 use_huge = (size > os_large_page_size() && lnx_huge_page_enabled); + U32 flag_huge = (use_huge ? MAP_HUGETLB : 0x0); + U32 flag_prot = PROT_READ | PROT_WRITE; + U32 map_flags = MAP_SHARED | flag_huge | MAP_POPULATE; + void* mapping = mmap(NULL, size, flag_prot, map_flags, fd, 0); + Assert("Failed map memory for shared memory" && mapping != MAP_FAILED); + + entity->map.data = mapping; + entity->map.size = size; + result = lnx_handle_from_entity(entity); return result; } @@ -1279,27 +1751,50 @@ internal OS_Handle os_shared_memory_open(String8 name) { OS_Handle result = {0}; - NotImplemented; + LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_MemoryMap); + LNX_fd fd = shm_open((char*)name.str, O_RDWR, 0x0 ); + Assert(fd != -1); + + entity->map.fd = fd; + entity->map.shm_name = name; + *result.u64 = IntFromPtr(entity); + return result; } internal void os_shared_memory_close(OS_Handle handle) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(handle); + shm_unlink( (char*)(entity->map.shm_name.str) ); } internal void * os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) { - NotImplemented; - return 0; + void* result = NULL; + LNX_Entity* entity = lnx_entity_from_handle(handle); + LNX_fd fd = entity->map.fd; + + B32 use_huge = (range.max > os_large_page_size() && lnx_huge_page_enabled); + U32 flag_huge = (use_huge ? MAP_HUGETLB : 0x0); + U32 flag_prot = PROT_READ | PROT_WRITE; + U32 map_flags = MAP_SHARED | flag_huge | MAP_POPULATE; + void* mapping = mmap(NULL, range.max, flag_prot, map_flags, fd, 0); + Assert("Failed map memory for shared memory" && mapping != MAP_FAILED); + + result = mapping + range.min; // Get the offset to the map opening + entity->map.data = mapping; + entity->map.size = range.max; + + return result; } internal void os_shared_memory_view_close(OS_Handle handle, void *ptr) { - NotImplemented; + LNX_Entity entity = *lnx_entity_from_handle(handle); + munmap(entity.map.data, entity.map.size); } //////////////////////////////// @@ -1358,7 +1853,11 @@ os_local_time_from_universal_time(DateTime *universal_time){ internal U64 os_now_microseconds(void){ struct timespec t; - clock_gettime(CLOCK_MONOTONIC, &t); + // NOTE: pedantic is it acutally worth it to use CLOCK_MONOTONIC_RAW? + // CLOCK_MONOTONIC is to occasional adjtime adjustments, the max error appears + // to be large. + // https://man7.org/linux/man-pages/man3/adjtime.3.html + clock_gettime(CLOCK_MONOTONIC_RAW, &t); U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); return(result); } @@ -1371,11 +1870,42 @@ os_sleep_milliseconds(U32 msec){ //////////////////////////////// //~ rjf: @os_hooks Child Processes (Implemented Per-OS) +/* NOTE: A terminal can be opened for any process but there is no default + terminal emulator on Linux, so instead you Have to cycle through a bunch of + common ones (there aren't that many). There is the XDG desktop specification + but that's a little bit of a chore to parse and its not even always present. + +The environment is inhereited by default but is easy to change in the future +with an execl variant. */ internal B32 -os_launch_process(OS_LaunchOptions *options){ +os_launch_process(OS_LaunchOptions *options, OS_Handle *handle_out) +{ + B32 success = 0; + Temp scratch = scratch_begin(0, 0); // TODO(allen): I want to redo this API before I bother implementing it here - NotImplemented; - return(false); + String8List cmdline_list = options->cmd_line; + char** cmdline = (char**)push_array(scratch.arena, char*, cmdline_list.node_count); + String8Node* x_node = cmdline_list.first; + for (int i=0; istring.str; + x_node = x_node->next; + } + + if (options->inherit_env) { NotImplemented; }; + if (options->consoleless == 0) { NotImplemented; }; + U32 pid = 0; + pid = fork(); + // Child + if (pid) + { + execvp((char*)options->path.str, cmdline); + success = 1; + exit(0); + } + // Parent + else { wait(NULL); } + return success; } //////////////////////////////// @@ -1406,7 +1936,7 @@ os_launch_thread(OS_ThreadFunctionType *func, void *ptr, void *params){ internal void os_release_thread_handle(OS_Handle thread){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(thread.id); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(thread.u64[0]); // remove my bit U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x1); // if the other bit is also gone, free entity @@ -1446,20 +1976,20 @@ os_mutex_alloc(void){ internal void os_mutex_release(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.u64[0]); pthread_mutex_destroy(&entity->mutex); lnx_free_entity(entity); } internal void os_mutex_take_(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.u64[0]); pthread_mutex_lock(&entity->mutex); } internal void os_mutex_drop_(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.u64[0]); pthread_mutex_unlock(&entity->mutex); } @@ -1469,38 +1999,59 @@ internal OS_Handle os_rw_mutex_alloc(void) { OS_Handle result = {0}; - NotImplemented; + LNX_rwlock_attr attr; + pthread_rwlockattr_init(&attr); + pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + LNX_rwlock rwlock = {0}; + int pthread_result = pthread_rwlock_init(&rwlock, &attr); + // This can be cleaned up now. + pthread_rwlockattr_destroy(&attr); + + if (pthread_result == 0) + { + LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Rwlock); + entity->rwlock = rwlock; + *result.u64 = IntFromPtr(entity); + } return result; } internal void os_rw_mutex_release(OS_Handle rw_mutex) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(rw_mutex); + pthread_rwlock_destroy(&entity->rwlock); } internal void os_rw_mutex_take_r_(OS_Handle mutex) { - NotImplemented; + // Is blocking varient + LNX_Entity* entity = lnx_entity_from_handle(mutex); + pthread_rwlock_rdlock(&entity->rwlock); } internal void os_rw_mutex_drop_r_(OS_Handle mutex) { - NotImplemented; + // NOTE: Aparently it results in undefined behaviour if there is no pre-existing lock + // https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_rwlock_unlock.html + LNX_Entity* entity = lnx_entity_from_handle(mutex); + pthread_rwlock_unlock(&entity->rwlock); } internal void os_rw_mutex_take_w_(OS_Handle mutex) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(mutex); + pthread_rwlock_rdlock(&entity->rwlock); } internal void os_rw_mutex_drop_w_(OS_Handle mutex) { - NotImplemented; + // NOTE: Should be the same thing + os_rw_mutex_drop_r_(mutex); } //- rjf: condition variables @@ -1513,8 +2064,11 @@ os_condition_variable_alloc(void){ // pthread pthread_condattr_t attr; pthread_condattr_init(&attr); - int pthread_result = pthread_cond_init(&entity->cond, &attr); + // Make sure condition uses CPU clock time + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC_RAW); + pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); pthread_condattr_destroy(&attr); + int pthread_result = pthread_cond_init(&entity->cond, &attr); if (pthread_result == -1){ lnx_free_entity(entity); entity = 0; @@ -1527,45 +2081,62 @@ os_condition_variable_alloc(void){ internal void os_condition_variable_release(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); pthread_cond_destroy(&entity->cond); lnx_free_entity(entity); } internal B32 os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ - B32 result = false; - LNX_Entity *entity_cond = (LNX_Entity*)PtrFromInt(cv.id); - LNX_Entity *entity_mutex = (LNX_Entity*)PtrFromInt(mutex.id); - // TODO(allen): implement the time control - pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex); + B32 result = 0; + LNX_Entity *entity_cond = lnx_entity_from_handle(cv); + LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex); + LNX_timespec timeout_stamp = lnx_now_precision_timespec(); + timeout_stamp.tv_nsec += endt_us * 1000; + + // The timeout is received as a system clock timespec of when to stop waiting + pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex, &timeout_stamp); return(result); } internal B32 os_condition_variable_wait_rw_r_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) { - NotImplemented; - return 0; + B32 result = 0; + LNX_Entity *entity_cond = lnx_entity_from_handle(cv); + LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex_rw); + LNX_timespec timeout_stamp = lnx_now_precision_timespec(); + timeout_stamp.tv_nsec += endt_us * 1000; + + // The timeout is received as a MONOTONIC_RAW clock timespec of when to stop waiting + pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex, &timeout_stamp); + return (result == 0); } internal B32 os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) { - NotImplemented; - return 0; + B32 result = 0; + LNX_Entity *entity_cond = lnx_entity_from_handle(cv); + LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex_rw); + LNX_timespec timeout_stamp = lnx_now_precision_timespec(); + timeout_stamp.tv_nsec += endt_us * 1000; + + // The timeout is received as a MONOTONIC_RAW clock timespec of when to stop waiting + result = pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex, &timeout_stamp); + return (result == 0); } internal void os_condition_variable_signal_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); pthread_cond_signal(&entity->cond); } internal void os_condition_variable_broadcast_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - DontCompile; + NotImplemented; + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); } //- rjf: cross-process semaphores @@ -1574,41 +2145,73 @@ internal OS_Handle os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) { OS_Handle result = {0}; - NotImplemented; + + // Create the named semaphore + // create | error if pre-existing , rw-rw---- + sem_t* semaphore = sem_open((char*)name.str, O_CREAT | O_EXCL, 660, initial_count); + if (semaphore != SEM_FAILED) + { + LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_Semaphore); + entity->semaphore.handle = semaphore; + entity->semaphore.max_value = max_count; + result = lnx_handle_from_entity(entity); + } return result; } internal void os_semaphore_release(OS_Handle semaphore) { - NotImplemented; + os_semaphore_close(semaphore); } internal OS_Handle os_semaphore_open(String8 name) { OS_Handle result = {0}; - NotImplemented; + LNX_Entity* handle = lnx_alloc_entity(LNX_EntityKind_Semaphore); + LNX_semaphore* semaphore; + semaphore = sem_open((char*)name.str, 0x0); + handle->semaphore.handle = semaphore; + Assert("Failed to open POSIX semaphore." || semaphore != SEM_FAILED); + + result = lnx_handle_from_entity(handle); return result; } internal void os_semaphore_close(OS_Handle semaphore) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(semaphore); + LNX_semaphore* _semaphore = entity->semaphore.handle; + sem_close(_semaphore); } internal B32 -os_semaphore_take(OS_Handle semaphore) +os_semaphore_take(OS_Handle semaphore, U64 endt_us) { - NotImplemented; - return 0; + U32 wait_result = 0; + LNX_timespec wait_until = lnx_now_precision_timespec(); + wait_until.tv_nsec += endt_us; + + LNX_Entity* entity = lnx_entity_from_handle(semaphore); + LNX_semaphore* _semaphore = entity->semaphore.handle; + // We have to impliment max_count ourselves + S32 current_value = 0; + sem_getvalue(_semaphore, ¤t_value); + if (entity->semaphore.max_value > current_value) + { + sem_timedwait(_semaphore, &wait_until); + } + return (wait_result != -1); } internal void os_semaphore_drop(OS_Handle semaphore) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(semaphore); + sem_t* _semaphore = entity->semaphore.handle; + sem_post(_semaphore); } //////////////////////////////// @@ -1629,7 +2232,7 @@ internal VoidProc * os_library_load_proc(OS_Handle lib, String8 name) { Temp scratch = scratch_begin(0, 0); - void *so = (void *)lib.id; + void *so = (void *)lib.u64[0]; char *name_cstr = (char *)push_str8_copy(scratch.arena, name).str; VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); scratch_end(scratch); @@ -1639,7 +2242,7 @@ os_library_load_proc(OS_Handle lib, String8 name) internal void os_library_close(OS_Handle lib) { - void *so = (void *)lib.id; + void *so = (void *)lib.u64[0]; dlclose(so); } @@ -1677,6 +2280,15 @@ os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, v internal OS_Guid os_make_guid(void) { - NotImplemented; -} + OS_Guid result = {0}; + LNX_uuid tmp;; + MemoryZeroArray(tmp); + uuid_generate(tmp); + MemoryCopy(&result.data1, 0+ tmp, 4); + MemoryCopy(&result.data2, 4+ tmp, 2); + MemoryCopy(&result.data3, 6+ tmp, 2); + MemoryCopy(&result.data4, 8+ tmp, 8); + + return result; +} diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.h b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.h index 9899f94a0..235aed81e 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.h +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.h @@ -7,25 +7,62 @@ //////////////////////////////// //~ NOTE(allen): Get all these linux includes -#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 +#include +#include +#include + +////////////////////////////////// +// Helper Typedefs + +// -- Time -- +// Time broken into major and minor parts +typedef struct timespec LNX_timespec; +typedef struct timeval LNX_timeval; +// Deconstructed Date-Time +typedef struct tm LNX_date; + +// -- File -- +// File Statistics +typedef S32 LNX_fd; +typedef struct stat LNX_fstat; +// Opaque directory stream of directory contents +typedef DIR LNX_dir; +// Opaque directory entry/file +typedef struct dirent LNX_dir_entry; + +// -- Other -- +typedef uuid_t LNX_uuid; + +// -- Syncronization Primitives-- +typedef sem_t LNX_semaphore; +typedef pthread_mutex_t LNX_mutex; +typedef pthread_mutexattr_t LNX_mutex_attr; +typedef pthread_rwlock_t LNX_rwlock; +typedef pthread_rwlockattr_t LNX_rwlock_attr; +typedef pthread_cond_t LNX_cond; //////////////////////////////// //~ NOTE(allen): File Iterator +typedef struct LNX_FileIter LNX_FileIter; struct LNX_FileIter{ int fd; DIR *dir; @@ -34,14 +71,18 @@ StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(LNX_FileIter), file_i //////////////////////////////// //~ NOTE(allen): Threading Entities - +typedef enum LNX_EntityKind LNX_EntityKind; enum LNX_EntityKind{ LNX_EntityKind_Null, LNX_EntityKind_Thread, LNX_EntityKind_Mutex, + LNX_EntityKind_Rwlock, LNX_EntityKind_ConditionVariable, + LNX_EntityKind_Semaphore, + LNX_EntityKind_MemoryMap, }; +typedef struct LNX_Entity LNX_Entity; struct LNX_Entity{ LNX_Entity *next; LNX_EntityKind kind; @@ -52,14 +93,27 @@ struct LNX_Entity{ void *ptr; pthread_t handle; } thread; - pthread_mutex_t mutex; - pthread_cond_t cond; + struct{ + sem_t* handle; + U32 max_value; + } semaphore; + struct{ + S32 fd; + U32 flags; // TODO: Maybe delete + void* data; + U64 size; + String8 shm_name; + } map; + LNX_mutex mutex; + LNX_rwlock rwlock; + LNX_cond cond; }; }; //////////////////////////////// //~ NOTE(allen): Safe Call Chain +typedef struct LNX_SafeCallChain LNX_SafeCallChain; struct LNX_SafeCallChain{ LNX_SafeCallChain *next; OS_ThreadFunctionType *fail_handler; @@ -69,13 +123,51 @@ struct LNX_SafeCallChain{ //////////////////////////////// //~ NOTE(allen): Helpers +// Helper Structs +typedef struct LNX_version LNX_version; +struct LNX_version { + U32 major; + U32 minor; + U32 patch; + String8 string; +}; + +typedef struct LNX_file_iter LNX_file_iter; +struct LNX_file_iter { + LNX_dir* dir_stream; + S32 dir_fd; +}; + internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list); + /* Helper function to return a timespec using a adjtime stable high precision clock + This should not be affected by setting the system clock or RTC*/ +internal LNX_timespec lnx_now_precision_timespec(); +// Get the current system time that is affected by setting the system clock +internal LNX_timespec lnx_now_system_timespec(); +// Typecast Functions internal void lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec); internal void lnx_tm_from_date_time(struct tm *out, DateTime *in); -internal void lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in); +internal void lnx_timespec_from_date_time(LNX_timespec* out, DateTime* in); +internal void lnx_timeval_from_date_time(LNX_timeval* out, DateTime* in); +internal void lnx_dense_time_from_timespec(DenseTime *out, LNX_timespec *in); +internal void lnx_timeval_from_dense_time(LNX_timeval* out, DenseTime* in); +internal void lnx_timespec_from_dense_time(LNX_timespec* out, DenseTime* in); +internal void lnx_timespec_from_timeval(LNX_timespec* out, LNX_timeval* in); +internal void lnx_timeval_from_timespec(LNX_timeval* out, LNX_timespec* in); internal void lnx_file_properties_from_stat(FileProperties *out, struct stat *in); +// Convert OS_AccessFlags to 'mmap' compatible 'PROT_' flags +internal U32 lnx_prot_from_os_flags(OS_AccessFlags flags); +// Convert OS_AccessFlags to 'open' compatible 'O_' flags +internal U32 lnx_open_from_os_flags(OS_AccessFlags flags); + +// Stable and consistent handle conversions +internal U32 lnx_fd_from_handle(OS_Handle file); +internal OS_Handle lnx_handle_from_fd(U32 fd); +internal LNX_Entity* lnx_entity_from_handle(OS_Handle handle); +internal OS_Handle lnx_handle_from_entity(LNX_Entity* entity); + internal String8 lnx_string_from_signal(int signum); internal String8 lnx_string_from_errno(int error_number); @@ -83,6 +175,6 @@ internal LNX_Entity* lnx_alloc_entity(LNX_EntityKind kind); internal void lnx_free_entity(LNX_Entity *entity); internal void* lnx_thread_base(void *ptr); -internal void lnx_safe_call_sig_handler(int); +internal void lnx_safe_call_sig_handler(int _); #endif //LINUX_H diff --git a/src/metagen/metagen_os/core/metagen_os_core.c b/src/metagen/metagen_os/core/metagen_os_core.c index 0323091d2..8500c4e7a 100644 --- a/src/metagen/metagen_os/core/metagen_os_core.c +++ b/src/metagen/metagen_os/core/metagen_os_core.c @@ -86,10 +86,15 @@ os_relaunch_self(void){ internal String8 os_data_from_file_path(Arena *arena, String8 path) { - OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_Shared, path); + Temp scratch = scratch_begin(0, 0); + // Create null terminated copy + String8 _path = push_str8_copy(scratch.arena, path); + + OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_Shared, _path); FileProperties props = os_properties_from_file(file); String8 data = os_string_from_file_range(arena, file, r1u64(0, props.size)); os_file_close(file); + scratch_end(scratch); return data; } diff --git a/src/metagen/metagen_os/core/win32/metagen_os_core_win32.c b/src/metagen/metagen_os/core/win32/metagen_os_core_win32.c index 19f5260b8..bbeea6c03 100644 --- a/src/metagen/metagen_os/core/win32/metagen_os_core_win32.c +++ b/src/metagen/metagen_os/core/win32/metagen_os_core_win32.c @@ -703,7 +703,7 @@ os_file_set_times(OS_Handle file, DateTime time) 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)); + SetFileTime(handle, NULL, &file_time, &file_time)); return result; } diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 8a6db8434..a97697e2d 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1,31 +1,58 @@ // Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#include - //////////////////////////////// //~ rjf: Globals global pthread_mutex_t lnx_mutex = {0}; global Arena *lnx_perm_arena = 0; -global String8List lnx_cmd_line_args = {0}; -global LNX_Entity lnx_entity_buffer[1024]; +global LNX_Entity lnx_entity_buffer[16384]; global LNX_Entity *lnx_entity_free = 0; global String8 lnx_initial_path = {0}; thread_static LNX_SafeCallChain *lnx_safe_call_chain = 0; +global U64 lnx_page_size = 4096; +// TODO(mallchad): This can't be used until the huge page allocation count is checked +// TODO(mallchad): Pretty sure this can be used now???? +global B32 lnx_huge_page_enabled = 0; +global B32 lnx_huge_page_use_1GB = 0; +global U16 lnx_ring_buffers_created = 0; +global U16 lnx_ring_buffers_limit = 65000; +global String8List lnx_environment = {0}; +global U64 lnx_hugepage_count_2MB = 0; +global U32 lnx_hugepage_count_min = 250; // (500 MB/2MB) + +global String8 lnx_hostname = {0}; +global LNX_version lnx_kernel_version = {0}; +global String8 lnx_architecture = {0}; +global String8 lnx_kernel_type = {0}; + +////////////////////////////////n +// Forward Declares +// NOTE: Half of these are just to silence the compiler. +int +memfd_create (const char *__name, unsigned int __flags) __THROW; +ssize_t +copy_file_range (int __infd, __off64_t *__pinoff, + int __outfd, __off64_t *__poutoff, + size_t __length, unsigned int __flags); +extern int +creat(const char *__file, mode_t __mode); + + //////////////////////////////// //~ rjf: Helpers +// TODO: Marked as old / review internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list){ - B32 success = true; - + B32 success = 1; + String8Node *node = list.first; if (node != 0){ U8 *ptr = node->string.str;; U8 *opl = ptr + node->string.size; - + U64 p = 0; for (;p < list.total_size;){ U64 amt64 = (U64)(opl - ptr); @@ -36,13 +63,13 @@ lnx_write_list_to_file_descriptor(int fd, String8List list){ } p += written_amt; ptr += written_amt; - + Assert(ptr <= opl); if (ptr == opl){ node = node->next; if (node == 0){ if (p < list.total_size){ - success = false; + success = 0; } break; } @@ -51,10 +78,11 @@ lnx_write_list_to_file_descriptor(int fd, String8List list){ } } } - + return(success); } +// TODO: Marked as old / review internal void lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec){ out->msec = msec; @@ -67,6 +95,7 @@ lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec){ out->year = in->tm_year + 1900; } +// TODO: Marked as old / review internal void lnx_tm_from_date_time(struct tm *out, DateTime *in){ out->tm_sec = in->sec; @@ -77,6 +106,16 @@ lnx_tm_from_date_time(struct tm *out, DateTime *in){ out->tm_year = in->year - 1900; } +internal void lnx_timespec_from_date_time(LNX_timespec* out, DateTime* in) +{ + NotImplemented; +} + +internal void lnx_timeval_from_date_time(LNX_timeval* out, DateTime* in) +{ + NotImplemented; +} + internal void lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in){ struct tm tm_time = {0}; @@ -86,6 +125,42 @@ lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in){ *out = dense_time_from_date_time(date_time); } +internal void +lnx_timeval_from_dense_time(LNX_timeval* out, DenseTime* in) +{ + // Miliseconds to Microseconds, should be U64 to long + out->tv_sec = 0; + out->tv_usec = 1000* (*in); +} + +internal void +lnx_timespec_from_dense_time(LNX_timespec* out, DenseTime* in) +{ + // Miliseconds to Seconds, should be U64 to long + out->tv_sec = (*in / 1000); + out->tv_nsec = 0; +} + +void +lnx_timespec_from_timeval(LNX_timespec* out, LNX_timeval* in ) +{ + out->tv_sec = in->tv_sec; + out->tv_nsec = in->tv_usec / 1000; +} + +void +lnx_timeval_from_timespec(LNX_timeval* out, LNX_timespec* in ) +{ + out->tv_sec = in->tv_sec; + out->tv_usec = in->tv_nsec * 1000; +} + +/* NOTE: ctime has very little to do with "creation time"- it's more about + inode modifications -but for this purpose it's usually considered the closest + analogue. Manage your own file-creation data if you actually want that info. + + There's way more info to draw from but leaving for now + https://man7.org/linux/man-pages/man3/stat.3type.html */ internal void lnx_file_properties_from_stat(FileProperties *out, struct stat *in){ MemoryZeroStruct(out); @@ -97,6 +172,63 @@ lnx_file_properties_from_stat(FileProperties *out, struct stat *in){ } } +internal U32 +lnx_prot_from_os_flags(OS_AccessFlags flags) +{ + U32 result = 0x0; + if (flags & OS_AccessFlag_Read) { result |= PROT_READ; } + if (flags & OS_AccessFlag_Write) { result |= PROT_WRITE; } + if (flags & OS_AccessFlag_Execute) { result |= PROT_EXEC; } + return result; +} + +internal U32 +lnx_open_from_os_flags(OS_AccessFlags flags) +{ + U32 result = 0x0; + // read/write flags are mutually exclusie on Linux `open()` + if ((flags & OS_AccessFlag_Write) && + (flags & OS_AccessFlag_Read)) { result |= O_RDWR | O_CREAT; } + else if (flags & OS_AccessFlag_Read) { result |= O_RDONLY; } + else if (flags & OS_AccessFlag_Write) { result |= O_WRONLY | O_CREAT; } + + // Doesn't make any sense on Linux, use os_file_map_open for execute permissions + // else if (flags & OS_AccessFlag_Execute) {} + // Shared doesn't make sense on Linux, file locking is explicit not set at open + // if(flags & OS_AccessFlag_Shared) {} + + return result; +} + +internal U32 + lnx_fd_from_handle(OS_Handle file) +{ + return *file.u64; +} + +internal OS_Handle +lnx_handle_from_fd(U32 fd) +{ + OS_Handle result = {0}; + *result.u64 = fd; + return result; +} + +internal LNX_Entity* +lnx_entity_from_handle(OS_Handle handle, LNX_EntityKind type) +{ + LNX_Entity* result = (LNX_Entity*)PtrFromInt(*handle.u64); + if (*handle.u64) { Assert(result->kind == type); } + return result; +} +internal OS_Handle lnx_handle_from_entity(LNX_Entity* entity) +{ + OS_Handle result = {0}; + *result.u64 = IntFromPtr(entity); + return result; +} + +// TODO: Marked as old / review internal String8 lnx_string_from_signal(int signum){ String8 result = str8_lit(""); @@ -229,6 +361,7 @@ lnx_string_from_signal(int signum){ return(result); } +// TODO: Marked as old / review internal String8 lnx_string_from_errno(int error_number){ String8 result = str8_lit(""); @@ -761,6 +894,7 @@ lnx_string_from_errno(int error_number){ return(result); } +// TODO: Marked as old / review internal LNX_Entity* lnx_alloc_entity(LNX_EntityKind kind){ pthread_mutex_lock(&lnx_mutex); @@ -772,6 +906,7 @@ lnx_alloc_entity(LNX_EntityKind kind){ return(result); } +// TODO: Marked as old / review internal void lnx_free_entity(LNX_Entity *entity){ entity->kind = LNX_EntityKind_Null; @@ -780,17 +915,19 @@ lnx_free_entity(LNX_Entity *entity){ pthread_mutex_unlock(&lnx_mutex); } +// TODO: Marked as old / review internal void* lnx_thread_base(void *ptr){ LNX_Entity *entity = (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(); - + // remove my bit U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x2); // if the other bit is also gone, free entity @@ -800,8 +937,9 @@ lnx_thread_base(void *ptr){ return(0); } +// TODO: Marked as old / review internal void -lnx_safe_call_sig_handler(int){ +lnx_safe_call_sig_handler(int _){ LNX_SafeCallChain *chain = lnx_safe_call_chain; if (chain != 0 && chain->fail_handler != 0){ chain->fail_handler(chain->ptr); @@ -809,9 +947,27 @@ lnx_safe_call_sig_handler(int){ abort(); } +internal LNX_timespec +lnx_now_precision_timespec() +{ + LNX_timespec result; + clock_gettime(CLOCK_MONOTONIC_RAW, &result); + + return result; +} + +internal LNX_timespec lnx_now_system_timespec() +{ + LNX_timespec result; + clock_gettime(CLOCK_REALTIME, &result); + return result; +} + + //////////////////////////////// //~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) +// TODO: Marked as old / review internal void os_init(void) { @@ -834,44 +990,125 @@ os_init(void) } ptr->next = 0; } - + // NOTE(allen): Permanent memory allocator for this layer Arena *perm_arena = arena_alloc(); lnx_perm_arena = perm_arena; - - // NOTE(allen): Initialize Paths - lnx_initial_path = os_get_path(lnx_perm_arena, OS_SystemPath_Current); - - // NOTE(rjf): Setup command line args - lnx_cmd_line_args = os_string_list_from_argcv(lnx_perm_arena, argc, argv); + + // Initialize Paths + // Don't make assumptions just let the path be as big as it needs to be + U8 _initial_path[1000]; + S32 _initial_size = readlink("/proc/self/exe", (char*)_initial_path, 1000); + + if (_initial_size > 0) + { + String8 _initial_tmp = str8(_initial_path, _initial_size); + str8_chop_last_slash(_initial_tmp); + lnx_initial_path = push_str8_copy(lnx_perm_arena, _initial_tmp); + } + // Give empty string for relative path on the offchance it fails + else { lnx_initial_path = str8_lit(""); } + + // Environment initialization + Temp scratch = scratch_begin(0, 0); + String8 env; + LNX_fd env_fd = open("/proc/self/environ", O_RDONLY); + U64 env_limit = 100000; + U8* environ_buffer = push_array(scratch.arena, U8, env_limit); + // *SIGH*, the 'environ' file doesn't have a size sso 'os_file_read' can't query it. + U64 read_success = read(env_fd, environ_buffer, env_limit); + close(env_fd); + + if (read_success) + { + for (U8* x_string=(U8*)environ_buffer; + (*x_string != 0 && x_string (1e9f * huge_threshold); + U32 page_size = huge_use_1GB ? MAP_HUGE_1GB : MAP_HUGE_2MB; + void *result; + if (lnx_huge_page_enabled) + { + /* NOTE: Huge pages are permanantly in memory unless paged out under high + memory pressure, MAP_POPULATE is probably not relevant, but you can mlock + it anyway if you want to be sure */ + result = mmap(0, size, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | page_size, + -1, 0); + } else { result = os_reserve(size); } + + return result; } internal B32 os_commit_large(void *ptr, U64 size){ - NotImplemented; - return 0; + U32 error = mprotect(ptr, size, PROT_READ|PROT_WRITE); + mlock(ptr, size); + munlock(ptr, size); + return (error == 0); } internal void @@ -885,95 +1122,149 @@ os_release(void *ptr, U64 size){ munmap(ptr, size); } -internal void +// Enable huge pages +// NOTE: Linux has huge pages instead of larges pages but there's no "nice" way to +// enable them from an unprivillaged application, that is unless you want to +// spawn a subprocess just for performing privileged actions +internal B32 os_set_large_pages(B32 flag) { - NotImplemented; + lnx_huge_page_enabled = os_large_pages_enabled(); + return (lnx_huge_page_enabled == flag); } internal B32 os_large_pages_enabled(void) { - NotImplemented; - return 0; -} - + /* NOTE(mallchad): This is aparently the reccomended way to check for hugepage support. + Do not ask... */ + U8 buffer[32]; + MemoryZeroArray(buffer); + LNX_fd fd = open("/proc/sys/vm/nr_hugepages", O_RDONLY); + // NOTE: Fake files have fake behaviour. Keep making this mistake. Virtual files have no size. + + String8 data = {0}; + Assert(fd >= 0); // Something is seriously wrong if we can't access this file + if (fd < 0) { return 0; } + + data.str = buffer; + data.size = read(fd, buffer, 32); + Assert(data.size > 0); // Probably indicative of platform quirk + if ( data.size <= 0) { return 0; } // Guard against underflow + U8 last_char = (data.str[ data.size - 1]); + if (last_char == '\n') { --data.size; } + close(fd); + + U64 hugepage_count = 0; + hugepage_count = u64_from_str8(data, 10); + B32 enable_largepages = (hugepage_count > lnx_hugepage_count_min); + + return enable_largepages; +} + +/* NOTE: The size seems to be consistent across Linux systems, it's configurable + but the use case seems to be niche enough and the interface weird enough that + most people won't do it. The `/proc/meminfo` virtual file can be queried for + hugepage size. */ internal U64 os_large_page_size(void) { - NotImplemented; - return 0; + U64 size = (lnx_huge_page_use_1GB ? GB(1) : MB(2)); + return size; } internal void* os_alloc_ring_buffer(U64 size, U64 *actual_size_out) { - NotImplemented; - return 0; + Temp scratch = scratch_begin(0, 0); + void* result = NULL; + + // Make fle descriptor + String8 base = str8_lit("metagen_ring_xxxx"); + String8 filename_anonymous = push_str8_copy(scratch.arena, base); + base16_from_data(filename_anonymous.str + filename_anonymous.size -4, + (U8*)&lnx_ring_buffers_created, + sizeof(lnx_ring_buffers_created)); + if (lnx_ring_buffers_created < lnx_ring_buffers_limit) + { + B32 use_huge = (size > os_large_page_size() && lnx_huge_page_enabled); + U32 flag_huge1 = (use_huge ? MFD_HUGETLB : 0x0); + U32 flag_huge2 = (use_huge ? (MAP_HUGETLB | MAP_HUGE_1GB) : 0x0); + U32 flag_prot = PROT_READ | PROT_WRITE; + U32 flag_map_initial = (flag_huge2 | MAP_ANONYMOUS | MAP_SHARED | MAP_POPULATE); + U32 flag_map_ring = (MAP_FIXED | MAP_SHARED); + + /* mmap circular buffer trick, create twice the buffer and double map it + with a shared file descriptor. Make sure to prevent mmap from changing + location with MAP_FIXED */ + S32 fd = memfd_create((const char*)filename_anonymous.str, flag_huge1); + Assert(fd != -1); + /* NOTE(mallchad): This is your warnnig to remember to resize/truncate + memory files before writing, this burned me like 3 times already */ + ftruncate(fd, size); + + result = mmap(NULL, 2* size, flag_prot, flag_map_initial, -1, 0); + void* ring_head = mmap(result, size, flag_prot, flag_map_ring, fd, 0); + void* ring_tail = mmap(result + size, size, flag_prot, flag_map_ring, fd, 0); + Assert(ring_head != (void*)-1); + Assert(ring_tail != (void*)-1); + *actual_size_out = size*2; + // Closing fd doesn't invalidate mapping + close(fd); + } + scratch_end(scratch); + return result; } internal void os_free_ring_buffer(void *ring_buffer, U64 actual_size) { - NotImplemented; + munmap(ring_buffer, actual_size); } //////////////////////////////// //~ rjf: @os_hooks System Info (Implemented Per-OS) +/* NOTE: Cache the result if you don't want to re-trigger the lookup */ internal String8 os_machine_name(void){ - local_persist B32 first = true; - local_persist String8 name = {0}; - - // TODO(allen): let's just pre-compute this at init and skip the complexity + String8 result = {0}; + pthread_mutex_lock(&lnx_mutex); - if (first){ - Temp scratch = scratch_begin(0, 0); - first = false; - - // get name - B32 got_final_result = false; - U8 *buffer = 0; - int size = 0; - for (S64 cap = 4096, r = 0; - r < 4; - cap *= 2, r += 1){ - scratch.restore(); - buffer = push_array_no_zero(scratch.arena, U8, cap); - size = gethostname((char*)buffer, cap); - if (size < cap){ - got_final_result = true; - break; - } - } - - // save string - if (got_final_result && size > 0){ - name.size = size; - name.str = push_array_no_zero(lnx_perm_arena, U8, name.size + 1); - MemoryCopy(name.str, buffer, name.size); - name.str[name.size] = 0; - } - - scratch_end(scratch); + Temp scratch = scratch_begin(0, 0); + + // NOTE: Hostname spec caps at 253 ASCII chars its a problem if its larger + U8* buffer = push_array(scratch.arena, U8, 256); + B32 failure = gethostname((char*)buffer, 256); + + if (failure == 0) + { + result = push_str8_copy(lnx_perm_arena, str8_cstring((char*)buffer)); } + scratch_end(scratch); pthread_mutex_unlock(&lnx_mutex); - - return(name); + + return result; } +/* Precomptued at init + NOTE: This setting can actually be changed at runtime, its not a super +important thing but it might be a better user experience if they can fix/improve +it without restarting. */ internal U64 -os_page_size(void){ - int size = getpagesize(); - return((U64)size); +os_page_size(void) +{ + return lnx_page_size; } internal U64 os_allocation_granularity(void) { - // On linux there is no equivalent of "dwAllocationGranularity" - os_page_size(); + /* On linux there is no equivalent of "dwAllocationGranularity" but you can + set the page size only if you have root or similar privillages. You could see + if there is a CAP privillage you can set for this. There are 2 huge page + sizes and 1 normal page size however */ + return os_page_size(); } internal U64 @@ -989,7 +1280,9 @@ os_logical_core_count(void) internal String8List os_get_command_line_arguments(void) { - return lnx_cmd_line_args; + String8List result = {0}; + NotImplemented; /* This doesn't appear to be used any longer */ + return result; } internal S32 @@ -1012,65 +1305,65 @@ os_get_tid(void){ internal String8List os_get_environment(void) { - NotImplemented; - String8List result = {0}; - return result; + return lnx_environment; } +// TODO: Marked as old / review internal U64 os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *out){ U64 result = 0; - + switch (path){ case OS_SystemPath_Binary: { - local_persist B32 first = true; + local_persist B32 first = 1; local_persist String8 name = {0}; - + // TODO(allen): let's just pre-compute this at init and skip the complexity pthread_mutex_lock(&lnx_mutex); if (first){ Temp scratch = scratch_begin(&arena, 1); - first = false; - + first = 0; + // get self string - B32 got_final_result = false; + B32 got_final_result = 0; U8 *buffer = 0; int size = 0; + /* NOTE: PATH_MAX aparently lies about the max path size so its + suggested to redo if it hits the max size */ for (S64 cap = PATH_MAX, r = 0; r < 4; cap *= 2, r += 1){ - scratch.restore(); buffer = push_array_no_zero(scratch.arena, U8, cap); size = readlink("/proc/self/exe", (char*)buffer, cap); if (size < cap){ - got_final_result = true; + got_final_result = 1; break; } } - + // save string if (got_final_result && size > 0){ String8 full_name = str8(buffer, size); - String8 name_chopped = string_path_chop_last_slash(full_name); + String8 name_chopped = str8_chop_last_slash(full_name); name = push_str8_copy(lnx_perm_arena, name_chopped); } - + scratch_end(scratch); } pthread_mutex_unlock(&lnx_mutex); - + result = 1; str8_list_push(arena, out, name); }break; - + case OS_SystemPath_Initial: { Assert(lnx_initial_path.str != 0); result = 1; str8_list_push(arena, out, lnx_initial_path); }break; - + case OS_SystemPath_Current: { char *cwdir = getcwd(0, 0); @@ -1079,7 +1372,7 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o result = 1; str8_list_push(arena, out, string); }break; - + case OS_SystemPath_UserProgramData: { char *home = getenv("HOME"); @@ -1087,7 +1380,7 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o result = 1; str8_list_push(arena, out, string); }break; - + case OS_SystemPath_ModuleLoad: { // TODO(allen): this one is big and complicated and only needed for making @@ -1095,10 +1388,23 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o NotImplemented; }break; } - + return(result); } +/* NOTE: It was tempting to use 'pthread_setname_np' but that is bound to glibc + and potentially the best compat would be free of glibc specific functions. + + Also aparently the Linux kernel enforces a 16 char thread name- the process + name/cmdline can be arbitrarily long + TODO: Rename cmdline to desired thread name*/ +internal void +os_set_thread_name(String8 string) +{ + LNX_thread self = pthread_self(); + prctl(PR_SET_NAME, string.str); +} + //////////////////////////////// //~ rjf: @os_hooks Process Control (Implemented Per-OS) @@ -1116,60 +1422,148 @@ internal OS_Handle os_file_open(OS_AccessFlags flags, String8 path) { OS_Handle file = {0}; - NotImplemented; + U32 access_flags = lnx_open_from_os_flags(flags); + + /* NOTE(mallchad): openat is supposedly meant to help prevent race conditions + with file moving or possibly a better way to put it is it helps resist + tampering with the referenced through relinks (assumption), ie symlink / mv . + Hopefully its more robust- we can close the dirfd it's kernel managed + now. The working directory can be changed but I don't know how best to + utilize it so leaving it for now. */ + + // String8 file_dir = str8_chop_last_slash(path); + // S32 dir_fd = open(file_dir, O_PATH); + // S32 fd = openat(fle_dir, path.str, access_flags); + + // Create file on write if doesn't exist, mode: rw-rw---- + S32 fd = openat(AT_FDCWD, (char*)path.str, access_flags, 0660); + // close(dirfd); + + // No Error + if (fd != -1) + { + *file.u64 = fd; + } return file; } internal void os_file_close(OS_Handle file) { - NotImplemented; + if(os_handle_match(file, os_handle_zero())) { return; } + S32 fd = *file.u64; + close(fd); } +/* NOTE: You can just use file maps on Linux and map the whole file because it + doesn't commit it to physical memory immediately and so shouldn't cost anything over file + mappings. Use MADV prefetch hints for performance. + NOTE: Aparently there is a race condition in relation to `stat` and + `lstat` so using fstat instead + https://cwe.mitre.org/data/definitions/367.html */ internal U64 os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) { - NotImplemented; - return 0; + S32 fd = *file.u64; + struct stat file_info; + fstat(fd, &file_info); + U64 filesize = file_info.st_size; + + // Make sure not to read more than the size of the file + Rng1U64 clamped = r1u64(ClampTop(rng.min, filesize), ClampTop(rng.max, filesize)); + U64 read_amount = clamped.max - clamped.min; + lseek(fd, clamped.min, SEEK_SET); + S64 read_bytes = read(fd, out_data, read_amount); + + // Return 0 instead of -1 on error + return (read_bytes > 0 ? read_bytes : 0) ; } internal void os_file_write(OS_Handle file, Rng1U64 rng, void *data) { - NotImplemented; + // Zero Valid Argument, often but not always STDIN + if ((*file.u64 < 0) || (data == NULL) || (rng.min - rng.max == 0)) { return; } + S32 fd = lnx_fd_from_handle(file); + LNX_fstat file_info; + fstat(fd, &file_info); + U64 filesize = file_info.st_size; + U64 write_size = rng.max - rng.min; + + AssertAlways(write_size > 0); + lseek(fd, rng.min, SEEK_SET); + // Expands file size if written off file end + U64 bytes_written = write(fd, data, write_size); + Assert("Zero or less written bytes is usually inticative of a bug" && bytes_written > 0); } internal B32 os_file_set_times(OS_Handle file, DateTime time) { - NotImplemented; + S32 fd = *file.u64; + LNX_timeval access_and_modification[2]; + DenseTime tmp = dense_time_from_date_time(time); + lnx_timeval_from_dense_time(access_and_modification, &tmp); + access_and_modification[1] = access_and_modification[0]; + B32 error = futimes(fd, access_and_modification); + + return (error != -1); } internal FileProperties os_properties_from_file(OS_Handle file) { - FileProperties props = {0}; - NotImplemented; - return props; + FileProperties result = {0}; + S32 fd = *file.u64; + LNX_fstat props = {0}; + B32 error = fstat(fd, &props); + + if (error == 0) + { + lnx_file_properties_from_stat(&result, &props); + } + return result; +} + +internal FileProperties +os_properties_from_file_path(String8 path) +{ + FileProperties result = {0}; + LNX_fstat props = {0}; + B32 error = stat((char*)path.str, &props); + if (error == 0) + { + lnx_file_properties_from_stat(&result, &props); + } + return result; } internal OS_FileID os_id_from_file(OS_Handle file) { - // TODO(nick): querry struct stat with fstat(2) and use st_dev and st_ino as ids - OS_FileID id = {0}; - NotImplemented; - return id; + OS_FileID result = {0}; + U32 fd = *file.u64; + LNX_fstat props = {0}; + B32 error = fstat(fd, &props); + + if (error == 0) + { + result.v[0] = props.st_dev; + result.v[1] = props.st_ino; + result.v[2] = 0; + } + return result; } +// TODO: Marked as old / review internal B32 os_delete_file_at_path(String8 path) { Temp scratch = scratch_begin(0, 0); - B32 result = false; - String8 name_copy = push_str8_copy(scratch.arena, name); + B32 result = 0; + String8 name_copy = push_str8_copy(scratch.arena, path); if (remove((char*)name_copy.str) != -1){ - result = true; + result = 1; } scratch_end(scratch); return(result); @@ -1178,61 +1572,145 @@ os_delete_file_at_path(String8 path) internal B32 os_copy_file_path(String8 dst, String8 src) { - NotImplemented; - return 0; + S32 source_fd = open((char*)src.str, 0x0, O_RDONLY); + S32 dest_fd = creat((char*)dst.str, O_WRONLY); + LNX_fstat props = {0}; + + S32 filesize = 0; + S32 bytes_written = 0; + B32 success = 0; + + fstat(source_fd, &props); + filesize = props.st_size; + + if (source_fd == 0 && dest_fd == 0) + { + bytes_written = copy_file_range(source_fd, NULL, dest_fd, NULL, filesize, 0x0); + success = (bytes_written == filesize); + } + close(source_fd); + close(dest_fd); + return success; } internal String8 os_full_path_from_path(Arena *arena, String8 path) { - // TODO: realpath can be used to resolve full path - String8 result = {0}; - NotImplemented; - return result; + String8 tmp = {0}; + /* NOTE: PATH_MAX aparently lies about the max path size so its suggested to + redo if it hits the max size */ + char buffer[PATH_MAX+10]; + MemoryZeroArray(buffer); + char* success = realpath((char*)path.str, buffer); + if (success) + { + tmp = str8_cstring(buffer); + } + return (push_str8_copy(lnx_perm_arena, tmp)); } internal B32 os_file_path_exists(String8 path) { - NotImplemented; - return 0; -} - -internal FileProperties -os_properties_from_file_path(String8 path) -{ - FileProperties props = {0}; - NotImplemented; - return props; + LNX_fstat _stub; + B32 exists = (0 == stat((char*)path.str, &_stub)); + return exists; } //- rjf: file maps +/* Does not map a view of the file into memory until a view is opened + 'OS_AccessFlags' is not relevant here */ internal OS_Handle os_file_map_open(OS_AccessFlags flags, OS_Handle file) { - NotImplemented; - OS_Handle handle = {0}; - return handle; + OS_Handle result = {0}; + + if (*file.u64 == 0) { return result; } + LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_MemoryMap); + S32 fd = *file.u64; + entity->map.fd = fd; + *result.u64 = IntFromPtr(entity); + + return result; } +/* NOTE(mallchad): munmap needs sizing data and I didn't know how to impliment + it without introducing a bunch of unnecesary complexity or changing the API, + hopefully it's okay but it doesn't really qualify as "threading entities" + anymore :/ */ internal void os_file_map_close(OS_Handle map) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(map, LNX_EntityKind_MemoryMap); + msync(entity->map.data, entity->map.size, MS_SYNC); + B32 failure = munmap(entity->map.data, entity->map.size); + /* NOTE: It shouldn't be that important if filemap fails but it ideally shouldn't + happen particularly when dealing with gigabytes of memory. */ + Assert(failure == 0); + lnx_free_entity(entity); } +/* NOTE(mallcahd): It looks this was supposed to a way to make a sub-portion of + a file, presumably to make very large files reasonably sensible to manage. But + right now the usage seems to just read from 0 starting point always, so I'm + just leaving it that way for now. Both the win32 and linux backend will break + if you try to use this differently for some reason at time of writing + [2024-07-11 Thu 17:22] + + If you would like to complete this function to work the way outlined above, + then take the first page-boundary aligned boundary before offset. + NOTE: You can try using MADV prefetch hints for performance. */ internal void * os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) { - NotImplemented; - return 0; + LNX_Entity* entity = lnx_entity_from_handle(map, LNX_EntityKind_MemoryMap); + if (entity == 0) { return NULL; } + + S32 fd = entity->map.fd; + struct stat file_info; + fstat(fd, &file_info); + U64 filesize = file_info.st_size; + U64 range_size = range.max - range.min; + /* TODO(mallchad): I can't figure out how to get the exact huge page size, the + mmap offset will just error if its not exact + B32 use_huge = (size > os_large_page_size() && lnx_huge_page_enabled); + F32 huge_threshold = 0.5f; + F32 huge_use_1GB = (size > (1e9f * huge_threshold) &&use_huge); + U64 page_size = (huge_use_1GB ? huge_size : + use_huge ?lnx_huge_sze : lnx_page_size ); + U64 page_flag = huge_use_1GB ? MAP_HUGE_1GB : MAP_HUGE_2MB; + */ + U64 page_size = lnx_page_size; + U32 page_flag = 0x0; + U32 map_flags = page_flag | MAP_POPULATE; + map_flags |= (flags & OS_AccessFlag_ShareRead ? MAP_SHARED : MAP_PRIVATE); + map_flags |= (flags & OS_AccessFlag_ShareWrite ? MAP_SHARED : MAP_PRIVATE); + + U32 prot_flags = lnx_prot_from_os_flags(flags); + U64 aligned_offset = AlignDownPow2(range.min, lnx_page_size); + U64 view_start_from_offset = (aligned_offset % lnx_page_size); + U64 map_size = (view_start_from_offset + range_size); + + void *address = mmap(NULL, map_size, prot_flags, map_flags, fd, aligned_offset); + entity->map.data = address; + entity->map.size = map_size; + void* result = (address + view_start_from_offset); + // Return NULL on error + if (address == (void*)-1) { result = NULL; } + + return result; } internal void os_file_map_view_close(OS_Handle map, void *ptr) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(map, LNX_EntityKind_MemoryMap); + AssertAlways( entity->map.data && (entity->map.data != (void*)-1) ); + /* NOTE: Make sure contents are synced with OS on the off chance the backing + file isn't POSIX compliant. Use MS_ASYNC if you want performance. */ + msync(entity->map.data, entity->map.size, MS_SYNC); + munmap(entity->map.data, entity->map.size); } //- rjf: directory iteration @@ -1240,21 +1718,74 @@ os_file_map_view_close(OS_Handle map, void *ptr) internal OS_FileIter * os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) { - NotImplemented; - return 0; + OS_FileIter* result = push_array(arena, OS_FileIter, 1); + result->flags = flags; + // String8 tmp = push_str8_copy(arena, path); + LNX_dir* directory = opendir((char*)path.str); + LNX_fd dir_fd = open((char*)path.str, 0x0, 0x0); + LNX_file_iter* out_iter = (LNX_file_iter*)result->memory; + out_iter->dir_stream = directory; + out_iter->dir_fd = dir_fd; + + // Never fail, just let the iterator return false. + return result; } + /* NOTE(mallchad): I have no idea what the return is for so it always returns true + unless the iterator is done */ internal B32 os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) { - NotImplemented; - return 0; + MemoryZeroStruct(info_out); + if (iter == NULL) { return 0; } + + LNX_dir* directory = NULL; + LNX_dir_entry* file = NULL; + LNX_fd working_path = -1; + FileProperties props = {0}; + LNX_fstat stats = {0}; + B32 no_file = 0; + + LNX_file_iter* iter_data = (LNX_file_iter*)iter->memory; + directory = iter_data->dir_stream; + working_path = iter_data->dir_fd; + if (directory == NULL || working_path == -1) { return 0; } + + // Should hopefully never infinite loop because readdir reaches NULL quickly + for (;;) + { + file = readdir(directory); + no_file = (file == NULL); + if (no_file) { iter->flags |= OS_FileIterFlag_Done; return 0; } + + if (iter->flags & OS_FileIterFlag_SkipFiles && file->d_type == DT_REG ) { continue; } + if (iter->flags & OS_FileIterFlag_SkipFolders && file->d_type == DT_DIR ) { continue; } + // TODO: Aparently this part of the API is in an indeterminate state. So hardcoding the behavior. + // if (iter->flags & OS_FileIterFlag_SkipHiddenFiles && file->d_name[0] == '.' ) { continue; } + if (file->d_name[0] == '.') { continue; } + break; + } + + LNX_fd fd = openat(working_path, file->d_name, 0x0, 0x0); + Assert(fd != -1); if (fd == -1) { return 0; } + S32 stats_err = fstat(fd, &stats); + Assert(stats_err == 0); if (stats_err != 0) { return 0; } + close(fd); + + info_out->name = push_str8_copy(arena, str8_cstring(file->d_name)); + lnx_file_properties_from_stat(&info_out->props, &stats); + + return 1; } internal void os_file_iter_end(OS_FileIter *iter) { - NotImplemented; + LNX_file_iter* iter_info = (LNX_file_iter*)iter->memory; + int stream_failure = closedir(iter_info->dir_stream); + int fd_failure = close(iter_info->dir_fd); + // Could be disabled + Assert(stream_failure == 0 && fd_failure == 0); } //- rjf: directory creation @@ -1262,13 +1793,12 @@ os_file_iter_end(OS_FileIter *iter) internal B32 os_make_directory(String8 path) { - Temp scratch = scratch_begin(0, 0); - B32 result = false; - String8 name_copy = push_str8_copy(scratch.arena, name); - if (mkdir((char*)name_copy.str, 0777) != -1){ - result = true; - } - scratch_end(scratch); + B32 result = 0; + mkdir((char*)path.str, 0770); + DIR* dirfd = opendir((char*)path.str); + result = (ENOENT != errno); + closedir(dirfd); + return(result); } @@ -1279,7 +1809,26 @@ internal OS_Handle os_shared_memory_alloc(U64 size, String8 name) { OS_Handle result = {0}; - NotImplemented; + LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_MemoryMap); + // Create, reuse if already open, mode: rw-rw---- + S32 fd = shm_open((char*)name.str, O_RDWR | O_CREAT, 0660); + /* NOTE(mallchad): This is your warnnig to remember to resize/truncate + memory files before writing, this burned me like 3 times already */ + ftruncate(fd, size); + Assert("Failed to alloc shared memory" && fd != -1); + + B32 use_huge = (size > os_large_page_size() && lnx_huge_page_enabled); + U32 flag_huge = (use_huge ? MAP_HUGETLB : 0x0); + U32 flag_prot = PROT_READ | PROT_WRITE; + U32 map_flags = MAP_SHARED | flag_huge | MAP_POPULATE; + void* mapping = mmap(NULL, size, flag_prot, map_flags, fd, 0); + Assert("Failed map memory for shared memory" && mapping != MAP_FAILED); + + entity->map.data = mapping; + entity->map.size = size; + entity->map.fd = fd; + entity->map.shm_name = push_str8_copy( lnx_perm_arena, name ); + result = lnx_handle_from_entity(entity); return result; } @@ -1287,27 +1836,51 @@ internal OS_Handle os_shared_memory_open(String8 name) { OS_Handle result = {0}; - NotImplemented; + LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_MemoryMap); + LNX_fd fd = shm_open((char*)name.str, O_RDWR, 0660 ); + Assert(fd != -1); + + entity->map.fd = fd; + entity->map.shm_name = push_str8_copy( lnx_perm_arena, name ); + *result.u64 = IntFromPtr(entity); + return result; } internal void os_shared_memory_close(OS_Handle handle) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(handle, LNX_EntityKind_MemoryMap); + shm_unlink( (char*)(entity->map.shm_name.str) ); } internal void * os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) { - NotImplemented; - return 0; + void* result = NULL; + LNX_Entity* entity = lnx_entity_from_handle(handle, LNX_EntityKind_MemoryMap); + LNX_fd fd = entity->map.fd; + + B32 use_huge = (range.max > os_large_page_size() && lnx_huge_page_enabled); + U32 flag_huge = (use_huge ? MAP_HUGETLB : 0x0); + U32 flag_prot = PROT_READ | PROT_WRITE; + U32 map_flags = MAP_SHARED | flag_huge | MAP_POPULATE; + void* mapping = mmap(NULL, range.max, flag_prot, map_flags, fd, 0); + Assert("Failed map memory for shared memory" && mapping != MAP_FAILED); + + result = mapping + range.min; // Get the offset to the map opening + entity->map.data = mapping; + entity->map.size = range.max; + if (result == (void*)-1) { result = NULL; } + + return result; } internal void os_shared_memory_view_close(OS_Handle handle, void *ptr) { - NotImplemented; + LNX_Entity entity = *lnx_entity_from_handle(handle, LNX_EntityKind_MemoryMap); + munmap(entity.map.data, entity.map.size); } //////////////////////////////// @@ -1338,7 +1911,7 @@ os_universal_time_from_local_time(DateTime *local_time){ lnx_tm_from_date_time(&local_tm, local_time); local_tm.tm_isdst = -1; time_t universal_t = mktime(&local_tm); - + // whatever type we ended up with -> DateTime (don't alter the space along the way) struct tm universal_tm = {0}; gmtime_r(&universal_t, &universal_tm); @@ -1356,7 +1929,7 @@ os_local_time_from_universal_time(DateTime *universal_time){ time_t universal_t = timegm(&universal_tm); struct tm local_tm = {0}; localtime_r(&universal_t, &local_tm); - + // whatever type we ended up with -> DateTime (don't alter the space along the way) DateTime result = {0}; lnx_date_time_from_tm(&result, &local_tm, 0); @@ -1366,29 +1939,93 @@ os_local_time_from_universal_time(DateTime *universal_time){ internal U64 os_now_microseconds(void){ struct timespec t; - clock_gettime(CLOCK_MONOTONIC, &t); + // NOTE: pedantic is it acutally worth it to use CLOCK_MONOTONIC_RAW? + // CLOCK_MONOTONIC is to occasional adjtime adjustments, the max error appears + // to be large. + // https://man7.org/linux/man-pages/man3/adjtime.3.html + clock_gettime(CLOCK_MONOTONIC_RAW, &t); U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); return(result); } internal void -os_sleep_milliseconds(U32 msec){ - usleep(msec*Thousand(1)); +os_sleep_milliseconds(U32 msec) +{ + LNX_timespec duration = {0}; + duration.tv_nsec = (msec* Million(1)); + nanosleep(&duration, NULL); } //////////////////////////////// //~ rjf: @os_hooks Child Processes (Implemented Per-OS) +/* NOTE: A terminal can be opened for any process but there is no default + terminal emulator on Linux, so instead you Have to cycle through a bunch of + common ones (there aren't that many). There is the XDG desktop specification + but that's a little bit of a chore to parse and its not even always present. + +The environment is inhereited by default but is easy to change in the future +with an execl variant. */ internal B32 -os_launch_process(OS_LaunchOptions *options){ +os_launch_process(OS_LaunchOptions *options, OS_Handle *handle_out) +{ + Temp scratch = scratch_begin(0, 0); + U8* buffer = (void*)mmap(NULL, 4096, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + B32* success_shared = (B32*)buffer; + S32* child_pid = (S32*)buffer+4; + *success_shared = 1; + *child_pid = 0; // TODO(allen): I want to redo this API before I bother implementing it here + // TODO(mallchad): Is this comment still relevant? gut says yes. + String8List cmdline_list = options->cmd_line; + char** cmdline = (char**)push_array(scratch.arena, char*, cmdline_list.node_count + 4); + String8Node* x_node = cmdline_list.first; + for (int i=0; istring.str; + x_node = x_node->next; + } + + if (options->inherit_env) { NotImplemented; }; + if (options->consoleless == 0) { NotImplemented; }; + S32 pid = 0; + pid = fork(); + // Child + if (pid) + { + *child_pid = pid; + if (child_pid > 0) { *(handle_out->u64) = (U64)child_pid; } + execv(*cmdline, cmdline); + *success_shared = 0; + exit(0); + } // Parent + else { waitpid(*child_pid, NULL, 0x0); } // TODO: Heck? Can't figure out how to wait on 'exec' + os_sleep_milliseconds(20); // Getto fix for no-wait issue + B32 success = *success_shared; + munmap(success_shared, 4096); + scratch_end(scratch); + return success; +} + +internal B32 +os_process_wait(OS_Handle handle, U64 endt_us) +{ + NotImplemented; + return 0; +} + +internal void +os_process_release_handle(OS_Handle handle) +{ NotImplemented; - return(false); } //////////////////////////////// //~ rjf: @os_hooks Threads (Implemented Per-OS) +// TODO: Marked as old / review +// TODO(mallchad): Isn't one of *params arg useuless? Should it be deleted? internal OS_Handle os_launch_thread(OS_ThreadFunctionType *func, void *ptr, void *params){ // entity @@ -1396,7 +2033,7 @@ os_launch_thread(OS_ThreadFunctionType *func, void *ptr, void *params){ entity->reference_mask = 0x3; entity->thread.func = func; entity->thread.ptr = ptr; - + // pthread pthread_attr_t attr; pthread_attr_init(&attr); @@ -1406,15 +2043,25 @@ os_launch_thread(OS_ThreadFunctionType *func, void *ptr, void *params){ lnx_free_entity(entity); entity = 0; } - + // cast to opaque handle OS_Handle result = {IntFromPtr(entity)}; return(result); } +internal B32 +os_thread_wait(OS_Handle handle, U64 endt_us) +{ + // TODO: Supposed to be thread sleep? + LNX_Entity* entity = lnx_entity_from_handle(handle, LNX_EntityKind_Thread); + NotImplemented; + return 0; +} + +// TODO: Marked as old / review internal void os_release_thread_handle(OS_Handle thread){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(thread.id); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(thread.u64[0]); // remove my bit U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x1); // if the other bit is also gone, free entity @@ -1435,7 +2082,7 @@ internal OS_Handle os_mutex_alloc(void){ // entity LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Mutex); - + // pthread pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); @@ -1446,7 +2093,7 @@ os_mutex_alloc(void){ lnx_free_entity(entity); entity = 0; } - + // cast to opaque handle OS_Handle result = {IntFromPtr(entity)}; return(result); @@ -1454,20 +2101,21 @@ os_mutex_alloc(void){ internal void os_mutex_release(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.u64[0]); pthread_mutex_destroy(&entity->mutex); lnx_free_entity(entity); } internal void os_mutex_take_(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); - pthread_mutex_lock(&entity->mutex); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.u64[0]); + S32 error = 0; + while(error = pthread_mutex_lock(&entity->mutex)) { } } internal void os_mutex_drop_(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.u64[0]); pthread_mutex_unlock(&entity->mutex); } @@ -1477,38 +2125,57 @@ internal OS_Handle os_rw_mutex_alloc(void) { OS_Handle result = {0}; - NotImplemented; + LNX_rwlock_attr attr; + pthread_rwlockattr_init(&attr); + pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE); + LNX_rwlock rwlock = {0}; + int pthread_result = pthread_rwlock_init(&rwlock, &attr); + // This can be cleaned up now. + pthread_rwlockattr_destroy(&attr); + + Assert(pthread_result == 0); + LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Rwlock); + entity->rwlock = rwlock; + *result.u64 = IntFromPtr(entity); return result; } internal void os_rw_mutex_release(OS_Handle rw_mutex) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(rw_mutex, LNX_EntityKind_Rwlock); + pthread_rwlock_destroy(&entity->rwlock); } internal void os_rw_mutex_take_r_(OS_Handle mutex) { - NotImplemented; + // Is blocking varient + LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Rwlock); + pthread_rwlock_rdlock(&entity->rwlock); } internal void os_rw_mutex_drop_r_(OS_Handle mutex) { - NotImplemented; + // NOTE: Aparently it results in undefined behaviour if there is no pre-existing lock + // https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_rwlock_unlock.html + LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Rwlock); + pthread_rwlock_unlock(&entity->rwlock); } internal void os_rw_mutex_take_w_(OS_Handle mutex) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Rwlock); + pthread_rwlock_wrlock(&(entity->rwlock)); } internal void os_rw_mutex_drop_w_(OS_Handle mutex) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Rwlock); + pthread_rwlock_unlock(&(entity->rwlock)); } //- rjf: condition variables @@ -1517,17 +2184,24 @@ internal OS_Handle os_condition_variable_alloc(void){ // entity LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_ConditionVariable); - + // pthread - pthread_condattr_t attr; - pthread_condattr_init(&attr); - int pthread_result = pthread_cond_init(&entity->cond, &attr); - pthread_condattr_destroy(&attr); - if (pthread_result == -1){ - lnx_free_entity(entity); - entity = 0; - } - + pthread_condattr_t attr_cond; + pthread_mutexattr_t attr_mutex; + + // Make sure condition uses CPU clock time + pthread_condattr_setclock(&attr_cond, CLOCK_MONOTONIC_RAW); + pthread_condattr_setpshared(&attr_cond, PTHREAD_PROCESS_PRIVATE); + pthread_mutexattr_init(&attr_mutex); + pthread_mutexattr_settype(&attr_mutex, PTHREAD_MUTEX_RECURSIVE); + int cond_result = pthread_cond_init(&entity->condition_variable.cond, &attr_cond); + int mutex_result = pthread_mutex_init(&entity->condition_variable.mutex, &attr_mutex); + + pthread_condattr_destroy(&attr_cond); + pthread_mutexattr_destroy(&attr_mutex); + + Assert(cond_result == 0); + // cast to opaque handle OS_Handle result = {IntFromPtr(entity)}; return(result); @@ -1535,45 +2209,116 @@ os_condition_variable_alloc(void){ internal void os_condition_variable_release(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - pthread_cond_destroy(&entity->cond); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); + pthread_cond_destroy(&entity->condition_variable.cond); + pthread_mutex_destroy(&entity->condition_variable.mutex); lnx_free_entity(entity); } internal B32 os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ - B32 result = false; - LNX_Entity *entity_cond = (LNX_Entity*)PtrFromInt(cv.id); - LNX_Entity *entity_mutex = (LNX_Entity*)PtrFromInt(mutex.id); - // TODO(allen): implement the time control - pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex); + B32 result = 0; + LNX_Entity *entity_cond = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); + LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex, LNX_EntityKind_Mutex); + LNX_timespec timeout_stamp = {0}; + timeout_stamp.tv_sec = (endt_us / Million(1)); + timeout_stamp.tv_nsec = ((endt_us % Million(1)) * Thousand(1)); + // TODO(mallchad): is endt supposed to be "end time?" + + // The timeout is received as a system clock timespec of when to stop waiting + B32 wait_result = pthread_cond_timedwait(&entity_cond->condition_variable.cond, + &entity_mutex->mutex, + &timeout_stamp); + result = (wait_result == 0); return(result); } internal B32 os_condition_variable_wait_rw_r_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) { - NotImplemented; - return 0; + // 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; } + LNX_Entity *cv_entity = (LNX_Entity *)cv.u64[0]; + LNX_Entity *rw_mutex_entity = (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->condition_variable.mutex); + int wait_result = pthread_cond_timedwait(&cv_entity->condition_variable.cond, + &cv_entity->condition_variable.mutex, + &endt_timespec); + if(wait_result != ETIMEDOUT) + { + pthread_rwlock_rdlock(&rw_mutex_entity->rwlock); + pthread_mutex_unlock(&cv_entity->condition_variable.mutex); + result = 1; + break; + } + pthread_mutex_unlock(&cv_entity->condition_variable.mutex); + 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) { - NotImplemented; - return 0; + // 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; } + LNX_Entity *cv_entity = (LNX_Entity *)cv.u64[0]; + LNX_Entity *rw_mutex_entity = (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->condition_variable.mutex); + int wait_result = pthread_cond_timedwait(&cv_entity->condition_variable.cond, + &cv_entity->condition_variable.mutex, + &endt_timespec); + if(wait_result != ETIMEDOUT) + { + pthread_rwlock_wrlock(&rw_mutex_entity->rwlock); + pthread_mutex_unlock(&cv_entity->condition_variable.mutex); + result = 1; + break; + } + pthread_mutex_unlock(&cv_entity->condition_variable.mutex); + if(wait_result == ETIMEDOUT) + { + break; + } + } + return result; } internal void -os_condition_variable_signal_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - pthread_cond_signal(&entity->cond); +os_condition_variable_signal_(OS_Handle cv) +{ + LNX_Entity *entity = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); + pthread_cond_signal(&entity->condition_variable.cond); } internal void -os_condition_variable_broadcast_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - DontCompile; +os_condition_variable_broadcast_(OS_Handle cv) +{ + LNX_Entity *entity = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); + pthread_cond_broadcast(&entity->condition_variable.cond); } //- rjf: cross-process semaphores @@ -1582,46 +2327,79 @@ internal OS_Handle os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) { OS_Handle result = {0}; - NotImplemented; + + // Create the named semaphore + // create | reuse pre-existing, rw-rw---- + sem_t* semaphore = sem_open((char*)name.str, O_RDWR | O_CREAT, 0660, initial_count); + if (semaphore != SEM_FAILED) + { + LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_Semaphore); + entity->semaphore.handle = semaphore; + entity->semaphore.max_value = max_count; + result = lnx_handle_from_entity(entity); + } return result; } internal void os_semaphore_release(OS_Handle semaphore) { - NotImplemented; + os_semaphore_close(semaphore); } internal OS_Handle os_semaphore_open(String8 name) { OS_Handle result = {0}; - NotImplemented; + LNX_Entity* handle = lnx_alloc_entity(LNX_EntityKind_Semaphore); + LNX_semaphore* semaphore; + semaphore = sem_open((char*)name.str, 0600); + handle->semaphore.handle = semaphore; + Assert("Failed to open POSIX semaphore." || semaphore != SEM_FAILED); + + result = lnx_handle_from_entity(handle); return result; } internal void os_semaphore_close(OS_Handle semaphore) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(semaphore, LNX_EntityKind_Semaphore); + LNX_semaphore* _semaphore = entity->semaphore.handle; + sem_close(_semaphore); } internal B32 os_semaphore_take(OS_Handle semaphore, U64 endt_us) { - NotImplemented; - return 0; + U32 wait_result = 0; + LNX_timespec wait_until = lnx_now_precision_timespec(); + wait_until.tv_nsec += endt_us; + + LNX_Entity* entity = lnx_entity_from_handle(semaphore, LNX_EntityKind_Semaphore); + LNX_semaphore* _semaphore = entity->semaphore.handle; + // We have to impliment max_count ourselves + S32 current_value = 0; + sem_getvalue(_semaphore, ¤t_value); + if (entity->semaphore.max_value > current_value) + { + sem_timedwait(_semaphore, &wait_until); + } + return (wait_result != -1); } internal void os_semaphore_drop(OS_Handle semaphore) { - NotImplemented; + LNX_Entity* entity = lnx_entity_from_handle(semaphore, LNX_EntityKind_Semaphore); + sem_t* _semaphore = entity->semaphore.handle; + sem_post(_semaphore); } //////////////////////////////// //~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) +// TODO: Marked as old / review internal OS_Handle os_library_open(String8 path) { @@ -1633,11 +2411,12 @@ os_library_open(String8 path) return lib; } +// TODO: Marked as old / review internal VoidProc * os_library_load_proc(OS_Handle lib, String8 name) { Temp scratch = scratch_begin(0, 0); - void *so = (void *)lib.id; + void *so = (void *)lib.u64[0]; char *name_cstr = (char *)push_str8_copy(scratch.arena, name).str; VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); scratch_end(scratch); @@ -1647,34 +2426,35 @@ os_library_load_proc(OS_Handle lib, String8 name) internal void os_library_close(OS_Handle lib) { - void *so = (void *)lib.id; + void *so = (void *)lib.u64[0]; dlclose(so); } //////////////////////////////// //~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) +// TODO: Marked as old / review internal void os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr){ LNX_SafeCallChain chain = {0}; SLLStackPush(lnx_safe_call_chain, &chain); chain.fail_handler = fail_handler; chain.ptr = ptr; - + struct sigaction new_act = {0}; new_act.sa_handler = lnx_safe_call_sig_handler; - + int signals_to_handle[] = { SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, }; struct sigaction og_act[ArrayCount(signals_to_handle)] = {0}; - + for (U32 i = 0; i < ArrayCount(signals_to_handle); i += 1){ sigaction(signals_to_handle[i], &new_act, &og_act[i]); } - + func(ptr); - + for (U32 i = 0; i < ArrayCount(signals_to_handle); i += 1){ sigaction(signals_to_handle[i], &og_act[i], 0); } @@ -1685,6 +2465,25 @@ os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, v internal OS_Guid os_make_guid(void) { - NotImplemented; -} + OS_Guid result = {0}; + LNX_uuid tmp;; + MemoryZeroArray(tmp); + uuid_generate(tmp); + MemoryCopy(&result.data1, 0+ tmp, 4); + MemoryCopy(&result.data2, 4+ tmp, 2); + MemoryCopy(&result.data3, 6+ tmp, 2); + MemoryCopy(&result.data4, 8+ tmp, 8); + + return result; +} +int +lnx_entry_point(int argc, char** argv) +{ + return 1; +} +int +main(int argc, char** argv) +{ + main_thread_base_entry_point(entry_point, argv, (U64)argc); +} diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index 9899f94a0..9d8fee125 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -7,41 +7,75 @@ //////////////////////////////// //~ NOTE(allen): Get all these linux includes -#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 - -//////////////////////////////// -//~ NOTE(allen): File Iterator - -struct LNX_FileIter{ - int fd; - DIR *dir; -}; -StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(LNX_FileIter), file_iter_memory_size); +#include +#include +#include +#include +#include +#include + +////////////////////////////////// +// Helper Typedefs + +// -- Time -- +// Time broken into major and minor parts +typedef struct timespec LNX_timespec; +typedef struct timeval LNX_timeval; +// Deconstructed Date-Time +typedef struct tm LNX_date; + +// -- File -- +// File Statistics +typedef S32 LNX_fd; +typedef struct stat LNX_fstat; +// Opaque directory stream of directory contents +typedef DIR LNX_dir; +// Opaque directory entry/file +typedef struct dirent LNX_dir_entry; + +// -- Other -- +typedef uuid_t LNX_uuid; + +// -- Threading Types -- +typedef pthread_t LNX_thread; +typedef sem_t LNX_semaphore; +typedef pthread_mutex_t LNX_mutex; +typedef pthread_mutexattr_t LNX_mutex_attr; +typedef pthread_rwlock_t LNX_rwlock; +typedef pthread_rwlockattr_t LNX_rwlock_attr; +typedef pthread_cond_t LNX_cond; //////////////////////////////// //~ NOTE(allen): Threading Entities - +typedef enum LNX_EntityKind LNX_EntityKind; enum LNX_EntityKind{ LNX_EntityKind_Null, LNX_EntityKind_Thread, LNX_EntityKind_Mutex, + LNX_EntityKind_Rwlock, LNX_EntityKind_ConditionVariable, + LNX_EntityKind_Semaphore, + LNX_EntityKind_MemoryMap, }; +typedef struct LNX_Entity LNX_Entity; struct LNX_Entity{ LNX_Entity *next; LNX_EntityKind kind; @@ -52,14 +86,30 @@ struct LNX_Entity{ void *ptr; pthread_t handle; } thread; - pthread_mutex_t mutex; - pthread_cond_t cond; + struct{ + sem_t* handle; + U32 max_value; + } semaphore; + struct{ + S32 fd; + U32 flags; // TODO: Maybe delete + void* data; + U64 size; + String8 shm_name; + } map; + struct{ + LNX_cond cond; + LNX_mutex mutex; + } condition_variable; + LNX_mutex mutex; + LNX_rwlock rwlock; }; }; //////////////////////////////// //~ NOTE(allen): Safe Call Chain +typedef struct LNX_SafeCallChain LNX_SafeCallChain; struct LNX_SafeCallChain{ LNX_SafeCallChain *next; OS_ThreadFunctionType *fail_handler; @@ -69,13 +119,53 @@ struct LNX_SafeCallChain{ //////////////////////////////// //~ NOTE(allen): Helpers +// Helper Structs +typedef struct LNX_version LNX_version; +struct LNX_version { + U32 major; + U32 minor; + U32 patch; + String8 string; +}; + +typedef struct LNX_file_iter LNX_file_iter; +struct LNX_file_iter { + LNX_dir* dir_stream; + S32 dir_fd; +}; + +int lnx_entry_point(int argc, char** argv); + internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list); + /* Helper function to return a timespec using a adjtime stable high precision clock + This should not be affected by setting the system clock or RTC*/ +internal LNX_timespec lnx_now_precision_timespec(); +// Get the current system time that is affected by setting the system clock +internal LNX_timespec lnx_now_system_timespec(); +// Typecast Functions internal void lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec); internal void lnx_tm_from_date_time(struct tm *out, DateTime *in); -internal void lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in); +internal void lnx_timespec_from_date_time(LNX_timespec* out, DateTime* in); +internal void lnx_timeval_from_date_time(LNX_timeval* out, DateTime* in); +internal void lnx_dense_time_from_timespec(DenseTime *out, LNX_timespec *in); +internal void lnx_timeval_from_dense_time(LNX_timeval* out, DenseTime* in); +internal void lnx_timespec_from_dense_time(LNX_timespec* out, DenseTime* in); +internal void lnx_timespec_from_timeval(LNX_timespec* out, LNX_timeval* in); +internal void lnx_timeval_from_timespec(LNX_timeval* out, LNX_timespec* in); internal void lnx_file_properties_from_stat(FileProperties *out, struct stat *in); +// Convert OS_AccessFlags to 'mmap' compatible 'PROT_' flags +internal U32 lnx_prot_from_os_flags(OS_AccessFlags flags); +// Convert OS_AccessFlags to 'open' compatible 'O_' flags +internal U32 lnx_open_from_os_flags(OS_AccessFlags flags); + +// Stable and consistent handle conversions +internal U32 lnx_fd_from_handle(OS_Handle file); +internal OS_Handle lnx_handle_from_fd(U32 fd); +internal LNX_Entity* lnx_entity_from_handle(OS_Handle handle, LNX_EntityKind type); +internal OS_Handle lnx_handle_from_entity(LNX_Entity* entity); + internal String8 lnx_string_from_signal(int signum); internal String8 lnx_string_from_errno(int error_number); @@ -83,6 +173,6 @@ internal LNX_Entity* lnx_alloc_entity(LNX_EntityKind kind); internal void lnx_free_entity(LNX_Entity *entity); internal void* lnx_thread_base(void *ptr); -internal void lnx_safe_call_sig_handler(int); +internal void lnx_safe_call_sig_handler(int _); #endif //LINUX_H diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c new file mode 100644 index 000000000..825a0b904 --- /dev/null +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -0,0 +1,563 @@ + +global Arena* gfx_lnx_arena = NULL; +global U32 gfx_lnx_max_monitors = 6; +global OS_EventFlags gfx_lnx_modifier_state = 0; +global GFX_LinuxWindowList gfx_lnx_windows = {0}; +global GFX_LinuxMonitorArray gfx_lnx_monitors = {0}; +global GFX_LinuxMonitor* gfx_lnx_primary_monitor = {0}; +global GFX_LinuxMonitor gfx_lnx_monitor_stub = {0}; +global void* gfx_lnx_icon = NULL; +global Vec2S32 gfx_lnx_icon_size = {0}; +global U32 gfx_lnx_icon_capacity = 0; +global U32 gfx_lnx_icon_stride = 0; + +/// Determines if wayland pathway should be used by default. We have seperate +/// pathway so recompilation isn't necesscary +global B32 gfx_lnx_wayland_preferred = 0; +global B32 gfx_lnx_wayland_disabled = 1; +/// Caps the amount of events that can be process in one collection run +global U32 gfx_lnx_event_limit = 5000; + +global S32 gfx_egl_version_major = 0; +global S32 gfx_egl_version_minor = 0; +global const S32 gfx_opengl_version_major = 4; +global const S32 gfx_opengl_version_minor = 0; +global String8 gfx_default_window_name = {0}; +global GFX_LinuxContext gfx_context = {0}; + +global EGLContext gfx_egl_context = NULL; +global EGLDisplay gfx_egl_display = NULL; +global EGLSurface gfx_egl_draw_surface = NULL; +global EGLSurface gfx_egl_read_surface = NULL; +global S32 num_config; +global S32 gfx_egl_config[] = { + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, + EGL_NONE +}; +EGLConfig gfx_egl_config_available[10]; +global S32 gfx_egl_config_available_size = 0; +global S32 gfx_egl_context_config[] = { + EGL_CONTEXT_MAJOR_VERSION, gfx_opengl_version_major, + EGL_CONTEXT_MINOR_VERSION, gfx_opengl_version_minor, + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, + EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE, EGL_FALSE, + EGL_NONE +}; + + +//////////////////////////////// +//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) + +/* NOTE(mallchad): The Linux graphic stack is messy and born out of this was + * EGL, yet another khronos API. To me, this seems to solve no end user + * problem, in fact it is actually more or less more work than using Xlib/xcb + * and Wayland backends directly. However, they claim, EGL allows them to + * solve backend technical problems with refresh orchestration, surface + * resource management, graphics acceleration orchestration and the like. + * + * I have no idea the validity of this. And I would love to know the validty of + * this, because the graphisc bugs on Linux are absurd and very few competent + * people seems to know where exactly they come from. but Wayland *REQUIRES* + * it. So be it. + */ + +GFX_LinuxMonitor* +gfx_monitor_from_id(U64 id) +{ + GFX_LinuxMonitor* x_monitor = NULL; + for (int i=0; i < gfx_lnx_monitors.head_size; ++i) + { + x_monitor = (gfx_lnx_monitors.data + i); + if (x_monitor->id == id) { return x_monitor; } + } + return &gfx_lnx_monitor_stub; +} + +GFX_LinuxMonitor* +gfx_monitor_from_handle(OS_Handle monitor) +{ + return gfx_monitor_from_id(*monitor.u64); +} + +OS_Handle gfx_handle_from_monitor(GFX_LinuxMonitor* monitor) +{ + OS_Handle result = {0}; + *(result.u64) = monitor->id; + return result; +} + +GFX_LinuxWindow* +gfx_window_from_handle(OS_Handle context) +{ + return (GFX_LinuxWindow*)PtrFromInt(*context.u64); +} + +OS_Handle +gfx_handle_from_window(GFX_LinuxWindow* window) +{ + OS_Handle result = {0}; + *result.u64 = IntFromPtr(window); + return result; +} + +B32 +gfx_load_image_from_ico(String8 ico, void** out_data, Vec2S32* out_size) +{ + // Copied from df_gfx + // unpack icon image data + Temp scratch = scratch_begin(0, 0); + String8 data = ico; + U8 *ptr = data.str; + U8 *opl = ptr+data.size; + + // read header + ICO_Header hdr = {0}; + if(ptr+sizeof(hdr) < opl) + { + MemoryCopy(&hdr, ptr, sizeof(hdr)); + ptr += sizeof(hdr); + } + + // read image entries + U64 entries_count = hdr.num_images; + ICO_Entry *entries = push_array(scratch.arena, ICO_Entry, hdr.num_images); + { + U64 bytes_to_read = sizeof(ICO_Entry)*entries_count; + bytes_to_read = Min(bytes_to_read, opl-ptr); + MemoryCopy(entries, ptr, bytes_to_read); + ptr += bytes_to_read; + } + + // find largest image + ICO_Entry *best_entry = 0; + U64 best_entry_area = 0; + for(U64 idx = 0; idx < entries_count; idx += 1) + { + ICO_Entry *entry = &entries[idx]; + U64 width = entry->image_width_px; + if(width == 0) { width = 256; } + U64 height = entry->image_height_px; + if(height == 0) { height = 256; } + U64 entry_area = width*height; + if(entry_area > best_entry_area) + { + best_entry = entry; + best_entry_area = entry_area; + } + } + + // deserialize raw image data from best entry's offset + U8 *image_data = 0; + B32 success = 1; + if(best_entry != 0) + { + U8 *file_data_ptr = data.str + best_entry->image_data_off; + U64 file_data_size = best_entry->image_data_size; + int width = 0; + int height = 0; + int components = 0; + image_data = stbi_load_from_memory(file_data_ptr, file_data_size, &width, &height, &components, 4); + + int width_padding = (4 - (width%4)) % 4; + int stride = (width + width_padding); + stride = width; + U32 data_size = (stride * height * components); + out_size->x = width; + out_size->y = height; + Assert(components == 4); // Made to assume 4 component RGBA format + + gfx_lnx_icon_capacity = data_size; + gfx_lnx_icon_stride = stride; + *out_data = push_array(gfx_lnx_arena, U8, data_size * 4); + MemoryCopy(*out_data, image_data, data_size); + // Cleanup + stbi_image_free(image_data); + } + else { success = 0; } + // Cleanup + scratch_end(scratch); + return success; +} + +internal void +os_graphical_init(void) +{ + // Allocate and setup basic internal stuff + gfx_lnx_arena = arena_alloc(); + + gfx_context.default_window_name = gfx_default_window_name; + gfx_context.default_window_size = vec_2f32(500, 500); + gfx_context.default_window_size = gfx_context.default_window_size; + gfx_context.default_window_pos = vec_2f32(500, 500); + gfx_context.default_window_pos = gfx_context.default_window_pos; + gfx_default_window_name = str8_lit("raddebugger"); + + ArrayAllocate(&gfx_lnx_monitors, gfx_lnx_arena, gfx_lnx_max_monitors); + ArrayAllocate(&gfx_lnx_monitors_active, gfx_lnx_arena, gfx_lnx_max_monitors); + + // Deserialize application icon + gfx_load_image_from_ico(df_g_icon_file_bytes, &gfx_lnx_icon, &gfx_lnx_icon_size); + + B32 init_result = 0; + if (gfx_lnx_wayland_disabled) + { init_result = x11_graphical_init(&gfx_context); } + else + { init_result = wayland_graphical_init(&gfx_context); } + Assert(init_result); + + // Initialize EGL - Windowing Agnostic + gfx_egl_display = eglGetDisplay(gfx_context.native_server); + // Generate a stubby window on fail + if (gfx_egl_display == EGL_NO_DISPLAY) + { gfx_egl_display = eglGetDisplay((EGLNativeDisplayType)EGL_DEFAULT_DISPLAY); } + Assert(gfx_egl_display != EGL_NO_DISPLAY); + + // NOTE: EGL_TRUE (1) if success, otherwise EGL_FALSE (0) or error code + B32 egl_initial_result = eglInitialize(gfx_egl_display, &gfx_egl_version_minor, + &gfx_egl_version_major); + B32 egl_config_result = eglChooseConfig(gfx_egl_display, gfx_egl_config, gfx_egl_config_available, + 10, &gfx_egl_config_available_size); + B32 egl_bind_result = eglBindAPI(EGL_OPENGL_API); + Assert(gfx_egl_config_available_size > 0); + Assert(egl_initial_result == 1 && egl_config_result == 1 && egl_bind_result == 1); + EGLConfig select_config = gfx_egl_config_available[0]; + gfx_egl_context = eglCreateContext(gfx_egl_display, select_config, + EGL_NO_CONTEXT, gfx_egl_context_config ); + Assert(gfx_egl_context != EGL_NO_CONTEXT); +} + + +//////////////////////////////// +//~ rjf: @os_hooks Clipboards (Implemented Per-OS) + +internal void +os_set_clipboard_text(String8 string) +{ + +NotImplemented;} + +internal String8 +os_get_clipboard_text(Arena *arena) +{ + String8 result = {0}; + return result; +NotImplemented;} + +//////////////////////////////// +//~ rjf: @os_hooks Windows (Implemented Per-OS) + +internal OS_Handle +os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title) +{ + // TODO(mallchad): Figure out default window placement + OS_Handle result = {0}; + B32 success = 0; + if (gfx_lnx_wayland_disabled) + { x11_window_open(&gfx_context, &result, resolution, flags, title); } + else + { wayland_window_open(&gfx_context, &result, resolution, flags, title); } + GFX_LinuxWindow* window = gfx_window_from_handle(result); + DLLPushBack(gfx_lnx_windows.first, gfx_lnx_windows.last, window); + + window->first_surface = eglCreateWindowSurface(gfx_egl_display, gfx_egl_config_available[0], + (EGLNativeWindowType)window->handle, NULL); + // This need to figure out how exactly this works double buffered + /* window->second_surface = eglCreateWindowSurface(gfx_egl_display, gfx_egl_config_available[0], + (EGLNativeWindowType)window->handle, NULL); */ + if (gfx_egl_read_surface = NULL) + { gfx_egl_read_surface = window->first_surface; }; + + return result; +} +internal void +os_window_close(OS_Handle window) +{ + +NotImplemented;} + +internal void +os_window_first_paint(OS_Handle window) +{ + // Nothing to do on first paint yet + NoOp; +} + +internal void +os_window_equip_repaint(OS_Handle window, OS_WindowRepaintFunctionType *repaint, void *user_data) +{ + +} + +internal void +os_window_focus(OS_Handle window) +{ + +NotImplemented;} + +internal B32 +os_window_is_focused(OS_Handle window) +{ + return 0; +NotImplemented;} + +internal B32 +os_window_is_fullscreen(OS_Handle window) +{ + return 0; +NotImplemented;} + +internal void +os_window_set_fullscreen(OS_Handle window, B32 fullscreen) +{ + +NotImplemented;} + +internal B32 +os_window_is_maximized(OS_Handle window) +{ + B32 result = 0; + if (gfx_lnx_wayland_disabled) + { result = x11_window_is_maximized(window); } + else + { result = wayland_window_is_maximized(window); } + return result; +} + +internal void +os_window_set_maximized(OS_Handle window, B32 maximized) +{ + if (gfx_lnx_wayland_disabled) + { x11_window_set_maximized(window, maximized); } + else + { wayland_window_set_maximized(window, maximized); } +} + +internal void +os_window_minimize(OS_Handle window) +{ +NotImplemented; +} + +internal void +os_window_bring_to_front(OS_Handle window) +{ +NotImplemented; +} + +internal void +os_window_set_monitor(OS_Handle window, OS_Handle monitor) +{ + if (gfx_lnx_wayland_disabled) + { x11_window_set_monitor(window, monitor); } + else + { wayland_window_set_monitor(window, monitor); } +} + +internal void +os_window_clear_custom_border_data(OS_Handle handle) +{ + // NOTE(mallchad): No practical use yet + NoOp; +} + +internal void +os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) +{ + /* NOTE(mallchad): No standard on linux, check back here later + https://gitlab.freedesktop.org/wayland/wayland-protocols/-/tree/main/unstable/xdg-decoration?ref_type=heads */ + NoOp; +} + +internal void +os_window_push_custom_edges(OS_Handle window, F32 thickness) +{ + if (gfx_lnx_wayland_disabled) + { x11_window_push_custom_edges(window, thickness); } + else + { wayland_window_push_custom_edges(window, thickness); } +} + +internal void +os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) +{ + /* NOTE(mallchad): I have no idea what this is supposed to be. I'm assuming + it's a Windows specific API because there's no generic reasoning for this + besides being an actual full implimentation for window setup, just not an + OS abstraction. So I'm not adding it. */ + NoOp; +} + +internal Rng2F32 +os_rect_from_window(OS_Handle window) +{ + Rng2F32 result = {0}; + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + result.min = _window->pos; + result.max = add_2f32(_window->pos, _window->size); + return result; +} + +internal Rng2F32 +os_client_rect_from_window(OS_Handle window) +{ + Rng2F32 result = {0}; + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + result.max = _window->size; + return result; +} + +internal F32 +os_dpi_from_window(OS_Handle window) +{ + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + Assert(_window->monitor->dpi > 0.001 && _window->monitor->dpi < 1000); + return (_window->monitor->dpi); +} + + +//////////////////////////////// +//~ rjf: @os_hooks Monitors (Implemented Per-OS) + +internal OS_HandleArray +os_push_monitors_array(Arena *arena) +{ + OS_HandleArray result = {0}; + if (gfx_lnx_wayland_disabled) + { x11_push_monitors_array(arena, &result); } + else + { wayland_push_monitors_array(arena, &result); } + return result; +} + +internal OS_Handle +os_primary_monitor(void) +{ + OS_Handle result = {0}; + x11_primary_monitor(&result); + return result; + NotImplemented;} + +internal OS_Handle +os_monitor_from_window(OS_Handle window) +{ + OS_Handle result = {0}; + if (gfx_lnx_wayland_disabled) + { x11_monitor_from_window(window, &result); } + else + { wayland_monitor_from_window(window, &result); } + return result; +} + +internal String8 +os_name_from_monitor(Arena *arena, OS_Handle monitor) +{ + String8 result = {0}; + if (gfx_lnx_wayland_disabled) + { x11_name_from_monitor(arena, monitor, &result); } + else + { wayland_name_from_monitor(arena, monitor, &result); } + return result; +} + +internal Vec2F32 +os_dim_from_monitor(OS_Handle monitor) +{ + Vec2F32 result = {0}; + if (gfx_lnx_wayland_disabled) + { x11_dim_from_monitor(monitor, &result); } + else + { x11_dim_from_monitor(monitor, &result); } + return result; +} + + +//////////////////////////////// +//~ rjf: @os_hooks Events (Implemented Per-OS) + +/* NOTE(mallchad): No idea what this is supposed to mean or be for it looks like + a dead pathway in this branch anyway so I'm just going to leave it. */ +internal void +os_send_wakeup_event(void) +{ + return; +} + +internal OS_EventList +os_get_events(Arena *arena, B32 wait) +{ + OS_EventList result = {0}; + if (gfx_lnx_wayland_disabled) + { x11_get_events(arena, wait, &result); } + else + { wayland_get_events(arena, wait, &result); } + return result; +} + +internal OS_EventFlags +os_get_event_flags(void) +{ + return gfx_lnx_modifier_state; +} + +internal B32 +os_key_is_down(OS_Key key) +{ + NotImplemented; + return 0; +} + +internal Vec2F32 +os_mouse_from_window(OS_Handle window) +{ + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + return _window->mouse_pos; +} + + +//////////////////////////////// +//~ rjf: @os_hooks Cursors (Implemented Per-OS) + +internal void +os_set_cursor(OS_Cursor cursor) +{ + NotImplemented; +} + + +//////////////////////////////// +//~ rjf: @os_hooks System Properties (Implemented Per-OS) + +internal F32 +os_double_click_time(void) +{ + return 0.f; + NotImplemented; +} + +internal F32 +os_caret_blink_time(void) +{ + return 0.f; + NotImplemented; +} + +internal F32 +os_default_refresh_rate(void) +{ + OS_Handle primary_monitor = os_primary_monitor(); + GFX_LinuxMonitor* monitor = gfx_monitor_from_handle(primary_monitor); + return monitor->refresh_rate; +} + + +//////////////////////////////// +//~ rjf: @os_hooks Native Messages & Panics (Implemented Per-OS) + +internal void +os_graphical_message(B32 error, String8 title, String8 message) +{ + NotImplemented; +} diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h new file mode 100644 index 000000000..b881fe4f4 --- /dev/null +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -0,0 +1,109 @@ + +#ifndef GFX_LINUX_H +#define GFX_LINUX_H + +#include + +typedef struct GFX_LinuxContext GFX_LinuxContext; +struct GFX_LinuxContext +{ + + String8 default_window_name; + Vec2F32 default_window_size; + Vec2F32 default_window_pos; + + EGLNativeDisplayType native_server; + EGLNativeWindowType native_window; +}; + +typedef enum GFX_SubPixelOrder GFX_SubPixelOrder; +enum GFX_SubPixelOrder +{ + GFX_SubPixelOrder_Stub +}; + +typedef enum GFX_MonitorOrientation GFX_MonitorOrientation; +enum GFX_MonitorOrientation +{ + GFX_MonitorOrientation_Stub +}; + +typedef struct GFX_LinuxMonitor GFX_LinuxMonitor; +struct GFX_LinuxMonitor +{ + U64 id; + U64 handle; + B32 primary; + // Offset should be zero on platforms where it doesn't make sense + Vec2F32 offset; + Vec2F32 offset_mid; + Vec2F32 size_px; + Vec2F32 size_physical_mm; + Vec2F32 size_mid_px; + Vec2F32 size_physical_mid_mm; + F32 dpi; + F32 dots_per_mm; + F32 refresh_rate; + F32 refresh_rate_target; + GFX_SubPixelOrder subpixel_order; + GFX_MonitorOrientation orientation; + B32 landscape; + B32 landscape_physical; + B32 physical; +}; + +typedef struct GFX_LinuxWindow GFX_LinuxWindow; +struct GFX_LinuxWindow +{ + GFX_LinuxWindow* next; + GFX_LinuxWindow* prev; + OS_Guid id; + U64 handle; + GFX_LinuxMonitor* monitor; + String8 name; + Vec2F32 pos; + Vec2F32 pos_mid; + Vec2F32 pos_target; + Vec2F32 size; + Vec2F32 size_target; + F32 border_width; + U32 root_relative_depth; + EGLSurface first_surface; + EGLSurface second_surface; + Vec2F32 mouse_pos; + DenseTime mouse_timestamp; + B32 wayland_native; +}; + +typedef struct GFX_LinuxWindowList GFX_LinuxWindowList; +struct GFX_LinuxWindowList +{ + GFX_LinuxWindow* first; + GFX_LinuxWindow* last; + U64 count; +}; + +DeclareArray(GFX_LinuxMonitorArray, GFX_LinuxMonitor); + +// Global Data +global Arena* gfx_lnx_arena; +global U32 gfx_lnx_max_monitors; +global OS_EventFlags gfx_lnx_modifier_state; +global GFX_LinuxWindowList gfx_lnx_windows; +/// A list of any monitors known internally +global GFX_LinuxMonitorArray gfx_lnx_monitors; +/// A list of any monitors actively being used or recorded by DF or GFX +global GFX_LinuxMonitorArray gfx_lnx_monitors_active; +global GFX_LinuxMonitor* gfx_lnx_primary_monitor; +global void* gfx_lnx_icon; +global Vec2S32 gfx_lnx_icon_size; +global U32 gfx_lnx_icon_capacity; +global U32 gfx_lnx_icon_stride; +global String8 gfx_default_window_name; + +GFX_LinuxMonitor* gfx_monitor_from_handle(OS_Handle monitor); +OS_Handle gfx_handle_from_monitor(GFX_LinuxMonitor* monitor); +internal GFX_LinuxWindow* gfx_window_from_handle(OS_Handle context); +internal OS_Handle gfx_handle_from_window(GFX_LinuxWindow* window); + +#endif /* GFX_LINUX_H */ diff --git a/src/os/gfx/linux/os_gfx_wayland.c b/src/os/gfx/linux/os_gfx_wayland.c new file mode 100644 index 000000000..5cabb3d1e --- /dev/null +++ b/src/os/gfx/linux/os_gfx_wayland.c @@ -0,0 +1,72 @@ + + +B32 +wayland_window_open(GFX_LinuxContext* gfx_context, OS_Handle* result, + Vec2F32 resolution, OS_WindowFlags flags, String8 title) +{ + NotImplemented; + return 0; +} + +B32 +wayland_graphical_init(GFX_LinuxContext* out) +{ + NotImplemented; + return 0; +} + +B32 +wayland_get_events(Arena *arena, B32 wait, OS_EventList* out) +{ + NotImplemented; + return 0; +} + +B32 +wayland_push_monitors_array(Arena* arena, OS_HandleArray* monitor) +{ + (void)monitor; + NotImplemented; + return 0; +} + +B32 +wayland_monitor_from_window(OS_Handle window, OS_Handle* out_monitor) +{ + NotImplemented; + return 0; +} + +B32 +wayland_name_from_monitor(Arena* arena, OS_Handle monitor, String8* result) +{ + NotImplemented; + return 0; +} + +B32 +wayland_window_set_monitor(OS_Handle window, OS_Handle monitor) +{ + return 0; +} + +B32 +wayland_window_push_custom_edges(OS_Handle window, F32 thickness) +{ + NotImplemented; + return 0; +} + +B32 +wayland_window_is_maximized(OS_Handle window) +{ + NotImplemented; + return 0; +} + +B32 +wayland_window_set_maximized(OS_Handle window, B32 maximized) +{ + NotImplemented; + return 0; +} diff --git a/src/os/gfx/linux/os_gfx_wayland.h b/src/os/gfx/linux/os_gfx_wayland.h new file mode 100644 index 000000000..e69de29bb diff --git a/src/os/gfx/linux/os_gfx_x11.c b/src/os/gfx/linux/os_gfx_x11.c new file mode 100644 index 000000000..79e486770 --- /dev/null +++ b/src/os/gfx/linux/os_gfx_x11.c @@ -0,0 +1,706 @@ + + +typedef Display X11_Display; +typedef Window X11_Window; +// Forward Declares + +global X11_Display* x11_server = NULL; +global X11_Window x11_window = 0; +global X11_Window x11_root_window = 0; +global S32 x11_default_screen = 0; +global Atom x11_atoms[100]; +global Atom x11_atom_tail = 0; +global U32 x11_unhandled_events = 0; +global U32 x11_xdnd_supported_version = 5; +global RROutput* x11_monitors = 0; +global U32 x11_monitors_size = 0; + +B32 +x11_monitor_init(GFX_LinuxMonitor* out_monitor, RROutput handle) +{ + U64 time_us = os_now_microseconds(); + out_monitor->handle = handle; + out_monitor->id = handle; + + B32 duplicate = 1; + for (int i=0; i < gfx_lnx_monitors.head_size; ++i) + { + duplicate = (handle == gfx_lnx_monitors.data[i].handle); + if (duplicate) { return 0; } + } + B32 success_update = x11_monitor_update_properties(out_monitor); + return success_update; +} + +internal B32 +x11_monitor_update_properties(GFX_LinuxMonitor* out_monitor) +{ + RROutput xrandr_output = out_monitor->handle; + XRRScreenResources* resources = XRRGetScreenResources(x11_server, x11_root_window); + XRROutputInfo* props = XRRGetOutputInfo(x11_server, resources, xrandr_output); + + XRRCrtcInfo* x_info = NULL; + XRRModeInfo* x_mode = NULL; + B32 found_crtc = 0; + for (int i=0; i< props->ncrtc; ++i) + { + x_info = XRRGetCrtcInfo(x11_server, resources, props->crtcs[i]); + /* NOTE(mallchad): If output is set its probably means the output is using + this config but I'm not always be sure its *this* output usin this crtc + mode so this should probably be investigated later*/ + if (x_info->noutput > 0) { found_crtc = 1; break; } + XRRFreeCrtcInfo(x_info); + } + if (found_crtc) + { + // Search for active mode + B32 found_mode = 0; + for (int i=0; inmode; ++i) + { + x_mode = resources->modes + i; + if (x_info->mode == x_mode->id) { found_mode = 1; break; } + } + if (found_mode) + { + out_monitor->refresh_rate = (x_mode->dotClock / (x_mode->vTotal * x_mode->hTotal)); + } + else + { + out_monitor->refresh_rate = 60; + } + + out_monitor->offset.x = x_info->x; + out_monitor->offset.y = x_info->y; + out_monitor->offset_mid.x = x_info->x + (x_info->width / 2); + out_monitor->offset_mid.y = x_info->y + (x_info->height / 2); + out_monitor->size_px.x = x_info->width; + out_monitor->size_px.y = x_info->height; + out_monitor->size_mid_px.x = (x_info->width / 2); + out_monitor->size_mid_px.y = (x_info->height / 2); + XRRFreeCrtcInfo(x_info); + } + else + { return 0; } + RROutput primary_output = XRRGetOutputPrimary(x11_server, x11_root_window); + out_monitor->primary = (out_monitor->handle == primary_output); + + out_monitor->size_physical_mm.x = props->mm_width; + out_monitor->size_physical_mm.y = props->mm_height; + out_monitor->size_physical_mid_mm.x = (props->mm_width / 2); + out_monitor->size_physical_mid_mm.y = (props->mm_height / 2); + + F32 cms_per_inch = 25.4; + out_monitor->dots_per_mm = (out_monitor->size_px.x / out_monitor->size_physical_mid_mm.x); + out_monitor->dpi = (out_monitor->dots_per_mm / cms_per_inch); + /* out_monitor->subpixel_order = */ + /* out_monitor->orientation; */ + out_monitor->landscape = (out_monitor->size_px.x > out_monitor->size_px.y); + out_monitor->landscape_physical = (props->mm_width > props->mm_height); + out_monitor->physical = 1; + + XRRFreeOutputInfo(props); + XRRFreeScreenResources(resources); + return 1; +} + +/// Update window properties from X11 +B32 +x11_window_update_properties(GFX_LinuxWindow* out) +{ + XWindowAttributes props = {0}; + + // Update base properties + XGetWindowAttributes(x11_server, out->handle, &props); + out->pos.x = props.x; + out->pos.y = props.y; + out->pos_mid.x = props.x + (props.width/2.f); + out->pos_mid.y = props.y + (props.height/2.f); + out->size.x = props.width; + out->size.y = props.height; + out->border_width = props.border_width; + out->root_relative_depth = props.depth; + + // Calculate window's current monitor + B32 horizontal_inside; + B32 vertical_inside; + + // Monitor Measurements + S32 right_extent; + S32 left_extent; + S32 up_extent; + S32 down_extent; + + B32 found = 0; + GFX_LinuxMonitor* x_monitor; + for (int i=0; i < gfx_lnx_monitors.head_size; ++i) + { + x_monitor = (i+ gfx_lnx_monitors.data); + left_extent = x_monitor->offset.x; + right_extent = (x_monitor->offset.x + x_monitor->size_px.x); + up_extent = x_monitor->offset.y; + down_extent = (x_monitor->offset.y + x_monitor->size_px.y); + horizontal_inside = (out->pos_mid.x > left_extent) && (out->pos_mid.x < right_extent); + vertical_inside = (out->pos_mid.y > up_extent) && (out->pos_mid.y < down_extent); + + if (horizontal_inside && vertical_inside) { found = 1; break; } + } + if (found == 0) + { out->monitor = NULL; } + else + { out->monitor = x_monitor; } + + return 1; +} + +OS_EventFlags +x11_event_flags_from_modmap(U32 modmap) +{ + /* NOTE(mallchad): Modifiers aren't as well standardized on Linux and + delegates to things like the X11 mod map, which is setup to support + several arbitrary modifier keys. ctrl and shift is pretty standard + but users may remap them, Meta (often windows) is often different + between desktop environments, alt is usually Mod1 (tested on KDE + Plasma) but could easily shift around. Be warned. */ + gfx_lnx_modifier_state = (modmap & ShiftMask) ? OS_EventFlag_Shift : 0x0; + gfx_lnx_modifier_state = (modmap & ControlMask) ? OS_EventFlag_Ctrl : 0x0; + gfx_lnx_modifier_state = (modmap & Mod1Mask) ? OS_EventFlag_Alt : 0x0; + return gfx_lnx_modifier_state; +} + +B32 +x11_repopulate_monitors() +{ + ArrayZero(&gfx_lnx_monitors); + GFX_LinuxMonitor x_monitor = {0}; + S32 monitor_count = 0; + XRRMonitorInfo* monitor_list = NULL; + + // Get active monitors only + monitor_list = XRRGetMonitors(x11_server, x11_root_window, 0, &monitor_count); + if (monitor_list == NULL) { Assert(0); return 0; } // Bug, its very odd if you can't get any windows + Assert(monitor_count < gfx_lnx_max_monitors); // App probably hasn't been configured to support more + + for (int i=0; inative_server = (EGLNativeDisplayType)x11_server; + + // Setup atoms + Atom test_atom = 0; + for (int i=0; idefault_window_pos.x, out->default_window_pos.y, + resolution.x, resolution.y, 0, + visual_info.depth, + InputOutput, + visual_info.visual, + CWBorderPixel|CWColormap|CWEventMask, + &window_attributes); + // Use default window name if none provided, no-name windows are super annoying. + if (title.size) + { XStoreName(x11_server, new_window, (char*)title.str); } + else + { XStoreName(x11_server, new_window, (char*)gfx_default_window_name.str); } + + // Subscribe to NET_WM_DELETE events and disable auto XDestroyWindow() + Atom enabled_protocols[] = { x11_atoms[ X11_Atom_WM_DELETE_WINDOW ] }; + XSetWMProtocols( x11_server, new_window, enabled_protocols, ArrayCount(enabled_protocols) ); + + // Subscribe to events + U32 event_mask = (ClientMessage | KeyPressMask | KeyReleaseMask | ButtonPressMask | + PointerMotionMask | StructureNotifyMask | FocusChangeMask | + EnterWindowMask | LeaveWindowMask); + XSelectInput(x11_server, new_window, event_mask); + + // Set window icon + if (gfx_lnx_icon != NULL) + { + Atom bit_width = 32; // bits per item + U32 user_type = XA_CARDINAL; // Abitrary User-Specified Layout ID + Temp scratch = scratch_begin(0, 0); + U32 pixel_count = (gfx_lnx_icon_stride * gfx_lnx_icon_size.y); + U32 item_count = 2+ pixel_count; + + /* NOTE(mallchad): 64-bit Cardinal becomes 64-bit long with upper 32-bits padded + ie Cardinal is based on sizeof long + Source format is 32-bit RGBA no padding + Destination format is 32-bit ARGB */ + U64 buffer_size = (2* sizeof(long)) + (item_count * sizeof(long)); + U8* buffer = push_array(gfx_lnx_arena, U8, buffer_size); + ((long*)buffer)[0] = 256; // Width + ((long*)buffer)[1] = 256; // Height + U32* source_pixel = NULL; + long* dest_pixel = NULL; + for (int i=0; i < pixel_count; ++i) + { + source_pixel = ((U32*)gfx_lnx_icon) + (i); + dest_pixel = ((long*)buffer) + 2 + (i); + // Convert ABGR to ARGB | AAbbGGrr -> AArrGGbb + *dest_pixel = (((*source_pixel & 0xFF000000)) | // A + ((*source_pixel & 0x00FF0000) >> 16 ) | // B + ((*source_pixel & 0x0000FF00) ) | // G + ((*source_pixel & 0x000000FF) << 16 )); // R + } + + XChangeProperty(x11_server, + new_window, + x11_atoms[ X11_Atom_WM_ICON ], + user_type, + bit_width, + PropModeReplace, + (U8*)buffer, + item_count); + scratch_end(scratch); + } + + // Make window viewable + XMapWindow(x11_server, new_window); + + // Fill out return data + result->handle = (U64)new_window; + result->name = push_str8_copy(gfx_lnx_arena, title); + result->pos_target = out->default_window_pos; + result->size_target = resolution; + result->wayland_native = 0; + x11_window_update_properties(result); + *out_handle = gfx_handle_from_window(result); + return 0; +} + +OS_Key +x11_oskey_from_keycode(U32 keycode) +{ + U32 table_size = ArrayCount(x11_keysym); + U32 x_keycode = 0; + for (int i=0; ihandle == event.xany.window) + { + window = x_window; + x_node->window = gfx_handle_from_window(x_window); + break; + } + x_window = x_window->next; + } + B32 window_found = (window != NULL || event.type == MappingNotify); // Window unused on Mappnig + Assert(window_found); // Something has gone horribly wrong if no match is found + + // Fulfil event type specific tasks + // NOTE(mallchad): Don't think wakeup is relevant here + switch (event.type) + { + case KeyPress: + x_node->timestamp_us = (event.xkey.time / 1000); // ms to us + x_node->kind = OS_EventKind_Press; + x_node->key = x11_oskey_from_keycode(event.xkey.keycode); + x_node->flags = x11_event_flags_from_modmap(event.xkey.state); + // TODO:(mallchad): Dunno what to do about this section right now + x_node->is_repeat = 0; + x_node->right_sided = 0; + x_node->repeat_count = 0; + break; + + case KeyRelease: + x_node->timestamp_us = (event.xkey.time / 1000); // ms to us + x_node->kind = OS_EventKind_Release; + x_node->key = x11_oskey_from_keycode(event.xkey.keycode); + x_node->flags = x11_event_flags_from_modmap(event.xkey.state); + // TODO:(mallchad): Dunno what to do about this section right now + x_node->is_repeat = 0; + x_node->right_sided = 0; + x_node->repeat_count = 0; + break; + + case MotionNotify: + /* PointerMotionMask - The client application receives MotionNotify + events independent of the state of the pointer buttons. + https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html#XButtonEvent + NOTE(mallchad): It should be constant update rate when using PointerMotionMask. */ + x_node->timestamp_us = (event.xmotion.time / 1000); // ms to us + x_node->kind = OS_EventKind_MouseMove; + x_node->pos = vec_2f32(event.xmotion.x_root, event.xmotion.y_root); // root window relative + x_node->flags = x11_event_flags_from_modmap(event.xmotion.state); + window->mouse_pos = (x_node->pos); + window->mouse_timestamp = (x_node->timestamp_us); + break; + + case ButtonPress: + x_node->timestamp_us = (event.xbutton.time / 1000); // ms to us + x_node->kind = OS_EventKind_Press; + U32 button_code = event.xbutton.button; + x_node->key = x11_mouse[button_code]; + x_node->flags = x11_event_flags_from_modmap(event.xbutton.state); + + B32 scroll_action = (button_code == X11_Mouse_ScrollUp || button_code == X11_Mouse_ScrollDown); + if (scroll_action) {} + /* NOTE(mallchad): Actually I don't know if this is sensible yet. X11 + doesn't support any kind of scroll even or touchpad but libinput + does. I don't know what the raddebugger API is for it yet so. */ + break; + + case ClientMessage: + { + // NOTE(mallchad): Not doing OS_EventKind_Text event. Feels Windows specific + // The rest of this section is just random 3rd-party spec events + XClientMessageEvent message = event.xclient; + Window source_window = message.data.l[0]; + U32 format = message.format; + B32 format_list = (message.data.l[1] & 1); + Window xdnd_version = (message.data.l[1] >> 24); + + if ((Atom)(message.data.l[0]) == x11_atoms[ X11_Atom_WM_DELETE_WINDOW ]) + { x_node->kind = OS_EventKind_WindowClose; } + else if ((Atom)message.message_type == x11_atoms[ X11_Atom_XdndDrop ] + && xdnd_version <= x11_xdnd_supported_version) + { + x_node->kind = OS_EventKind_FileDrop; + if (format_list) + { + /* https://tronche.com/gui/x/xlib/window-information/XGetWindowProperty.html + https://www.codeproject.com/Tips/5387630/How-to-handle-X11-Drag-n-Drop-events + TODO(mallchad): not finishing this because it's long and + complicated and can't be tested properly for a really long + time */ + U8* data; + U32 format_count; + Atom requested_type = 1; // 1 byte return format + Atom return_type = 0; + U32 return_format = 0; + U64 return_count = 0; // Based on return type + U64 bytes_read = 0; + U64 bytes_unread = 0; + XGetWindowProperty((Display*) x11_server, + source_window, + x11_atoms[ X11_Atom_XdndTypeList ], + 0, + LONG_MAX, + False, + requested_type, + &return_type, + (S32*)&return_format, + &return_count, + &bytes_unread, + &data); + bytes_read = (return_count * return_type); + Trap(); + XFree(data); + } + if (message.format != 0) + { + // Get X11 updated time variable + // TODO(mallchad): What the heck does the "format" atom mean? + Time current_time = CurrentTime; + XConvertSelection((Display*) x11_server, + x11_atoms[ X11_Atom_XdndSelection ], + format, + x11_atoms[ X11_Atom_XdndSelection ], + message.window, + current_time); + // XSendEvent(); // Inform sending client the drag'n'drop was receive + } + NotImplemented; + } + break; + } + + case FocusIn: + x_node->kind = OS_EventKind_Null; + break; + + case FocusOut: + x_node->kind = OS_EventKind_WindowLoseFocus; + break; + + case RRScreenChangeNotify: + break; + case RRNotify: + { + XRRNotifyEvent* xrr_event = (XRRNotifyEvent*)&event; + Trap(); + + switch (xrr_event->subtype) + { + case RRNotify_CrtcChange: break; + NotImplemented; + case RRNotify_OutputChange: + { + XRROutputChangeNotifyEvent* xrr_event2 = (XRROutputChangeNotifyEvent*)&event; + NotImplemented; + break; + } + case RRNotify_OutputProperty: + { + XRROutputPropertyNotifyEvent* xrr_event2 = (XRROutputPropertyNotifyEvent*)&event; + NotImplemented; + break; + } + /* NOTE(mallchad): Should never really change because that would + imply hardware graphics change, except in the extremely rare case + of Virtual graphics device creaction, this would break everything + at the OS level reguardless. + */ + case RRNotify_ProviderChange: break; + case RRNotify_ProviderProperty: break; + + case RRNotify_ResourceChange: break; + } + } + break; + default: + ++x11_unhandled_events; + keep_event = 0; + x_node->kind = OS_EventKind_Null; + break; + + } + if (keep_event) + { + // Set links + if (out->first == NULL) + { out->first = x_node; out->last = x_node; } + else + { + out->last->next = x_node; + x_node->prev = out->last; + out->last = x_node; + } + ++(out->count); + + } + } + return 1; +} + +// Unused +B32 +x11_push_monitors_array(Arena* arena, OS_HandleArray* monitor) +{ + NoOp; + return 1; +} + +B32 +x11_primary_monitor(OS_Handle* monitor) +{ + RROutput primary_output = XRRGetOutputPrimary(x11_server, x11_root_window); + + GFX_LinuxMonitor* x_monitor = NULL; + B32 found_primary = 0; + for (int i=0; i < gfx_lnx_monitors.head_size; ++i) + { + x_monitor = (gfx_lnx_monitors.data + i); + found_primary = (x_monitor->handle == primary_output); + if (found_primary) { *monitor = gfx_handle_from_monitor(x_monitor); break; } + } + return found_primary; +} + +B32 +x11_monitor_from_window(OS_Handle window, OS_Handle* out_monitor) +{ + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + if (_window->monitor != NULL) + { *out_monitor = gfx_handle_from_monitor(_window->monitor); } + else + { return 0; } + return 1; +} + +B32 +x11_name_from_monitor(Arena* arena, OS_Handle monitor, String8* out_name) +{ + GFX_LinuxMonitor* _monitor = gfx_monitor_from_handle(monitor); + RROutput xrandr_output = _monitor->handle; + XRRScreenResources* resources = XRRGetScreenResources(x11_server, x11_root_window); + XRROutputInfo* props = XRRGetOutputInfo(x11_server, resources, xrandr_output); + String8 monitor_name = str8_cstring(props->name); + *out_name = push_str8_copy(arena, monitor_name); + + XRRFreeOutputInfo(props); + XRRFreeScreenResources(resources); + return 1; +} + +B32 +x11_dim_from_monitor(OS_Handle monitor, Vec2F32* out_v2) +{ + GFX_LinuxMonitor* _monitor = gfx_monitor_from_handle(monitor); + if ( x11_monitor_update_properties(_monitor) ) + { *out_v2 = _monitor->size_px; return 0; } + else + { return 0; } +} + +B32 +x11_window_set_monitor(OS_Handle window, OS_Handle monitor) +{ + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + GFX_LinuxMonitor* _monitor = gfx_monitor_from_handle(monitor); + F32 monitor_relative_x = (_window->pos.x - _window->monitor->offset.x + _monitor->offset.x); + F32 monitor_relative_y = (_window->pos.y - _window->monitor->offset.y + _monitor->offset.y); + XMoveResizeWindow(x11_server, _window->handle, + monitor_relative_x, + monitor_relative_y, + _window->size.y, + _window->size.y); + x11_window_update_properties(_window); + return 1; +} + +B32 +x11_window_push_custom_edges(OS_Handle window, F32 thickness) +{ + /* NOTE(mallchad): X11 doesn't support fractional border width, you are free + to set your own border pixmap though */ + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + XSetWindowBorderWidth(x11_server, _window->handle, thickness); + return 1; +} + +B32 +x11_window_is_maximized(OS_Handle window) +{ + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + Atom* props = NULL; // Return typed data + U64 offset = 0; + U64 user_type = 0; // Abitrary User-Type set by XChangeProperty + U64 bit_width = 0; // Bits per item + U64 item_count = 0; + U64 bytes_read = 0; + U64 bytes_unread = 0; + + XGetWindowProperty(x11_server, + _window->handle, + x11_atoms[ X11_Atom_WM_STATE ], + offset, + LONG_MAX, + False, + AnyPropertyType, + &user_type, + (S32*)&bit_width, + &item_count, + &bytes_unread, + (U8**)&props); + bytes_read = ((item_count * bit_width) / 8); + + B32 horizontal_maximized = 0; + B32 vertical_maximized = 0; + B32 is_maximized = 0; + for (int i=0; ihandle, + x11_atoms[ X11_Atom_WM_STATE ], + 1, + bit_width, + PropModeAppend, + (U8*)wm_state_new, + 1); + return 1; +} diff --git a/src/os/gfx/linux/os_gfx_x11.h b/src/os/gfx/linux/os_gfx_x11.h new file mode 100644 index 000000000..7cd4b96ac --- /dev/null +++ b/src/os/gfx/linux/os_gfx_x11.h @@ -0,0 +1,280 @@ + +#ifndef GFX_X11_H +#define GFX_X11_H +// X11 - Xorg Base Headers +#include +#include +#include +#include + +// X11 Extensions +#include + +// OpenGL +#include + +// Undefine annoy X11 macros +#undef Status + + +/* NOTE(mallchad): Xrandr is the X Resize, Rotate and Reflection extension, which allows + for seamlessly spreading an X11 display across multiple physical monitors. It + does this by creating an oversized X11 display and mapping user-defined + regions onto the physical monitors. + + An Xrandr "provider" can be best though of as a logical monitor supplier, + like a graphics card that makes monitors available and drawable, or a virtual + graphics card that pipes through a network. + + An Xrandr "monitor" is a logical monitor that usually represents a physical + monitor and all its relevant properties. + + An Xrandr "output" is a virtual object that sets allows for writing pixel + data to, for all intents and purposes it is more or less analogous to a + physical monitor but isn't bound by hardware changes, this is the most + relevant object to users. + + An Xrandr "crtc" is an archaic representation of Cathode-Ray-Tube Controller + properties. For our purposes it carries extra display info. Mostly just + position, rotation and sizing data. + + The Xrandr struct _XRRMonitorInfo has the field "outputs", I'm not actually + convinced it's ever more than 1 +*/ + +global U32 x11_keysym[] = { + XK_VoidSymbol, // OS_Key_Null + XK_Escape, // OS_Key_Esc + XK_F1, // OS_Key_F1 + XK_F2, // OS_Key_F2 + XK_F3, // OS_Key_F3 + XK_F4, // OS_Key_F4 + XK_F5, // OS_Key_F5 + XK_F6, // OS_Key_F6 + XK_F7, // OS_Key_F7 + XK_F8, // OS_Key_F8 + XK_F9, // OS_Key_F9 + XK_F10, // OS_Key_F10 + XK_F11, // OS_Key_F11 + XK_F12, // OS_Key_F12 + XK_F13, // OS_Key_F13 + XK_F14, // OS_Key_F14 + XK_F15, // OS_Key_F15 + XK_F16, // OS_Key_F16 + XK_F17, // OS_Key_F17 + XK_F18, // OS_Key_F18 + XK_F19, // OS_Key_F19 + XK_F20, // OS_Key_F20 + XK_F21, // OS_Key_F21 + XK_F22, // OS_Key_F22 + XK_F23, // OS_Key_F23 + XK_F24, // OS_Key_F24 + XK_apostrophe, // OS_Key_Tick + XK_0, // OS_Key_0 + XK_1, // OS_Key_1 + XK_2, // OS_Key_2 + XK_3, // OS_Key_3 + XK_4, // OS_Key_4 + XK_5, // OS_Key_5 + XK_6, // OS_Key_6 + XK_7, // OS_Key_7 + XK_8, // OS_Key_8 + XK_9, // OS_Key_9 + XK_minus, // OS_Key_Minus + XK_equal, // OS_Key_Equal + XK_BackSpace, // OS_Key_Backspace + XK_Tab, // OS_Key_Tab + XK_Q, // OS_Key_Q + XK_W, // OS_Key_W + XK_W, // OS_Key_E + XK_R, // OS_Key_R + XK_T, // OS_Key_T + XK_Y, // OS_Key_Y + XK_U, // OS_Key_U + XK_I, // OS_Key_I + XK_O, // OS_Key_O + XK_P, // OS_Key_P + XK_parenright, // OS_Key_LeftBracket + XK_parenleft, // OS_Key_RightBracket + XK_backslash, // OS_Key_BackSlash + XK_Caps_Lock, // OS_Key_CapsLock + XK_A, // OS_Key_A + XK_S, // OS_Key_S + XK_D, // OS_Key_D + XK_F, // OS_Key_F + XK_G, // OS_Key_G + XK_H, // OS_Key_H + XK_J, // OS_Key_J + XK_K, // OS_Key_K + XK_L, // OS_Key_L + XK_semicolon, // OS_Key_Semicolon + XK_quotedbl, // OS_Key_Quote + XK_Return, // OS_Key_Return + XK_Shift_R, // OS_Key_Shift + XK_Z, // OS_Key_Z + XK_X, // OS_Key_X + XK_C, // OS_Key_C + XK_V, // OS_Key_V + XK_B, // OS_Key_B + XK_N, // OS_Key_N + XK_M, // OS_Key_M + XK_comma, // OS_Key_Comma + XK_percent, // OS_Key_Period + XK_slash, // OS_Key_Slash + XK_Control_L, // OS_Key_Ctrl + XK_Alt_L, // OS_Key_Alt + XK_space, // OS_Key_Space + XK_Menu, // OS_Key_Menu + XK_Scroll_Lock, // OS_Key_ScrollLock + XK_Pause, // OS_Key_Pause + XK_Insert, // OS_Key_Insert + XK_Home, // OS_Key_Home + XK_Page_Up, // OS_Key_PageUp + XK_Delete, // OS_Key_Delete + XK_End, // OS_Key_End + XK_Page_Down, // OS_Key_PageDown + XK_Up, // OS_Key_Up + XK_Left, // OS_Key_Left + XK_Down, // OS_Key_Down + XK_Right, // OS_Key_Right + XK_VoidSymbol, // OS_Key_Ex0 + XK_VoidSymbol, // OS_Key_Ex1 + XK_VoidSymbol, // OS_Key_Ex2 + XK_VoidSymbol, // OS_Key_Ex3 + XK_VoidSymbol, // OS_Key_Ex4 + XK_VoidSymbol, // OS_Key_Ex5 + XK_VoidSymbol, // OS_Key_Ex6 + XK_VoidSymbol, // OS_Key_Ex7 + XK_VoidSymbol, // OS_Key_Ex8 + XK_VoidSymbol, // OS_Key_Ex8 + XK_VoidSymbol, // OS_Key_Ex9 + XK_VoidSymbol, // OS_Key_Ex10 + XK_VoidSymbol, // OS_Key_Ex10 + XK_VoidSymbol, // OS_Key_Ex11 + XK_VoidSymbol, // OS_Key_Ex12 + XK_VoidSymbol, // OS_Key_Ex12 + XK_VoidSymbol, // OS_Key_Ex13 + XK_VoidSymbol, // OS_Key_Ex14 + XK_VoidSymbol, // OS_Key_Ex15 + XK_VoidSymbol, // OS_Key_Ex16 + XK_VoidSymbol, // OS_Key_Ex17 + XK_VoidSymbol, // OS_Key_Ex18 + XK_VoidSymbol, // OS_Key_Ex19 + XK_VoidSymbol, // OS_Key_Ex20 + XK_VoidSymbol, // OS_Key_Ex21 + XK_VoidSymbol, // OS_Key_Ex22 + XK_VoidSymbol, // OS_Key_Ex23 + XK_VoidSymbol, // OS_Key_Ex24 + XK_VoidSymbol, // OS_Key_Ex25 + XK_VoidSymbol, // OS_Key_Ex26 + XK_VoidSymbol, // OS_Key_Ex27 + XK_VoidSymbol, // OS_Key_Ex28 + XK_VoidSymbol, // OS_Key_Ex29 + XK_Num_Lock, // OS_Key_NumLock + XK_KP_Divide, // OS_Key_NumSlash + XK_KP_Multiply, // OS_Key_NumStar + XK_KP_Subtract, // OS_Key_NumMinus + XK_KP_Add, // OS_Key_NumPlus + XK_KP_Decimal, // OS_Key_NumPeriod + XK_KP_0, // OS_Key_Num0 + XK_KP_1, // OS_Key_Num1 + XK_KP_2, // OS_Key_Num2 + XK_KP_3, // OS_Key_Num3 + XK_KP_4, // OS_Key_Num4 + XK_KP_5, // OS_Key_Num5 + XK_KP_6, // OS_Key_Num6 + XK_KP_7, // OS_Key_Num7 + XK_KP_8, // OS_Key_Num8 + XK_KP_9, // OS_Key_Num9 + XK_Pointer_Button1, // OS_Key_LeftMouseButton + XK_Pointer_Button2, // OS_Key_MiddleMouseButton + XK_Pointer_Button3 // OS_Key_RightMouseButton +}; + +/// Enum from X11 button numbers to meaningful names +enum +{ + X11_Mouse_Left = 1, + X11_Mouse_Middle = 2, + X11_Mouse_Right = 3, + X11_Mouse_ScrollUp = 4, + X11_Mouse_ScrollDown = 5 +}; + +/// Conversion from human-readable names to x11 "Buttons" +global U32 x11_mouse[] = +{ + 0, + OS_Key_LeftMouseButton, + OS_Key_MiddleMouseButton, + OS_Key_RightMouseButton, +}; + +/// Access locations for atom values in 'x11_atom' from derived names +enum x11_atoms +{ + X11_Atom_WM_STATE, + X11_Atom_WM_DELETE_WINDOW, + X11_Atom_WM_STATE_MODAL, + X11_Atom_WM_STATE_STICKY, + X11_Atom_WM_STATE_MAXIMIZED_VERT, + X11_Atom_WM_STATE_MAXIMIZED_HORZ, + X11_Atom_WM_STATE_SHADED, + X11_Atom_WM_STATE_SKIP_TASKBAR, + X11_Atom_WM_STATE_SKIP_PAGER, + X11_Atom_WM_STATE_HIDDEN, + X11_Atom_WM_STATE_FULLSCREEN, + X11_Atom_WM_STATE_ABOVE, + X11_Atom_WM_STATE_BELOW, + X11_Atom_WM_STATE_DEMANDS_ATTENTION, + X11_Atom_WM_ICON, + X11_Atom_XdndTypeList, + X11_Atom_XdndSelection, + X11_Atom_XdndEnter, + X11_Atom_XdndPosition, + X11_Atom_XdndStatus, + X11_Atom_XdndLeave, + X11_Atom_XdndDrop, + X11_Atom_XdndFinished, + X11_Atom_XdndActionCopy, + X11_Atom_TextURIList, + X11_Atom_TextPlan +}; + +global char* x11_test_atoms[] = +{ + "_NET_WM_STATE", + "WM_DELETE_WINDOW", // NOTE(mallchad): This one is named different idk why + /* NOTE(mallchad): Freedesktop WM Extensions for state like fullscreen + https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html#id2522991 + */ + "_NET_WM_STATE_MODAL", + "_NET_WM_STATE_STICKY", + "_NET_WM_STATE_MAXIMIZED_VERT", + "_NET_WM_STATE_MAXIMIZED_HORZ", + "_NET_WM_STATE_SHADED", + "_NET_WM_STATE_SKIP_TASKBAR", + "_NET_WM_STATE_SKIP_PAGER", + "_NET_WM_STATE_HIDDEN", + "_NET_WM_STATE_FULLSCREEN", + "_NET_WM_STATE_ABOVE", + "_NET_WM_STATE_BELOW", + "_NET_WM_STATE_DEMANDS_ATTENTION", + "_NET_WM_ICON", + "XdndTypeList", + "XdndSelection", + "XdndEnter", + "XdndPosition", + "XdndStatus", + "XdndLeave", + "XdndDrop", + "XdndFinished", + "XdndActionCopy", + "text/uri-list", + "text/plain" +}; + +#endif // GFX_X11_H + +struct GFX_LinuxMonitor; +internal B32 x11_monitor_update_properties(struct GFX_LinuxMonitor* out_monitor); diff --git a/src/os/gfx/os_gfx.h b/src/os/gfx/os_gfx.h index 962820a15..eee4b0eec 100644 --- a/src/os/gfx/os_gfx.h +++ b/src/os/gfx/os_gfx.h @@ -142,6 +142,7 @@ internal F32 os_dpi_from_window(OS_Handle window); //////////////////////////////// //~ rjf: @os_hooks Monitors (Implemented Per-OS) +/// NOTE(mallchad): 'os_push_monitors_array' serves close to no use at present and is almost unused. internal OS_HandleArray os_push_monitors_array(Arena *arena); internal OS_Handle os_primary_monitor(void); internal OS_Handle os_monitor_from_window(OS_Handle window); diff --git a/src/os/os_inc.c b/src/os/os_inc.c index 7b2125aec..55b3c2815 100644 --- a/src/os/os_inc.c +++ b/src/os/os_inc.c @@ -24,6 +24,13 @@ # endif #elif OS_LINUX # include "core/linux/os_core_linux.c" +# if OS_FEATURE_GRAPHICAL && !OS_GFX_STUB +# ifndef WAYLAND_DISABLED +# include "gfx/linux/os_gfx_wayland.c" +# endif +# include "gfx/linux/os_gfx_x11.c" +# include "gfx/linux/os_gfx_linux.c" +# endif #else # error no OS layer setup #endif diff --git a/src/os/os_inc.h b/src/os/os_inc.h index 8d7c1db15..d348d9f5a 100644 --- a/src/os/os_inc.h +++ b/src/os/os_inc.h @@ -36,6 +36,13 @@ # endif #elif OS_LINUX # include "core/linux/os_core_linux.h" +# if OS_FEATURE_GRAPHICAL && !OS_GFX_STUB +# ifndef WAYLAND_DISABLED +# include "gfx/linux/os_gfx_wayland.h" +# endif +# include "gfx/linux/os_gfx_x11.h" +# include "gfx/linux/os_gfx_linux.h" +# endif #else # error no OS layer setup #endif diff --git a/src/raddbg/raddbg.c b/src/raddbg/raddbg.c index 2b636d6a0..8bd191b96 100644 --- a/src/raddbg/raddbg.c +++ b/src/raddbg/raddbg.c @@ -169,6 +169,7 @@ update_and_render(OS_Handle repaint_window_handle, void *user_data) DF_CmdParams params = window ? df_cmd_params_from_window(window) : df_cmd_params_from_gfx(); B32 take = 0; B32 skip = 0; + if (window == NULL) { continue; } //- rjf: try drag-drop if(df_drag_is_active() && event->kind == OS_EventKind_Release && event->key == OS_Key_LeftMouseButton) diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 212666b1c..19117c2c4 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -25,6 +25,16 @@ #include "third_party/rad_lzb_simple/rad_lzb_simple.h" #include "third_party/rad_lzb_simple/rad_lzb_simple.c" +#if !defined(STBI_INCLUDE_STB_IMAGE_H) +# define STB_IMAGE_IMPLEMENTATION +# define STBI_ONLY_PNG +# define STBI_ONLY_BMP +# include "third_party/stb/stb_image.h" +#endif + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "third_party/stb/stb_image_write.h" + //- rjf: [h] #include "base/base_inc.h" #include "os/os_inc.h" diff --git a/src/render/generated/render.meta.h b/src/render/generated/render.meta.h index 411e900e5..2714756be 100644 --- a/src/render/generated/render.meta.h +++ b/src/render/generated/render.meta.h @@ -20,6 +20,14 @@ R_Tex2DFormat_RGBA32, R_Tex2DFormat_COUNT, } R_Tex2DFormat; +typedef enum R_Tex2DKind +{ +R_Tex2DKind_Static, +R_Tex2DKind_Dynamic, +R_Tex2DKind_Stream, +R_Tex2DKind_COUNT, +} R_Tex2DKind; + typedef enum R_ResourceKind { R_ResourceKind_Static, @@ -44,6 +52,13 @@ R_GeoTopologyKind_TriangleStrip, R_GeoTopologyKind_COUNT, } R_GeoTopologyKind; +typedef enum R_BufferKind +{ +R_BufferKind_Static, +R_BufferKind_Dynamic, +R_BufferKind_COUNT, +} R_BufferKind; + typedef enum R_PassKind { R_PassKind_UI, 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..b9d27fe60 --- /dev/null +++ b/src/render/opengl/generated/render_opengl.meta.c @@ -0,0 +1,5 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//- GENERATED CODE + 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..7785fff6f --- /dev/null +++ b/src/render/opengl/generated/render_opengl.meta.h @@ -0,0 +1,805 @@ +// 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 + +// Adapted from Dear ImGui generated OpenGL bindings +// Adapted from KHR/khrplatform.h to avoid including entire file. +#ifndef __khrplatform_h_ +typedef float khronos_float_t; +typedef signed char khronos_int8_t; +typedef unsigned char khronos_uint8_t; +typedef signed short int khronos_int16_t; +typedef unsigned short int khronos_uint16_t; +#ifdef _WIN64 +typedef signed long long int khronos_intptr_t; +typedef signed long long int khronos_ssize_t; +#else +typedef signed long int khronos_intptr_t; +typedef signed long int khronos_ssize_t; +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +typedef signed __int64 khronos_int64_t; +typedef unsigned __int64 khronos_uint64_t; +#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100) +#include +typedef int64_t khronos_int64_t; +typedef uint64_t khronos_uint64_t; +#else +typedef signed long long khronos_int64_t; +typedef unsigned long long khronos_uint64_t; +#endif +#endif // __khrplatform_h_ + +typedef void GLvoid; +typedef unsigned int GLenum; +typedef khronos_float_t GLfloat; +typedef int GLint; +typedef int GLsizei; +typedef unsigned int GLbitfield; +typedef double GLdouble; +typedef unsigned int GLuint; +typedef unsigned char GLboolean; +typedef khronos_uint8_t GLubyte; +typedef khronos_float_t GLclampf; +typedef double GLclampd; +typedef khronos_ssize_t GLsizeiptr; +typedef khronos_intptr_t GLintptr; +typedef char GLchar; +typedef khronos_int16_t GLshort; +typedef khronos_int8_t GLbyte; +typedef khronos_uint16_t GLushort; +typedef khronos_uint16_t GLhalf; +typedef struct __GLsync *GLsync; +typedef khronos_uint64_t GLuint64; +typedef khronos_int64_t GLint64; +typedef khronos_uint64_t GLuint64EXT; +typedef khronos_int64_t GLint64EXT; + + +typedef void (*PFNGL_DrawArrays) (GLenum mode, GLint first, GLsizei count); +typedef void (*PFNGL_DrawElements) (GLenum mode, GLsizei count, GLenum type, const void *indices); +typedef void (*PFNGL_GenBuffers) (GLsizei n, GLuint *buffers); +typedef void (*PFNGL_BindBuffer) (GLenum target, GLuint buffer); +typedef void (*PFNGL_BufferData) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); +typedef void (*PFNGL_DeleteShader) (GLuint shader); +typedef GLuint (*PFNGL_CreateShader) (GLenum type); +typedef void (*PFNGL_ShaderSource) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); +typedef void (*PFNGL_CompileShader) (GLuint shader); +typedef void (*PFNGL_GetShaderiv) (GLuint shader, GLenum pname, GLint *params); +typedef void (*PFNGL_GetShaderInfoLog) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef GLuint (*PFNGL_CreateProgram) (void); +typedef void (*PFNGL_GetProgramInfoLog) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (*PFNGL_AttachShader) (GLuint program, GLuint shader); +typedef void (*PFNGL_LinkProgram) (GLuint program); +typedef void (*PFNGL_GetProgramiv) (GLuint program, GLenum pname, GLint *params); +typedef void (*PFNGL_GenVertexArrays) (GLsizei n, GLuint *arrays); +typedef GLuint (*PFNGL_GetUniformBlockIndex) (GLuint program, const GLchar* unifromBlockName); +typedef void (*PFNGL_UniformBlockBinding) (GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); +typedef void (*PFNGL_GenTextures) (GLsizei n, GLuint *textures); +typedef void (*PFNGL_BindTexture) (GLenum target, GLuint texture); +typedef void (*PFNGL_TexImage2D) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); +typedef void (*PFNGL_TexSubImage2D) (GLenum target, GLint level, GLint xofffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels); +typedef void (*PFNGL_Disable) (GLenum cap); +typedef void (*PFNGL_Enable) (GLenum cap); +typedef void (*PFNGL_Clear) (GLbitfield mask); +typedef void (*PFNGL_ClearColor) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +typedef void (*PFNGL_ClearDepth) (GLdouble depth); +typedef void (*PFNGL_CullFace) (GLenum mode); +typedef void (*PFNGL_FrontFace) (GLenum mode); +typedef void (*PFNGL_BlendFunc) (GLenum sfactor, GLenum dfactor); +typedef void (*PFNGL_Viewport) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (*PFNGL_UseProgram) (GLuint program); +typedef void (*PFNGL_BindVertexArray) (GLuint array); +typedef void (*PFNGL_ActiveTexture) (GLenum texture); +typedef void (*PFNGL_DeleteBuffers) (GLsizei n, const GLuint *buffers); +typedef void (*PFNGL_DeleteTextures) (GLsizei n, const GLuint *textures); +typedef void* (*PFNGL_MapBuffer) (GLenum target, GLenum access); +typedef GLboolean (*PFNGL_UnmapBuffer) (GLenum target); +typedef void (*PFNGL_EnableVertexAttribArray) (GLuint index); +typedef void (*PFNGL_VertexAttribDivisor) (GLuint index, GLuint divisor); +typedef void (*PFNGL_VertexAttribPointer) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +typedef void (*PFNGL_BindBufferBase) (GLenum target, GLuint index, GLuint buffer); +typedef void (*PFNGL_TexParameteri) (GLenum target, GLenum pname, GLint param); +typedef void (*PFNGL_Scissor) (GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (*PFNGL_DrawArraysInstanced) (GLenum mode, GLint first, GLsizei count, GLsizei instancecount); +typedef void (*PFNGL_DeleteFramebuffers) (GLsizei n, const GLuint *framebuffers); +typedef void (*PFNGL_GenFramebuffers) (GLsizei n, GLuint *ids); +typedef void (*PFNGL_BindFramebuffer) (GLenum target, GLuint framebuffer); +typedef void (*PFNGL_FramebufferTexture2D) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (*PFNGL_Uniform2f) (GLint location, GLfloat v0, GLfloat v1); +typedef void (*PFNGL_UniformMatrix4fv) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef GLint (*PFNGL_GetUniformLocation) (GLuint program, const GLchar *name); +typedef void (*PFNGL_DepthFunc) (GLenum func); +typedef GLubyte* (*PFNGL_GetString) (GLenum name); +typedef GLubyte* (*PFNGL_GetStringi) (GLenum name, GLuint index); + +const char* rgl_function_names[] = +{ +"glDrawArrays", +"glDrawElements", +"glGenBuffers", +"glBindBuffer", +"glBufferData", +"glDeleteShader", +"glCreateShader", +"glShaderSource", +"glCompileShader", +"glGetShaderiv", +"glGetShaderInfoLog", +"glCreateProgram", +"glGetProgramInfoLog", +"glAttachShader", +"glLinkProgram", +"glGetProgramiv", +"glGenVertexArrays", +"glGetUniformBlockIndex", +"glUniformBlockBinding", +"glGenTextures", +"glBindTexture", +"glTexImage2D", +"glTexSubImage2D", +"glDisable", +"glEnable", +"glClear", +"glClearColor", +"glClearDepth", +"glCullFace", +"glFrontFace", +"glBlendFunc", +"glViewport", +"glUseProgram", +"glBindVertexArray", +"glActiveTexture", +"glDeleteBuffers", +"glDeleteTextures", +"glMapBuffer", +"glUnmapBuffer", +"glEnableVertexAttribArray", +"glVertexAttribDivisor", +"glVertexAttribPointer", +"glBindBufferBase", +"glTexParameteri", +"glScissor", +"glDrawArraysInstanced", +"glDeleteFramebuffers", +"glGenFramebuffers", +"glBindFramebuffer", +"glFramebufferTexture2D", +"glUniform2f", +"glUniformMatrix4fv", +"glGetUniformLocation", +"glDepthFunc", +"glGetString", +"glGetStringi", +}; + +typedef struct R_GLProcFunctions R_GLProcFunctions; +struct R_GLProcFunctions +{ +union +{ +void* _pointers[ArrayCount(rgl_function_names)]; +struct +{ +PFNGL_DrawArrays DrawArrays; +PFNGL_DrawElements DrawElements; +PFNGL_GenBuffers GenBuffers; +PFNGL_BindBuffer BindBuffer; +PFNGL_BufferData BufferData; +PFNGL_DeleteShader DeleteShader; +PFNGL_CreateShader CreateShader; +PFNGL_ShaderSource ShaderSource; +PFNGL_CompileShader CompileShader; +PFNGL_GetShaderiv GetShaderiv; +PFNGL_GetShaderInfoLog GetShaderInfoLog; +PFNGL_CreateProgram CreateProgram; +PFNGL_GetProgramInfoLog GetProgramInfoLog; +PFNGL_AttachShader AttachShader; +PFNGL_LinkProgram LinkProgram; +PFNGL_GetProgramiv GetProgramiv; +PFNGL_GenVertexArrays GenVertexArrays; +PFNGL_GetUniformBlockIndex GetUniformBlockIndex; +PFNGL_UniformBlockBinding UniformBlockBinding; +PFNGL_GenTextures GenTextures; +PFNGL_BindTexture BindTexture; +PFNGL_TexImage2D TexImage2D; +PFNGL_TexSubImage2D TexSubImage2D; +PFNGL_Disable Disable; +PFNGL_Enable Enable; +PFNGL_Clear Clear; +PFNGL_ClearColor ClearColor; +PFNGL_ClearDepth ClearDepth; +PFNGL_CullFace CullFace; +PFNGL_FrontFace FrontFace; +PFNGL_BlendFunc BlendFunc; +PFNGL_Viewport Viewport; +PFNGL_UseProgram UseProgram; +PFNGL_BindVertexArray BindVertexArray; +PFNGL_ActiveTexture ActiveTexture; +PFNGL_DeleteBuffers DeleteBuffers; +PFNGL_DeleteTextures DeleteTextures; +PFNGL_MapBuffer MapBuffer; +PFNGL_UnmapBuffer UnmapBuffer; +PFNGL_EnableVertexAttribArray EnableVertexAttribArray; +PFNGL_VertexAttribDivisor VertexAttribDivisor; +PFNGL_VertexAttribPointer VertexAttribPointer; +PFNGL_BindBufferBase BindBufferBase; +PFNGL_TexParameteri TexParameteri; +PFNGL_Scissor Scissor; +PFNGL_DrawArraysInstanced DrawArraysInstanced; +PFNGL_DeleteFramebuffers DeleteFramebuffers; +PFNGL_GenFramebuffers GenFramebuffers; +PFNGL_BindFramebuffer BindFramebuffer; +PFNGL_FramebufferTexture2D FramebufferTexture2D; +PFNGL_Uniform2f Uniform2f; +PFNGL_UniformMatrix4fv UniformMatrix4fv; +PFNGL_GetUniformLocation GetUniformLocation; +PFNGL_DepthFunc DepthFunc; +PFNGL_GetString GetString; +PFNGL_GetStringi GetStringi; +}; +}; +}; + +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_UNIFORM_BUFFER 0x8A11 +#define GL_STREAM_DRAW 0x88E0 +#define GL_STATIC_DRAW 0x88E4 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_FALSE 0 +#define GL_TRUE 1 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_ONE 1 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_CULL_FACE 0x0B44 +#define GL_DEPTH_TEST 0x0B71 +#define GL_STENCIL_TEST 0x0B90 +#define GL_VIEWPORT 0x0BA2 +#define GL_BLEND 0x0BE2 +#define GL_SCISSOR_TEST 0x0C11 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_RGBA 0x1908 +#define GL_BGRA 0x80E1 +#define GL_RED 0x1903 +#define GL_RG 0x8227 +#define GL_R8 0x8229 +#define GL_RG8 0x822B +#define GL_RGBA8 0x8058 +#define GL_R16 0x822A +#define GL_RGBA16 0x805B +#define GL_R32F 0x822E +#define GL_RG32F 0x8230 +#define GL_RGBA32F 0x8814 +#define GL_UNSIGNED_INT_24_8 0x84fa +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_CW 0x0900 +#define GL_TEXTURE0 0x84C0 +#define GL_FRAMEBUFFER 0x8d40 +#define GL_COLOR_ATTACHMENT0 0x8ce0 +#define GL_DEPTH_STENCIL_ATTACHMENT 0x821a +#define GL_DEPTH_STENCIL 0x84f9 +#define GL_DEPTH24_STENCIL8 0x88f0 +#define GL_DEPTH_BUFFER_BIT 0x0100 +#define GL_LESS 0x0201 +#define GL_GREATER 0x0204 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_CLAMP_TO_EDGE 0x812F + + +C_LINKAGE_BEGIN +read_only global String8 rgl_rect_common_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"#define float2 vec2\n" +"#define float3 vec3\n" +"#define float4 vec4\n" +"#define float3x3 mat3\n" +"#define float4x4 mat4\n" +"\n" +"layout (std140) uniform Globals\n" +"{\n" +" float2 viewport_size_px;\n" +" float opacity;\n" +" float _padding;\n" +"\n" +" float4x4 texture_sample_channel_map;\n" +"\n" +" float2 texture_t2d_size_px;\n" +" float _padding1;\n" +" float _padding2;\n" +"\n" +" mat4x4 xform;\n" +"\n" +" float2 xform_scale;\n" +" float _padding3;\n" +" float _padding4;\n" +"};\n" +"" +); + +read_only global String8 rgl_rect_vs_src = +str8_lit_comp( +"" +"\n" +"layout (location=0) in float4 a_dst_rect_px;\n" +"layout (location=1) in float4 a_src_rect_px;\n" +"layout (location=2) in float4 a_color00;\n" +"layout (location=3) in float4 a_color01;\n" +"layout (location=4) in float4 a_color10;\n" +"layout (location=5) in float4 a_color11;\n" +"layout (location=6) in float4 a_corner_radii_px;\n" +"layout (location=7) in float4 a_style_params;\n" +"\n" +"out Vertex2Pixel\n" +"{\n" +" flat float2 rect_half_size_px;\n" +" float2 texcoord_pct;\n" +" float2 sdf_sample_pos;\n" +" float4 tint;\n" +" float corner_radius_px;\n" +" flat float border_thickness_px;\n" +" flat float softness_px;\n" +" flat float omit_texture;\n" +"} vertex2pixel;\n" +"\n" +"void main()\n" +"{\n" +" //- rjf: unpack & xform rectangle src/dst vertices\n" +" float2 dst_p0_px = a_dst_rect_px.xy;\n" +" float2 dst_p1_px = a_dst_rect_px.zw;\n" +" float2 src_p0_px = a_src_rect_px.xy;\n" +" float2 src_p1_px = a_src_rect_px.zw;\n" +" float2 dst_size_px = abs(dst_p1_px - dst_p0_px);\n" +"\n" +" //- rjf: unpack style params\n" +" float border_thickness_px = a_style_params.x;\n" +" float softness_px = a_style_params.y;\n" +" float omit_texture = a_style_params.z;\n" +"\n" +" //- rjf: prep per-vertex arrays to sample from (p: position, t: texcoord, c: colorcoord, r: cornerradius)\n" +" float2 dst_p_verts_px[4];\n" +" dst_p_verts_px[0] = float2(dst_p0_px.x, dst_p1_px.y);\n" +" dst_p_verts_px[1] = float2(dst_p0_px.x, dst_p0_px.y);\n" +" dst_p_verts_px[2] = float2(dst_p1_px.x, dst_p1_px.y);\n" +" dst_p_verts_px[3] = float2(dst_p1_px.x, dst_p0_px.y);\n" +"\n" +" float2 src_p_verts_px[4];\n" +" src_p_verts_px[0] = float2(src_p0_px.x, src_p1_px.y);\n" +" src_p_verts_px[1] = float2(src_p0_px.x, src_p0_px.y);\n" +" src_p_verts_px[2] = float2(src_p1_px.x, src_p1_px.y);\n" +" src_p_verts_px[3] = float2(src_p1_px.x, src_p0_px.y);\n" +"\n" +" float dst_r_verts_px[4] = float[](\n" +" a_corner_radii_px.y,\n" +" a_corner_radii_px.x,\n" +" a_corner_radii_px.w,\n" +" a_corner_radii_px.z\n" +" );\n" +"\n" +" float4 src_color[4];\n" +" src_color[0] = a_color01;\n" +" src_color[1] = a_color00;\n" +" src_color[2] = a_color11;\n" +" src_color[3] = a_color10;\n" +"\n" +" int vertex_id = gl_VertexID;\n" +" float2 dst_verts_pct = float2((vertex_id >> 1) != 0 ? 1.f : 0.f,\n" +" (vertex_id & 1) != 0 ? 0.f : 1.f);\n" +"\n" +" // rjf: fill vertex -> pixel data\n" +" {\n" +" float2 xformed_pos = (transpose(xform) * float4(dst_p_verts_px[vertex_id], 1.f, 0.0f)).xy;\n" +" xformed_pos.y = viewport_size_px.y - xformed_pos.y;\n" +" gl_Position.xy = 2.f * xformed_pos/viewport_size_px - 1.f;\n" +" gl_Position.z = 0.f;\n" +" gl_Position.w = 1.f;\n" +" vertex2pixel.rect_half_size_px = dst_size_px / 2.f * xform_scale;\n" +" vertex2pixel.texcoord_pct = src_p_verts_px[vertex_id] / texture_t2d_size_px;\n" +" vertex2pixel.sdf_sample_pos = (2.f * dst_verts_pct - 1.f) * vertex2pixel.rect_half_size_px;\n" +" vertex2pixel.tint = src_color[vertex_id];\n" +" vertex2pixel.corner_radius_px = dst_r_verts_px[vertex_id];\n" +" vertex2pixel.border_thickness_px = border_thickness_px;\n" +" vertex2pixel.softness_px = softness_px;\n" +" vertex2pixel.omit_texture = omit_texture;\n" +" }\n" +"}\n" +"" +); + +read_only global String8 rgl_rect_fs_src = +str8_lit_comp( +"" +"\n" +"in Vertex2Pixel\n" +"{\n" +" flat float2 rect_half_size_px;\n" +" float2 texcoord_pct;\n" +" float2 sdf_sample_pos;\n" +" float4 tint;\n" +" float corner_radius_px;\n" +" flat float border_thickness_px;\n" +" flat float softness_px;\n" +" flat float omit_texture;\n" +"} vertex2pixel;\n" +"\n" +"out float4 o_final_color;\n" +"\n" +"uniform sampler2D main_t2d;\n" +"\n" +"float rect_sdf(float2 sample_pos, float2 rect_half_size, float r)\n" +"{\n" +" return length(max(abs(sample_pos) - rect_half_size + r, 0.0)) - r;\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" // rjf: blend corner colors to produce final tint\n" +" float4 tint = vertex2pixel.tint;\n" +"\n" +" // rjf: sample texture\n" +" float4 albedo_sample = float4(1, 1, 1, 1);\n" +" if(vertex2pixel.omit_texture < 1)\n" +" {\n" +" albedo_sample = texture(main_t2d, vertex2pixel.texcoord_pct) * transpose(texture_sample_channel_map);\n" +" }\n" +"\n" +" // rjf: determine SDF sample position\n" +" float2 sdf_sample_pos = vertex2pixel.sdf_sample_pos;\n" +"\n" +" // rjf: sample for borders\n" +" float border_sdf_t = 1;\n" +" if(vertex2pixel.border_thickness_px > 0)\n" +" {\n" +" float border_sdf_s = rect_sdf(sdf_sample_pos,\n" +" vertex2pixel.rect_half_size_px - float2(vertex2pixel.softness_px*2.f, vertex2pixel.softness_px*2.f) - vertex2pixel.border_thickness_px,\n" +" max(vertex2pixel.corner_radius_px-vertex2pixel.border_thickness_px, 0));\n" +" border_sdf_t = smoothstep(0, 2*vertex2pixel.softness_px, border_sdf_s);\n" +" }\n" +" if(border_sdf_t < 0.001f)\n" +" {\n" +" discard;\n" +" }\n" +"\n" +" // rjf: sample for corners\n" +" float corner_sdf_t = 1;\n" +" if(vertex2pixel.corner_radius_px > 0 || vertex2pixel.softness_px > 0.75f)\n" +" {\n" +" float corner_sdf_s = rect_sdf(sdf_sample_pos,\n" +" vertex2pixel.rect_half_size_px - float2(vertex2pixel.softness_px*2.f, vertex2pixel.softness_px*2.f),\n" +" vertex2pixel.corner_radius_px);\n" +" corner_sdf_t = 1-smoothstep(0, 2*vertex2pixel.softness_px, corner_sdf_s);\n" +" }\n" +"\n" +" // rjf: form+return final color\n" +" o_final_color = albedo_sample;\n" +" o_final_color *= tint;\n" +" o_final_color.a *= opacity;\n" +" o_final_color.a *= corner_sdf_t;\n" +" o_final_color.a *= border_sdf_t;\n" +"}\n" +"" +); + +read_only global String8 rgl_finalize_common_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"#define float2 vec2\n" +"#define float3 vec3\n" +"#define float4 vec4\n" +"#define float3x3 mat3\n" +"#define float4x4 mat4\n" +"" +); + +read_only global String8 rgl_finalize_vs_src = +str8_lit_comp( +"" +"\n" +"out Vertex2Pixel\n" +"{\n" +" float2 uv;\n" +"} v2p;\n" +"\n" +"void main()\n" +"{\n" +" int vertex_id = gl_VertexID;\n" +" float2 uv = vec2(vertex_id & 1, vertex_id >> 1);\n" +"\n" +" v2p.uv = uv;\n" +" gl_Position = vec4(uv * 2.0 - 1.0, 0.0, 1.0);\n" +"}\n" +"" +); + +read_only global String8 rgl_finalize_fs_src = +str8_lit_comp( +"" +"\n" +"in Vertex2Pixel\n" +"{\n" +" float2 uv;\n" +"} v2p;\n" +"\n" +"uniform sampler2D stage_t2d;\n" +"\n" +"out float4 o_final_color;\n" +"\n" +"void main()\n" +"{\n" +" o_final_color = float4(texture(stage_t2d, v2p.uv).rgb, 1.0);\n" +"}\n" +"" +); + +read_only global String8 rgl_blur_common_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"#define float2 vec2\n" +"#define float3 vec3\n" +"#define float4 vec4\n" +"#define float3x3 mat3\n" +"#define float4x4 mat4\n" +"\n" +"layout (std140) uniform Globals\n" +"{\n" +" float4 rect;\n" +" float4 corner_radii_px;\n" +"\n" +" float2 viewport_size;\n" +" uint blur_count;\n" +" uint _padding;\n" +"\n" +" float4 kernel[32];\n" +"};\n" +"" +); + +read_only global String8 rgl_blur_vs_src = +str8_lit_comp( +"" +"\n" +"out Vertex2Pixel\n" +"{\n" +" float2 texcoord;\n" +" float2 sdf_sample_pos;\n" +" flat float2 rect_half_size;\n" +" float corner_radius;\n" +"} v2p;\n" +"\n" +"void main()\n" +"{\n" +" float2 vertex_positions_scrn[4];\n" +" vertex_positions_scrn[0] = rect.xw;\n" +" vertex_positions_scrn[1] = rect.xy;\n" +" vertex_positions_scrn[2] = rect.zw;\n" +" vertex_positions_scrn[3] = rect.zy;\n" +"\n" +" float corner_radii_px[] = float[]\n" +" (\n" +" corner_radii_px.y,\n" +" corner_radii_px.x,\n" +" corner_radii_px.w,\n" +" corner_radii_px.z\n" +" );\n" +"\n" +" int vertex_id = gl_VertexID;\n" +" float2 cornercoords_pct = float2(\n" +" (vertex_id >> 1) != 0 ? 1.f : 0.f,\n" +" (vertex_id & 1) != 0 ? 0.f : 1.f);\n" +"\n" +" float2 vertex_position_pct = vertex_positions_scrn[vertex_id] / viewport_size;\n" +" float2 vertex_position_scr = 2.f * vertex_position_pct - 1.f;\n" +"\n" +" float2 rect_half_size = float2((rect.z-rect.x)/2, (rect.w-rect.y)/2);\n" +"\n" +" {\n" +" gl_Position = float4(vertex_position_scr.x, -vertex_position_scr.y, 0.f, 1.f);\n" +" v2p.texcoord = vertex_position_pct;\n" +" v2p.texcoord.y = 1.0 - v2p.texcoord.y;\n" +" v2p.sdf_sample_pos = (2.f * cornercoords_pct - 1.f) * rect_half_size;\n" +" v2p.rect_half_size = rect_half_size - 2.f;\n" +" v2p.corner_radius = corner_radii_px[vertex_id];\n" +" }\n" +"}\n" +"" +); + +read_only global String8 rgl_blur_fs_src = +str8_lit_comp( +"" +"\n" +"in Vertex2Pixel\n" +"{\n" +" float2 texcoord;\n" +" float2 sdf_sample_pos;\n" +" flat float2 rect_half_size;\n" +" float corner_radius;\n" +"} v2p;\n" +"\n" +"\n" +"uniform vec2 u_direction;\n" +"uniform sampler2D stage_t2d;\n" +"\n" +"out float4 o_final_color;\n" +"\n" +"float rect_sdf(float2 sample_pos, float2 rect_half_size, float r)\n" +"{\n" +" return length(max(abs(sample_pos) - rect_half_size + r, 0.0)) - r;\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" // rjf: blend weighted texture samples into color\n" +" float3 color = kernel[0].x * texture(stage_t2d, v2p.texcoord).rgb;\n" +"\n" +" for(uint i = 1u; i < blur_count; i += 1u)\n" +" {\n" +" float weight = kernel[i].x;\n" +" float offset = kernel[i].y;\n" +" color += weight * texture(stage_t2d, v2p.texcoord - offset * u_direction).rgb;\n" +" color += weight * texture(stage_t2d, v2p.texcoord + offset * u_direction).rgb;\n" +" }\n" +"\n" +" // rjf: sample for corners\n" +" float corner_sdf_s = rect_sdf(v2p.sdf_sample_pos, v2p.rect_half_size, v2p.corner_radius);\n" +" float corner_sdf_t = 1-smoothstep(0, 2, corner_sdf_s);\n" +"\n" +" // rjf: weight output color by sdf\n" +" // this is doing alpha testing, leave blurring only where mostly opaque pixels are\n" +" if (corner_sdf_t < 0.9f)\n" +" {\n" +" discard;\n" +" }\n" +"\n" +" o_final_color = float4(color, 1.f);\n" +"}\n" +"" +); + +read_only global String8 rgl_mesh_common_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"#define float2 vec2\n" +"#define float3 vec3\n" +"#define float4 vec4\n" +"#define float3x3 mat3\n" +"#define float4x4 mat4\n" +"" +); + +read_only global String8 rgl_mesh_vs_src = +str8_lit_comp( +"" +"\n" +"uniform mat4 xform;\n" +"\n" +"layout(location=0) in float3 a_position;\n" +"layout(location=1) in float3 a_normal;\n" +"layout(location=2) in float2 a_texcoord;\n" +"layout(location=3) in float3 a_color;\n" +"\n" +"out Vertex2Pixel\n" +"{\n" +" float2 texcoord;\n" +" float4 color;\n" +"} v2p;\n" +"\n" +"void main()\n" +"{\n" +" gl_Position = xform * float4(a_position, 1.f);\n" +" v2p.texcoord = a_texcoord;\n" +" v2p.color = float4(a_color, 1.f);\n" +"}\n" +"" +); + +read_only global String8 rgl_mesh_fs_src = +str8_lit_comp( +"" +"\n" +"in Vertex2Pixel\n" +"{\n" +" float2 texcoord;\n" +" float4 color;\n" +"} v2p;\n" +"\n" +"out float4 o_final_color;\n" +"\n" +"void main()\n" +"{\n" +" o_final_color = v2p.color;\n" +"}\n" +"" +); + +read_only global String8 rgl_geo3dcomposite_common_src = +str8_lit_comp( +"" +"\n" +"#version 330 core\n" +"#define float2 vec2\n" +"#define float3 vec3\n" +"#define float4 vec4\n" +"#define float3x3 mat3\n" +"#define float4x4 mat4\n" +"" +); + +read_only global String8 rgl_geo3dcomposite_vs_src = +str8_lit_comp( +"" +"\n" +"out Vertex2Pixel\n" +"{\n" +" float2 uv;\n" +"} v2p;\n" +"\n" +"void main()\n" +"{\n" +" int vertex_id = gl_VertexID;\n" +" float2 uv = vec2(vertex_id & 1, vertex_id >> 1);\n" +"\n" +" v2p.uv = uv;\n" +" gl_Position = vec4(uv * 2.0 - 1.0, 0.0, 1.0);\n" +"}\n" +"" +); + +read_only global String8 rgl_geo3dcomposite_fs_src = +str8_lit_comp( +"" +"\n" +"in Vertex2Pixel\n" +"{\n" +" float2 uv;\n" +"} v2p;\n" +"\n" +"uniform sampler2D stage_t2d;\n" +"\n" +"out float4 o_final_color;\n" +"\n" +"void main()\n" +"{\n" +" o_final_color = float4(texture(stage_t2d, v2p.uv).rgb, 1.0);\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..9436a26a3 --- /dev/null +++ b/src/render/opengl/render_opengl.c @@ -0,0 +1,501 @@ + +#include "generated/render_opengl.meta.h" +#include "generated/render_opengl.meta.c" + +global R_GLContext rgl = {0}; +global R_GLProcFunctions gl = {0}; +R_GLTexture rgl_texture_stub = {0}; + +// NOTE(mallchad): This is Linux specific, whatever, we can think about making it agnostic later. +// Or not... + +// -- Internal Functions -- + +R_Handle +rgl_handle_from_texture(R_GLTexture* texture) +{ + R_Handle result = {0}; + MemoryCopyStruct(&result, &texture->id); + return result; +} + +R_GLTexture* +rgl_texture_from_handle(R_Handle texture) +{ + for (int i=0; iindex = rgl.pipelines.head_size - 1; + R_GLShader* shader1 = ArrayPushTail(&rgl.shaders, NULL); + pipeline->index = rgl.shaders.head_size - 1; + R_GLShader* shader2 = ArrayPushTail(&rgl.shaders, NULL); + pipeline->index = rgl.shaders.head_size - 1; + ArrayAllocate(&pipeline->attached_shaders, rgl.arena, 2); + + pipeline->name = name; + shader1->name = name; + shader1->source_include = source_include; + shader1->source = source_vertex; + shader1->kind = GL_VERTEX_SHADER; + + shader2->name = name; + shader2->source_include = source_include; + shader2->source = source_fragment; + shader2->kind = GL_FRAGMENT_SHADER; + + pipeline->attached_shaders.data[0] = shader1; + pipeline->attached_shaders.data[1] = shader2; + return pipeline; +} + +B32 +rgl_pipeline_refresh(R_GLPipeline* pipeline) +{ + // -- Interface checking -- + Assert(pipeline->name.size); + B32 pipeline_uninitialized = (pipeline->id.data1 == 0); + if (pipeline_uninitialized) + { + pipeline->id = os_make_guid(); + pipeline->handle = gl.CreateProgram(); + Assert(pipeline->handle != 0); // Error creating program + for (int i=0; iattached_shaders.head_size; ++i ) + { + gl.AttachShader(pipeline->handle, pipeline->attached_shaders.data[i]->handle); + } + gl.LinkProgram(pipeline->handle); + } + return 1; +} + +B32 +rgl_shader_refresh(R_GLShader* shader) +{ + // -- Interface checking -- + Assert(shader->name.size); + Assert(shader->source.size); + Temp scratch = scratch_begin(0, 0); + String8 debug_name = {0}; + String8 source = push_str8_cat(scratch.arena, shader->source_include, shader->source); + + // -- New Shader Initialize Pathway -- + B32 shader_uninitialized = (shader->id.data1 == 0); + if (shader_uninitialized) + { + shader->id = os_make_guid(); + RGL_CHECK_ERROR(shader->handle = gl.CreateShader(shader->kind)); + if (glIsShader(shader->handle) == 0) { return 0; } + /* Assert(shader->handle != 0); // Error creating shader */ + switch (shader->kind) + { + case GL_VERTEX_SHADER: + debug_name = push_str8_cat(scratch.arena, str8_lit("vs_"), shader->name); break; + case GL_FRAGMENT_SHADER: + debug_name = push_str8_cat(scratch.arena, str8_lit("fs_"), shader->name); break; + case GL_GEOMETRY_SHADER: + debug_name = push_str8_cat(scratch.arena, str8_lit("gs_"), shader->name); break; + case GL_COMPUTE_SHADER: + debug_name = push_str8_cat(scratch.arena, str8_lit("cs_"), shader->name); break; + case GL_TESS_CONTROL_SHADER: + debug_name = push_str8_cat(scratch.arena, str8_lit("tcs_"), shader->name); break; + case GL_TESS_EVALUATION_SHADER: + debug_name = push_str8_cat(scratch.arena, str8_lit("tes_"), shader->name); break; + default: + debug_name = push_str8_cat(scratch.arena, str8_lit("s_"), shader->name); break; + } + RGL_LATEST_GL( glObjectLabel(GL_SHADER, shader->handle, shader->name.size, shader->name.str) ) + S32 string_size = (S32)source.size; + char* string_list[] = { (char*)source.str }; + gl.ShaderSource(shader->handle, 1, (const char**)string_list, &string_size); + RGL_CHECK_ERROR(gl.CompileShader(shader->handle)); + } + shader->ready = 1; + scratch_end(scratch); + return 1; +} + +// -- Public API Functions + +//- rjf: top-level layer initialization +r_hook void +r_init(CmdLine *cmdln) +{ + // -- Initalize basics -- + U32 rgl_pipeline_limit = 100; + U32 rgl_shader_limit = 300; + rgl.arena = arena_alloc(); + rgl.object_limit = 1000; + rgl.buffer_ids = push_array(rgl.arena, U32, rgl.object_limit); + rgl.vertex_ids = push_array(rgl.arena, U32, rgl.object_limit); + rgl.texture_ids = push_array(rgl.arena, U32, rgl.object_limit); + ArrayAllocate(&rgl.buffers, rgl.arena, rgl.object_limit); + ArrayAllocate(&rgl.vertex_arrays, rgl.arena, rgl.object_limit); + ArrayAllocate(&rgl.textures, rgl.arena, rgl.object_limit); + ArrayAllocate(&rgl.meshes, rgl.arena, rgl.object_limit); + ArrayAllocate(&rgl.shaders, rgl.arena, rgl_shader_limit); + ArrayAllocate(&rgl.pipelines, rgl.arena, rgl_pipeline_limit); + + // -- Load dynamic function pointers -- + void* func_ptr = NULL; + for (int i=0; isize; +} + +r_hook R_Tex2DFormat +r_format_from_tex2d(R_Handle texture) +{ + R_Tex2DFormat result = 0; + NotImplemented; + return result; +} + +r_hook void +r_fill_tex2d_region(R_Handle texture, Rng2S32 subrect, void *data) +{ + +} + +//- rjf: buffers +r_hook R_Handle +r_buffer_alloc(R_ResourceKind kind, U64 size, void *data) +{ + R_Handle result = {0}; + return result; +} + +r_hook void +r_buffer_release(R_Handle buffer) +{ + +} + +//- rjf: frame markers +r_hook void +r_begin_frame(void) +{ + +} + +r_hook void +r_end_frame(void) +{ + +} + +r_hook void +r_window_begin_frame(OS_Handle window, R_Handle window_equip) +{ +#if OS_LINUX + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + B32 switch_result = eglMakeCurrent(gfx_egl_display, _window->first_surface, + _window->first_surface, gfx_egl_context); + Assert(switch_result == EGL_TRUE); + static B32 first_run = 1; + if (first_run) + { + printf("OpenGL Implementation Vendor: %s \n\ +OpenGL Renderer String: %s \n\ +OpenGL Version: %s \n\ +OpenGL Shading Language Version: %s \n", gl.GetString( GL_VENDOR ), glGetString(GL_RENDERER), + glGetString(GL_VERSION), glGetString(GL_SHADING_LANGUAGE_VERSION)); + first_run = 0; + } +#endif +} +r_hook void +r_window_end_frame(OS_Handle window, R_Handle window_equip) +{ + Temp scratch = scratch_begin(0,0); + U8* buffer = push_array(scratch.arena, U8, 1920*1080*4); + Vec4F32 dark_magenta = vec_4f32( 0.2f, 0.f, 0.2f, 1.0f ); +#if OS_LINUX + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + + static B32 regenerate_objects = 1; + if (regenerate_objects) + { + // Setup Objects and Compile Shaders + for (int i=0; ihandle); + + // Enable vsync + S32 vsync_result = eglSwapInterval(gfx_egl_display, 1); + S32 swap_result = eglSwapBuffers(gfx_egl_display, _window->first_surface); + glFlush(); + glFinish(); + +#elif OS_WINDOWS + /* NOTE(mallchad): You can do wglSwapBuffers or whatever is relevant for any + other relevant paltform this isn't seperated into a platform specific + function or file is because this part will literally be the equivilent of + like a 3 line difference or so and I didn't want to change the API yet. */ + glClearColor(dark_magenta.x, dark_magenta.y, dark_magenta.z, dark_magenta.w); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); +#endif // OS_LINUX / OS_Windows + + // Cleanup + fflush(stdout); + scratch_end(scratch); +} + +//- rjf: render pass submission +r_hook void +r_window_submit(OS_Handle window, R_Handle window_equip, R_PassList *passes) +{ + +} diff --git a/src/render/opengl/render_opengl.h b/src/render/opengl/render_opengl.h new file mode 100644 index 000000000..1010b8ca2 --- /dev/null +++ b/src/render/opengl/render_opengl.h @@ -0,0 +1,210 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef RENDER_OPENGL_H +#define RENDER_OPENGL_H + +#define RGL_USE_LATEST_GL 0 + +/// Only run if condition is true +#if RGL_USE_LATEST_GL + #define RGL_LATEST_GL(x) do { (x); } while (0); +#else + #define RGL_LATEST_GL(x) +#endif // RGL_USE_LATEST_GL + +typedef struct R_GLBuffer R_GLBuffer; +struct R_GLBuffer +{ + /// Unique identifier that is stable between OS object regeneration + OS_Guid id; + // Internal location to storage array, may not be 100% stable at all times + U32 index; + // Internal OpenGL handle + U32 handle; + /// The Buffer Binding Target type, ie 'GL_ARRAY_BUFFER' + U32 kind; + /// Denotes if the object is using a valid OpenGL buffer, effectively an "active" tag + B32 bound; + String8 name; +}; +DeclareArray(R_GLBufferArray, R_GLBufferArray); + +/// A single stage of a shader +typedef struct R_GLShader R_GLShader; +struct R_GLShader +{ + OS_Guid id; + U32 index; + U32 handle; + String8 name; + /// The type of shader stage, ie 'GL_FRAGMENT_SHADER' + U32 kind; + /// A source string to include because GLSL is fun and doesn't support includes. + String8 source_include; + /// A source string to be compiled + String8 source; + /// Specifies if 'source_include' and 'source' are actually filenames + B32 source_file; + /// Hints if there is a newer version of this shader + B32 stale; + B32 ready; +}; +DeclareArray(R_GLShaderArray, R_GLShader); +DeclareArray(R_GLShaderPointerArray, R_GLShader*); + +/// A whole program/pipeline of linked shader units +typedef struct R_GLPipeline R_GLPipeline; +struct R_GLPipeline +{ + // Zero ID means object is uninitialized + OS_Guid id; + U32 index; + U32 handle; + String8 name; + R_GLShaderPointerArray attached_shaders; + /// Hints if there is a newer version of this shader pipeline + B32 stale; + /// If this pipeline is ready to be used in rendering + B32 ready; +}; +DeclareArray(R_GLPipelineArray, R_GLPipeline); + +typedef struct R_GLVertexArray R_GLVertexArray; +struct R_GLVertexArray +{ + /// Unique identifier that is stable between OS object regeneration + OS_Guid id; + // Internal location to storage array, may not be 100% stable at all times + U32 index; + // Internal OpenGL handle + U32 handle; + U8* data; + U32 vertex_components; + U32 format; + B32 normalized; +}; +DeclareArray(R_GLVertexArrayArray, R_GLVertexArray); + +typedef struct R_GLTexture R_GLTexture; +struct R_GLTexture +{ + OS_Guid id; + U32 index; + U32 handle; + void* data; + Vec2S32 size; + /// The pixel formaat of 'data' + R_Tex2DFormat format; + /// How to store the data on the GPU side + R_Tex2DFormat format_internal; + // A hint to OpenGL on how the data will be accessed from the GPU side + U32 usage_pattern; + String8 name; + // The name a file to read the texture from, if it exists + String8 source_file; +}; +DeclareArray(R_GLTextureArray, R_GLTexture); + +typedef struct R_GLMesh R_GLMesh; +struct R_GLMesh +{ + OS_Guid id; + U32 index; + U32 handle; + Vec3F32* vertecies; + R_GLTexture texture; + U32 gl_draw_mode; +}; +DeclareArray(R_GLMeshArray, R_GLMesh); + +typedef struct R_GLContext R_GLContext; +struct R_GLContext +{ + Arena* arena; + /// Number of various ID's and array sizes to use + U32 object_limit; + /// Internal OpenGL generated GPU proxy buffers ID's + U32* buffer_ids; + /// Internal OpenGl generated texture ID's + U32* texture_ids; + /// Internal OpenGL generated logical vertex array ID's + U32* vertex_ids; + /// GPU proxy buffers + R_GLBufferArray buffers; + R_GLVertexArrayArray vertex_arrays; + R_GLTextureArray textures; + R_GLMeshArray meshes; + R_GLShaderArray shaders; + R_GLPipelineArray pipelines; + + R_GLPipeline* shader_rectangle; + R_GLPipeline* shader_mesh; + R_GLPipeline* shader_blur; + R_GLPipeline* shader_geometry; + R_GLPipeline* shader_composite; + R_GLPipeline* shader_final; +}; + +/** Conversion table between R_OGL_Tex2DFormat to OpenGL internalformat + I suffix -> integer + UI suffix -> unsigned integar + F suffix -> float 8 */ +U32 rgl_texture_formats[] = +{ + GL_R8UI, + GL_RG8UI, + GL_RGBA8UI, + 0, // BGRA not a valid format but can be used for internal representation, + GL_R16UI, + GL_RGBA16UI, + GL_R32UI, + GL_RG32UI, + GL_RGBA32UI +}; + +internal B32 rgl_read_format_from_texture_format(U32* out_format, U32* out_type, R_Tex2DFormat format); +internal U32 rgl_internal_format_from_texture_format(R_Tex2DFormat format); +internal R_GLTexture* rgl_texture_from_handle(R_Handle texture); +internal R_Handle rgl_handle_from_texture(R_GLTexture* texture); +/// Clear OpenGL error list must clear errors _before calling an OpenGL function, return error count +internal U32 rgl_clear_errors(); +#define RGL_CHECK_ERROR(x) \ + do { rgl_clear_errors(); (x); rgl_check_error( str8_lit(__FILE__), __LINE__ ); } while(0); + +// Returns true if no errors +internal B32 rgl_check_error( String8 source_file, U32 source_line ); +internal B32 rgl_shader_init(R_GLShader* shader); +internal B32 rgl_pipeline_init(R_GLPipeline* pipeline); +internal R_GLPipeline* rgl_pipeline_simple_create(String8 name, String8 source_include, String8 source_vertex, String8 source_fragment); + +//- rjf: top-level layer initialization +r_hook void r_init(CmdLine *cmdln); + +//- rjf: window setup/teardown +r_hook R_Handle r_window_equip(OS_Handle window); +r_hook void r_window_unequip(OS_Handle window, R_Handle window_equip); + +//- rjf: textures +r_hook R_Handle r_tex2d_alloc(R_ResourceKind kind, Vec2S32 size, R_Tex2DFormat format, void *data); +r_hook void r_tex2d_release(R_Handle texture); +r_hook R_ResourceKind r_kind_from_tex2d(R_Handle texture); +r_hook Vec2S32 r_size_from_tex2d(R_Handle texture); +r_hook R_Tex2DFormat r_format_from_tex2d(R_Handle texture); +r_hook void r_fill_tex2d_region(R_Handle texture, Rng2S32 subrect, void *data); + +//- rjf: buffers +r_hook R_Handle r_buffer_alloc(R_ResourceKind kind, U64 size, void *data); +r_hook void r_buffer_release(R_Handle buffer); + +//- rjf: frame markers +r_hook void r_begin_frame(void); +r_hook void r_end_frame(void); +r_hook void r_window_begin_frame(OS_Handle window, R_Handle window_equip); +r_hook void r_window_end_frame(OS_Handle window, R_Handle window_equip); + +//- rjf: render pass submission +r_hook void r_window_submit(OS_Handle window, R_Handle window_equip, R_PassList *passes); + + +#endif // RENDER_OPENGL_H diff --git a/src/render/opengl/render_opengl.mdesk b/src/render/opengl/render_opengl.mdesk new file mode 100644 index 000000000..a1a5af733 --- /dev/null +++ b/src/render/opengl/render_opengl.mdesk @@ -0,0 +1,704 @@ +//////////////////////////////// +//~ dmylo: OpenGL bindings generator +@table(ret, name, params) GLFunctionsTable: +{ + {void DrawArrays "GLenum mode, GLint first, GLsizei count"} + {void DrawElements "GLenum mode, GLsizei count, GLenum type, const void *indices"} + {void GenBuffers "GLsizei n, GLuint *buffers"} + {void BindBuffer "GLenum target, GLuint buffer"} + {void BufferData "GLenum target, GLsizeiptr size, const void *data, GLenum usage"} + {void DeleteShader "GLuint shader"} + {GLuint CreateShader "GLenum type"} + {void ShaderSource "GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length"} + {void CompileShader "GLuint shader"} + {void GetShaderiv "GLuint shader, GLenum pname, GLint *params"} + {void GetShaderInfoLog "GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog"} + {GLuint CreateProgram "void"} + {void GetProgramInfoLog "GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog"} + {void AttachShader "GLuint program, GLuint shader"} + {void LinkProgram "GLuint program"} + {void GetProgramiv "GLuint program, GLenum pname, GLint *params"} + {void GenVertexArrays "GLsizei n, GLuint *arrays"} + {GLuint GetUniformBlockIndex "GLuint program, const GLchar* unifromBlockName"} + {void UniformBlockBinding "GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding"} + {void GenTextures "GLsizei n, GLuint *textures"} + {void BindTexture "GLenum target, GLuint texture"} + {void TexImage2D "GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels"} + {void TexSubImage2D "GLenum target, GLint level, GLint xofffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels"} + {void Disable "GLenum cap"} + {void Enable "GLenum cap"} + {void Clear "GLbitfield mask"} + {void ClearColor "GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha"} + {void ClearDepth "GLdouble depth"} + {void CullFace "GLenum mode"} + {void FrontFace "GLenum mode"} + {void BlendFunc "GLenum sfactor, GLenum dfactor"} + {void Viewport "GLint x, GLint y, GLsizei width, GLsizei height"} + {void UseProgram "GLuint program"} + {void BindVertexArray "GLuint array"} + {void ActiveTexture "GLenum texture"} + {void DeleteBuffers "GLsizei n, const GLuint *buffers"} + {void DeleteTextures "GLsizei n, const GLuint *textures"} + {"void*" MapBuffer "GLenum target, GLenum access"} + {GLboolean UnmapBuffer "GLenum target"} + {void EnableVertexAttribArray "GLuint index"} + {void VertexAttribDivisor "GLuint index, GLuint divisor"} + {void VertexAttribPointer "GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer"} + {void BindBufferBase "GLenum target, GLuint index, GLuint buffer"} + {void TexParameteri "GLenum target, GLenum pname, GLint param"} + {void Scissor "GLint x, GLint y, GLsizei width, GLsizei height"} + {void DrawArraysInstanced "GLenum mode, GLint first, GLsizei count, GLsizei instancecount"} + {void DeleteFramebuffers "GLsizei n, const GLuint *framebuffers"} + {void GenFramebuffers "GLsizei n, GLuint *ids"} + {void BindFramebuffer "GLenum target, GLuint framebuffer"} + {void FramebufferTexture2D "GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level"} + {void Uniform2f "GLint location, GLfloat v0, GLfloat v1"} + {void UniformMatrix4fv "GLint location, GLsizei count, GLboolean transpose, const GLfloat *value"} + {GLint GetUniformLocation "GLuint program, const GLchar *name"} + {void DepthFunc "GLenum func"} + {"GLubyte*" GetString "GLenum name"} + {"GLubyte*" GetStringi "GLenum name, GLuint index"} +} + +@table(name, value) OGL_DefinesTable: +{ + {GL_ARRAY_BUFFER 0x8892} + {GL_ELEMENT_ARRAY_BUFFER 0x8893} + {GL_UNIFORM_BUFFER 0x8A11} + {GL_STREAM_DRAW 0x88E0} + {GL_STATIC_DRAW 0x88E4} + {GL_DYNAMIC_DRAW 0x88E8} + {GL_WRITE_ONLY 0x88B9} + {GL_FRAGMENT_SHADER 0x8B30} + {GL_VERTEX_SHADER 0x8B31} + {GL_COMPILE_STATUS 0x8B81} + {GL_LINK_STATUS 0x8B82} + {GL_INFO_LOG_LENGTH 0x8B84} + {GL_COLOR_BUFFER_BIT 0x00004000} + {GL_FALSE 0} + {GL_TRUE 1} + {GL_TRIANGLES 0x0004} + {GL_TRIANGLE_STRIP 0x0005} + {GL_ONE 1} + {GL_SRC_ALPHA 0x0302} + {GL_ONE_MINUS_SRC_ALPHA 0x0303} + {GL_FRONT 0x0404} + {GL_BACK 0x0405} + {GL_FRONT_AND_BACK 0x0408} + {GL_CULL_FACE 0x0B44} + {GL_DEPTH_TEST 0x0B71} + {GL_STENCIL_TEST 0x0B90} + {GL_VIEWPORT 0x0BA2} + {GL_BLEND 0x0BE2} + {GL_SCISSOR_TEST 0x0C11} + {GL_TEXTURE_2D 0x0DE1} + {GL_UNSIGNED_BYTE 0x1401} + {GL_UNSIGNED_SHORT 0x1403} + {GL_UNSIGNED_INT 0x1405} + {GL_FLOAT 0x1406} + {GL_RGBA 0x1908} + {GL_BGRA 0x80E1} + {GL_RED 0x1903} + {GL_RG 0x8227} + {GL_R8 0x8229} + {GL_RG8 0x822B} + {GL_RGBA8 0x8058} + {GL_R16 0x822A} + {GL_RGBA16 0x805B} + {GL_R32F 0x822E} + {GL_RG32F 0x8230} + {GL_RGBA32F 0x8814} + {GL_UNSIGNED_INT_24_8 0x84fa} + {GL_NEAREST 0x2600} + {GL_LINEAR 0x2601} + {GL_TEXTURE_MAG_FILTER 0x2800} + {GL_TEXTURE_MIN_FILTER 0x2801} + {GL_CW 0x0900} + {GL_TEXTURE0 0x84C0} + {GL_FRAMEBUFFER 0x8d40} + {GL_COLOR_ATTACHMENT0 0x8ce0} + {GL_DEPTH_STENCIL_ATTACHMENT 0x821a} + {GL_DEPTH_STENCIL 0x84f9} + {GL_DEPTH24_STENCIL8 0x88f0} + {GL_DEPTH_BUFFER_BIT 0x0100} + {GL_LESS 0x0201} + {GL_GREATER 0x0204} + {GL_TEXTURE_WRAP_S 0x2802} + {GL_TEXTURE_WRAP_T 0x2803} + {GL_CLAMP_TO_EDGE 0x812F} +} + +//- dmylo: base types +@gen +{ + `// Adapted from Dear ImGui generated OpenGL bindings. + `// Adapted from KHR/khrplatform.h to avoid including entire file.` + `#ifndef __khrplatform_h_` + `typedef float khronos_float_t;` + `typedef signed char khronos_int8_t;` + `typedef unsigned char khronos_uint8_t;` + `typedef signed short int khronos_int16_t;` + `typedef unsigned short int khronos_uint16_t;` + `#ifdef _WIN64` + `typedef signed long long int khronos_intptr_t;` + `typedef signed long long int khronos_ssize_t;` + `#else` + `typedef signed long int khronos_intptr_t;` + `typedef signed long int khronos_ssize_t;` + `#endif` + `` + `#if defined(_MSC_VER) && !defined(__clang__)` + `typedef signed __int64 khronos_int64_t;` + `typedef unsigned __int64 khronos_uint64_t;` + `#elif (defined(__clang__) || defined(__GNUC__)) && (__cplusplus < 201100)` + `#include ` + `typedef int64_t khronos_int64_t;` + `typedef uint64_t khronos_uint64_t;` + `#else` + `typedef signed long long khronos_int64_t;` + `typedef unsigned long long khronos_uint64_t;` + `#endif` + `#endif // __khrplatform_h_` + `` + `typedef void GLvoid;` + `typedef unsigned int GLenum;` + `typedef khronos_float_t GLfloat;` + `typedef int GLint;` + `typedef int GLsizei;` + `typedef unsigned int GLbitfield;` + `typedef double GLdouble;` + `typedef unsigned int GLuint;` + `typedef unsigned char GLboolean;` + `typedef khronos_uint8_t GLubyte;` + `typedef khronos_float_t GLclampf;` + `typedef double GLclampd;` + `typedef khronos_ssize_t GLsizeiptr;` + `typedef khronos_intptr_t GLintptr;` + `typedef char GLchar;` + `typedef khronos_int16_t GLshort;` + `typedef khronos_int8_t GLbyte;` + `typedef khronos_uint16_t GLushort;` + `typedef khronos_uint16_t GLhalf;` + `typedef struct __GLsync *GLsync;` + `typedef khronos_uint64_t GLuint64;` + `typedef khronos_int64_t GLint64;` + `typedef khronos_uint64_t GLuint64EXT;` + `typedef khronos_int64_t GLint64EXT;` +` +} + +@gen{ `` } + +//- dmylo: Functions typedefs +@gen +{ + @expand(GLFunctionsTable a) `typedef $(a.ret) (*PFNGL_$(a.name)) ($(a.params));` +} + +@gen{ `` } + +//- dmylo: Function names +@gen +{ + `const char* rgl_function_names[] = ` + `{` +} + +@gen +{ + @expand(GLFunctionsTable a) ` "gl$(a.name)",`, +} + +@gen +{ + `};` +} + +@gen{ `` } + +//- dmylo: Functions struct +@gen +{ + `typedef struct R_GLProcFunctions R_GLProcFunctions;` + `struct R_GLProcFunctions` + `{` + ` union` + ` {` + ` void* _pointers[ArrayCount(rgl_function_names)];` + ` struct` + ` {` +} + +@gen +{ + @expand(GLFunctionsTable a) ` PFNGL_$(a.name) $(a.name);`, +} + +@gen +{ + ` };` + ` };` + `};` +} + +@gen{ `` } + +@gen +{ + @expand(OGL_DefinesTable a) `#define $(a.name) $(a.value)` +} + +@gen{ `` } +@gen{ `` } + +//////////////////////////////// +//~ dmylo: UI Rectangle Shaders +@embed_string rgl_rect_common_src: +""" +#version 330 core +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define float3x3 mat3 +#define float4x4 mat4 + +layout (std140) uniform Globals +{ + float2 viewport_size_px; + float opacity; + float _padding; + + float4x4 texture_sample_channel_map; + + float2 texture_t2d_size_px; + float _padding1; + float _padding2; + + mat4x4 xform; + + float2 xform_scale; + float _padding3; + float _padding4; +}; +""" + +@embed_string rgl_rect_vs_src: +""" +layout (location=0) in float4 a_dst_rect_px; +layout (location=1) in float4 a_src_rect_px; +layout (location=2) in float4 a_color00; +layout (location=3) in float4 a_color01; +layout (location=4) in float4 a_color10; +layout (location=5) in float4 a_color11; +layout (location=6) in float4 a_corner_radii_px; +layout (location=7) in float4 a_style_params; + +out Vertex2Pixel +{ + flat float2 rect_half_size_px; + float2 texcoord_pct; + float2 sdf_sample_pos; + float4 tint; + float corner_radius_px; + flat float border_thickness_px; + flat float softness_px; + flat float omit_texture; +} vertex2pixel; + +void main() +{ + //- rjf: unpack & xform rectangle src/dst vertices + float2 dst_p0_px = a_dst_rect_px.xy; + float2 dst_p1_px = a_dst_rect_px.zw; + float2 src_p0_px = a_src_rect_px.xy; + float2 src_p1_px = a_src_rect_px.zw; + float2 dst_size_px = abs(dst_p1_px - dst_p0_px); + + //- rjf: unpack style params + float border_thickness_px = a_style_params.x; + float softness_px = a_style_params.y; + float omit_texture = a_style_params.z; + + //- rjf: prep per-vertex arrays to sample from (p: position, t: texcoord, c: colorcoord, r: cornerradius) + float2 dst_p_verts_px[4]; + dst_p_verts_px[0] = float2(dst_p0_px.x, dst_p1_px.y); + dst_p_verts_px[1] = float2(dst_p0_px.x, dst_p0_px.y); + dst_p_verts_px[2] = float2(dst_p1_px.x, dst_p1_px.y); + dst_p_verts_px[3] = float2(dst_p1_px.x, dst_p0_px.y); + + float2 src_p_verts_px[4]; + src_p_verts_px[0] = float2(src_p0_px.x, src_p1_px.y); + src_p_verts_px[1] = float2(src_p0_px.x, src_p0_px.y); + src_p_verts_px[2] = float2(src_p1_px.x, src_p1_px.y); + src_p_verts_px[3] = float2(src_p1_px.x, src_p0_px.y); + + float dst_r_verts_px[4] = float[]( + a_corner_radii_px.y, + a_corner_radii_px.x, + a_corner_radii_px.w, + a_corner_radii_px.z + ); + + float4 src_color[4]; + src_color[0] = a_color01; + src_color[1] = a_color00; + src_color[2] = a_color11; + src_color[3] = a_color10; + + int vertex_id = gl_VertexID; + float2 dst_verts_pct = float2((vertex_id >> 1) != 0 ? 1.f : 0.f, + (vertex_id & 1) != 0 ? 0.f : 1.f); + + // rjf: fill vertex -> pixel data + { + float2 xformed_pos = (transpose(xform) * float4(dst_p_verts_px[vertex_id], 1.f, 0.0f)).xy; + xformed_pos.y = viewport_size_px.y - xformed_pos.y; + gl_Position.xy = 2.f * xformed_pos/viewport_size_px - 1.f; + gl_Position.z = 0.f; + gl_Position.w = 1.f; + vertex2pixel.rect_half_size_px = dst_size_px / 2.f * xform_scale; + vertex2pixel.texcoord_pct = src_p_verts_px[vertex_id] / texture_t2d_size_px; + vertex2pixel.sdf_sample_pos = (2.f * dst_verts_pct - 1.f) * vertex2pixel.rect_half_size_px; + vertex2pixel.tint = src_color[vertex_id]; + vertex2pixel.corner_radius_px = dst_r_verts_px[vertex_id]; + vertex2pixel.border_thickness_px = border_thickness_px; + vertex2pixel.softness_px = softness_px; + vertex2pixel.omit_texture = omit_texture; + } +} +""" + +@embed_string rgl_rect_fs_src: +""" +in Vertex2Pixel +{ + flat float2 rect_half_size_px; + float2 texcoord_pct; + float2 sdf_sample_pos; + float4 tint; + float corner_radius_px; + flat float border_thickness_px; + flat float softness_px; + flat float omit_texture; +} vertex2pixel; + +out float4 o_final_color; + +uniform sampler2D main_t2d; + +float rect_sdf(float2 sample_pos, float2 rect_half_size, float r) +{ + return length(max(abs(sample_pos) - rect_half_size + r, 0.0)) - r; +} + +void main() +{ + // rjf: blend corner colors to produce final tint + float4 tint = vertex2pixel.tint; + + // rjf: sample texture + float4 albedo_sample = float4(1, 1, 1, 1); + if(vertex2pixel.omit_texture < 1) + { + albedo_sample = texture(main_t2d, vertex2pixel.texcoord_pct) * transpose(texture_sample_channel_map); + } + + // rjf: determine SDF sample position + float2 sdf_sample_pos = vertex2pixel.sdf_sample_pos; + + // rjf: sample for borders + float border_sdf_t = 1; + if(vertex2pixel.border_thickness_px > 0) + { + float border_sdf_s = rect_sdf(sdf_sample_pos, + vertex2pixel.rect_half_size_px - float2(vertex2pixel.softness_px*2.f, vertex2pixel.softness_px*2.f) - vertex2pixel.border_thickness_px, + max(vertex2pixel.corner_radius_px-vertex2pixel.border_thickness_px, 0)); + border_sdf_t = smoothstep(0, 2*vertex2pixel.softness_px, border_sdf_s); + } + if(border_sdf_t < 0.001f) + { + discard; + } + + // rjf: sample for corners + float corner_sdf_t = 1; + if(vertex2pixel.corner_radius_px > 0 || vertex2pixel.softness_px > 0.75f) + { + float corner_sdf_s = rect_sdf(sdf_sample_pos, + vertex2pixel.rect_half_size_px - float2(vertex2pixel.softness_px*2.f, vertex2pixel.softness_px*2.f), + vertex2pixel.corner_radius_px); + corner_sdf_t = 1-smoothstep(0, 2*vertex2pixel.softness_px, corner_sdf_s); + } + + // rjf: form+return final color + o_final_color = albedo_sample; + o_final_color *= tint; + o_final_color.a *= opacity; + o_final_color.a *= corner_sdf_t; + o_final_color.a *= border_sdf_t; +} +""" + +//////////////////////////////// +//~ dmylo: UI Finalize Shaders +@embed_string rgl_finalize_common_src: +""" +#version 330 core +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define float3x3 mat3 +#define float4x4 mat4 +""" + +@embed_string rgl_finalize_vs_src: +""" +out Vertex2Pixel +{ + float2 uv; +} v2p; + +void main() +{ + int vertex_id = gl_VertexID; + float2 uv = vec2(vertex_id & 1, vertex_id >> 1); + + v2p.uv = uv; + gl_Position = vec4(uv * 2.0 - 1.0, 0.0, 1.0); +} +""" + +@embed_string rgl_finalize_fs_src: +""" +in Vertex2Pixel +{ + float2 uv; +} v2p; + +uniform sampler2D stage_t2d; + +out float4 o_final_color; + +void main() +{ + o_final_color = float4(texture(stage_t2d, v2p.uv).rgb, 1.0); +} +""" + +//////////////////////////////// +//~ dmylo: Blur Shaders +@embed_string rgl_blur_common_src: +""" +#version 330 core +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define float3x3 mat3 +#define float4x4 mat4 + +layout (std140) uniform Globals +{ + float4 rect; + float4 corner_radii_px; + + float2 viewport_size; + uint blur_count; + uint _padding; + + float4 kernel[32]; +}; +""" + +@embed_string rgl_blur_vs_src: +""" +out Vertex2Pixel +{ + float2 texcoord; + float2 sdf_sample_pos; + flat float2 rect_half_size; + float corner_radius; +} v2p; + +void main() +{ + float2 vertex_positions_scrn[4]; + vertex_positions_scrn[0] = rect.xw; + vertex_positions_scrn[1] = rect.xy; + vertex_positions_scrn[2] = rect.zw; + vertex_positions_scrn[3] = rect.zy; + + float corner_radii_px[] = float[] + ( + corner_radii_px.y, + corner_radii_px.x, + corner_radii_px.w, + corner_radii_px.z + ); + + int vertex_id = gl_VertexID; + float2 cornercoords_pct = float2( + (vertex_id >> 1) != 0 ? 1.f : 0.f, + (vertex_id & 1) != 0 ? 0.f : 1.f); + + float2 vertex_position_pct = vertex_positions_scrn[vertex_id] / viewport_size; + float2 vertex_position_scr = 2.f * vertex_position_pct - 1.f; + + float2 rect_half_size = float2((rect.z-rect.x)/2, (rect.w-rect.y)/2); + + { + gl_Position = float4(vertex_position_scr.x, -vertex_position_scr.y, 0.f, 1.f); + v2p.texcoord = vertex_position_pct; + v2p.texcoord.y = 1.0 - v2p.texcoord.y; + v2p.sdf_sample_pos = (2.f * cornercoords_pct - 1.f) * rect_half_size; + v2p.rect_half_size = rect_half_size - 2.f; + v2p.corner_radius = corner_radii_px[vertex_id]; + } +} +""" + +@embed_string rgl_blur_fs_src: +""" +in Vertex2Pixel +{ + float2 texcoord; + float2 sdf_sample_pos; + flat float2 rect_half_size; + float corner_radius; +} v2p; + + +uniform vec2 u_direction; +uniform sampler2D stage_t2d; + +out float4 o_final_color; + +float rect_sdf(float2 sample_pos, float2 rect_half_size, float r) +{ + return length(max(abs(sample_pos) - rect_half_size + r, 0.0)) - r; +} + +void main() +{ + // rjf: blend weighted texture samples into color + float3 color = kernel[0].x * texture(stage_t2d, v2p.texcoord).rgb; + + for(uint i = 1u; i < blur_count; i += 1u) + { + float weight = kernel[i].x; + float offset = kernel[i].y; + color += weight * texture(stage_t2d, v2p.texcoord - offset * u_direction).rgb; + color += weight * texture(stage_t2d, v2p.texcoord + offset * u_direction).rgb; + } + + // rjf: sample for corners + float corner_sdf_s = rect_sdf(v2p.sdf_sample_pos, v2p.rect_half_size, v2p.corner_radius); + float corner_sdf_t = 1-smoothstep(0, 2, corner_sdf_s); + + // rjf: weight output color by sdf + // this is doing alpha testing, leave blurring only where mostly opaque pixels are + if (corner_sdf_t < 0.9f) + { + discard; + } + + o_final_color = float4(color, 1.f); +} +""" + +//////////////////////////////// +//~ dmylo: Mesh Shaders + +@embed_string rgl_mesh_common_src: +""" +#version 330 core +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define float3x3 mat3 +#define float4x4 mat4 +""" + +@embed_string rgl_mesh_vs_src: +""" +uniform mat4 xform; + +layout(location=0) in float3 a_position; +layout(location=1) in float3 a_normal; +layout(location=2) in float2 a_texcoord; +layout(location=3) in float3 a_color; + +out Vertex2Pixel +{ + float2 texcoord; + float4 color; +} v2p; + +void main() +{ + gl_Position = xform * float4(a_position, 1.f); + v2p.texcoord = a_texcoord; + v2p.color = float4(a_color, 1.f); +} +""" + +@embed_string rgl_mesh_fs_src: +""" +in Vertex2Pixel +{ + float2 texcoord; + float4 color; +} v2p; + +out float4 o_final_color; + +void main() +{ + o_final_color = v2p.color; +} +"""; + +//////////////////////////////// +//~ dmylo: Geo3D Composition Shaders +@embed_string rgl_geo3dcomposite_common_src: +""" +#version 330 core +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define float3x3 mat3 +#define float4x4 mat4 +""" + +@embed_string rgl_geo3dcomposite_vs_src: +""" +out Vertex2Pixel +{ + float2 uv; +} v2p; + +void main() +{ + int vertex_id = gl_VertexID; + float2 uv = vec2(vertex_id & 1, vertex_id >> 1); + + v2p.uv = uv; + gl_Position = vec4(uv * 2.0 - 1.0, 0.0, 1.0); +} +""" + +@embed_string rgl_geo3dcomposite_fs_src: +""" +in Vertex2Pixel +{ + float2 uv; +} v2p; + +uniform sampler2D stage_t2d; + +out float4 o_final_color; + +void main() +{ + o_final_color = float4(texture(stage_t2d, v2p.uv).rgb, 1.0); +} +""" diff --git a/src/render/render_core.mdesk b/src/render/render_core.mdesk index 2aea39637..993f49986 100644 --- a/src/render/render_core.mdesk +++ b/src/render/render_core.mdesk @@ -35,7 +35,7 @@ R_ResourceKindTable: // stream resource will be often updated fully overwriting previous data // GPU can only read it // CPU can update via Map (with WRITE_DISCARD flag) + Unmap - {Stream "Stream "} + {Stream "Stream "} } @table(name, display_string) @@ -54,6 +54,13 @@ R_GeoTopologyKindTable: {TriangleStrip "Triangle Strip" } } +@table(name, display_string) +R_BufferKindTable: +{ + {Static "Static" } + {Dynamic "Dynamic"} +} + @table(name, batch, display_string) R_PassKindTable: { @@ -71,6 +78,12 @@ R_PassKindTable: COUNT, } +@enum R_Tex2DKind: +{ + @expand(R_ResourceKindTable a) `$(a.name)`, + COUNT, +} + @enum R_ResourceKind: { @expand(R_ResourceKindTable a) `$(a.name)`, @@ -89,6 +102,13 @@ R_PassKindTable: COUNT, } +@enum R_BufferKind: +{ + @expand(R_BufferKindTable a) `$(a.name)`, + COUNT, +} + + @enum R_PassKind: { @expand(R_PassKindTable a) `$(a.name)`, diff --git a/src/render/render_inc.c b/src/render/render_inc.c index f85761b57..c5c7f0449 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 95ab1a2fb..828aa7276 100644 --- a/src/render/render_inc.h +++ b/src/render/render_inc.h @@ -9,12 +9,17 @@ #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 +#else + #define R_BACKEND R_BACKEND_STUB #endif //////////////////////////////// @@ -26,9 +31,11 @@ //~ rjf: Backend Includes #if R_BACKEND == R_BACKEND_STUB -# include "stub/render_stub.h" + #include "stub/render_stub.h" #elif R_BACKEND == R_BACKEND_D3D11 -# include "d3d11/render_d3d11.h" + #include "d3d11/render_d3d11.h" +#elif R_BACKEND == R_BACKEND_OPENGL + #include "opengl/render_opengl.h" #else # error Renderer backend not specified. #endif diff --git a/src/render/stub/render_stub.c b/src/render/stub/render_stub.c index b64dfa83d..a552d0309 100644 --- a/src/render/stub/render_stub.c +++ b/src/render/stub/render_stub.c @@ -41,7 +41,7 @@ r_tex2d_release(R_Handle texture) r_hook R_ResourceKind r_kind_from_tex2d(R_Handle texture) { - return R_ResourceStatic; + return R_ResourceKind_Static; } r_hook Vec2S32 diff --git a/src/tests/test_os_core.c b/src/tests/test_os_core.c new file mode 100644 index 000000000..58e4710b8 --- /dev/null +++ b/src/tests/test_os_core.c @@ -0,0 +1,439 @@ + +// From Core +#include "lib_rdi_format/rdi_format.h" +#include "lib_rdi_format/rdi_format.c" +#include "third_party/rad_lzb_simple/rad_lzb_simple.h" +#include "third_party/rad_lzb_simple/rad_lzb_simple.c" + +#include "base/base_inc.h" +#include "os/os_inc.h" +#include "task_system/task_system.h" +#include "rdi_make/rdi_make_local.h" +#include "coff/coff.h" +#include "codeview/codeview.h" +#include "codeview/codeview_stringize.h" +#include "msf/msf.h" +#include "pdb/pdb.h" +#include "pdb/pdb_stringize.h" + +#include "base/base_inc.c" +#include "os/os_inc.c" +#include "task_system/task_system.c" +#include "rdi_make/rdi_make_local.c" +#include "coff/coff.c" +#include "codeview/codeview.c" +#include "codeview/codeview_stringize.c" +#include "msf/msf.c" +#include "pdb/pdb.c" +#include "pdb/pdb_stringize.c" + +// Tests only + +internal int total = 0; +internal int passes = 0; +internal int failed = 0; +internal int manual_checks = 0; + +/* + ASNI Color Codes + Default - \x1b[39m + Green - \x1b[32m + Red - \x1b[31m +*/ + +void +test( B32 passed, char* description ) +{ + static B32 alternate_color = 1; + char* alternating = (alternate_color ? "\x1b[38;5;180m" : "\x1b[0m" ); + alternate_color = !alternate_color; + ++total; + if (passed) + { + ++passes; + printf( "\x1b[1mTest \x1b[32mPassed\x1b[39m |\x1b[0m %s%s\x1b[0m\n", alternating, description ); + } + else + { + ++failed; + printf( "\x1b[1mTest \x1b[31mFailed\x1b[39m |\x1b[0m %s%s\x1b[0m\n", alternating, description ); + } +} + +#define MANUAL_CHECKF(fmt, ...) printf( "\x1b[1mManual Check| \x1b[0m" fmt "\n" , ##__VA_ARGS__ ); \ + ++manual_checks + +void +SECTION( char* label ) +{ + printf( "\n\x1b[36;1m[%s]\x1b[0m\n", label ); +} + +typedef struct Dummy_Payload Dummy_Payload; +struct Dummy_Payload +{ + OS_Handle mutex; + U32* last_lock; +}; + +void +dummy_routine( void* restrict payload ) +{ + Arena* g_arena = arena_alloc(); + OS_Handle process = {0}; + String8List process_command = {0}; + OS_LaunchOptions process_config = {0}; + if (OS_WINDOWS) + { + // Untested + str8_list_push( g_arena, &process_command, str8_lit("C:/Windows/cmd.exe") ); + } + else + { + str8_list_push( g_arena, &process_command, str8_lit("/usr/bin/echo") ); + str8_list_push( g_arena, &process_command, str8_lit("Make me a sandwich!") ); + } + process_config.cmd_line = process_command; + /* res = os_launch_process( &process_config, &process ); test( res, "os_launch_process" ); */ + struct Dummy_Payload* _payload = (Dummy_Payload*)payload; + printf( "2nd Thread Locking\n" ); + os_mutex_take_( _payload->mutex ); + *(_payload->last_lock) = 2; + printf( "2nd Thread Unlocked\n" ); + os_mutex_drop_( _payload->mutex ); +} + +typedef struct Share Share; +struct Share +{ + OS_Handle rwlock; + U32 rwlock_last; +}; + +void +rwlock_routine( void* restrict payload ) +{ + Share* g_share_data = payload; + // Should be locked by prexisting writer lock + os_rw_mutex_take_r( g_share_data->rwlock ); + g_share_data->rwlock_last += 1; + os_rw_mutex_drop_r( g_share_data->rwlock ); +} + +// Stub +internal void +entry_point(CmdLine *cmd_line) +{} + +/* NOTE(mallchad): These tests were built with testing the Linux layer in mind, + it was supposed to be cross-platform, and it may still work, but the API is + not friendly to error checking, so treat these files a scratchpad than rather + than gospel. +*/ +int test_main() +{ + srand( time(NULL) ); + U64 seed = rand(); + printf( "Setting seed to random number: %d\n", seed ); + + TCTX tctx_; + tctx_init_and_equip(&tctx_); + os_init(); test( 1, "os_init" ); + Arena* g_arena = arena_alloc(); + U8* g_shared_base = NULL; + U8* g_shared = NULL; + OS_Handle g_shared_shm = {0}; + String8 g_shared_name = str8_lit("rad_test_global"); + g_shared_shm = os_shared_memory_alloc( MB(100), g_shared_name ); + g_shared = os_shared_memory_view_open( g_shared_shm, rng_1u64(0, MB(100)) ); + g_shared_base = g_shared; + AssertAlways( g_shared != NULL ); + + B32 res = 0; + U64 g_buffer[4096]; + Share* g_share_data = (Share*)g_shared; g_shared += sizeof(Share); + + SECTION( "OS Memory Management" ); + // test various sizes of mmap because failure might be size dependant + void* res_reserve = NULL; + void* error_ptr = (void*)-1; + res_reserve = os_reserve(1); test( res_reserve != error_ptr, "os_reserve 1" ); + res_reserve = os_reserve(3000); test( res_reserve != error_ptr, "os_reserve 3000" ); + res_reserve = os_reserve(4096); test( res_reserve != error_ptr, "os_reserve 4096" ); + res_reserve = os_reserve(4097); test( res_reserve != error_ptr, "os_reserve 4097" ); + res_reserve = os_reserve(200400); test( res_reserve != error_ptr, "os_reserve 200400" ); + res_reserve = os_reserve(1000200); test( res_reserve != error_ptr, "os_reserve 1000200" ); + res_reserve = os_reserve(4000800); test( res_reserve != error_ptr, "os_reserve 4000800" ); + + B32 res_commit = 0; + void* commit_buffer = os_reserve(4096); + if (res_reserve != error_ptr) + { + res_commit = os_commit( commit_buffer, 4096); test( res_commit, "os_commit" ); + } + res = 0; + if (res_commit) + { + // Sorry, all 3 will crash on failure. + MemoryZero( commit_buffer, 4096 ); test( 1, "os_commit is writable" ); + if (res_commit) + { + // Not easily testable + os_decommit( commit_buffer, 4096 ); test( 1, "os_decommit" ); + os_release( commit_buffer, 4096 ); test( 1, "os_release" ); + } + } + + res_reserve = os_reserve_large(4096); test( res_reserve != error_ptr, "os_reserve_large" ); + test( os_large_pages_enabled(), "Large Pages Enabled Locally" ); + res = os_commit_large( res_reserve, 4096 ); test( res, "os_commit_large" ); + + B32 large_page_on = os_large_pages_enabled(); + res = os_set_large_pages( !large_page_on); test( res, "os_set_large_pages toggle"); + + U64 large_size = os_large_page_size(); test( large_size > 0 && large_size < GB(512), + "os_large_page_size" ); + void* res_ring = NULL; + res_ring = os_alloc_ring_buffer( 4096, (U64*)g_buffer ); test( res_ring != error_ptr, + "os_alloc_ring_buffer" ); + /* Try to write over the buffer boundary, should write to the start of buffer + if allocated correcctly */ + MemorySet( res_ring+2048, (char)seed, 4000 ); + test( MemoryCompare( res_ring, res_ring+4096, 128) == 0, + "os_alloc_ring_buffer write over boundary" ); + + // No easy way to check if it suceeded + os_free_ring_buffer( res_ring, 4096 ); test( 1, "os_free_ring_buffer" ); + MANUAL_CHECKF( "os_page_size output: %lu", os_page_size() ); + MANUAL_CHECKF( "os_large_page_size output: %lu", os_large_page_size() ); + + // Shared memory tests + String8 shm_name = str8_lit( "rad_test_shm" ); + OS_Handle shm = {0}; + OS_Handle shm_same = {0}; + void* shm_buf = NULL; + void* shm_buf2 = NULL; + shm = os_shared_memory_alloc( 4096, shm_name ); test( 1, "os_shared_memory_alloc" ); + shm_same = os_shared_memory_open( shm_name ); test( 1, "os_shared_memory_open" ); + shm_buf = os_shared_memory_view_open( shm, rng_1u64(0, 2000 ) ); + shm_buf2 = os_shared_memory_view_open( shm_same, rng_1u64(0, 2000 ) ); + // Make sure its writable + MemorySet( shm_buf, seed, 2000 ); test( shm_buf, "os_shared_memory_view_open" ); + res = MemoryCompare( shm_buf, shm_buf2, 2000 ); + test( res == 0, "os_shared_memory integrity check" ); + os_shared_memory_view_close( shm, shm_buf ); + os_shared_memory_close( shm_same ); + os_shared_memory_close( shm ); + + + SECTION( "OS Miscellaneous" ); + // Valid Hostname + String8 hostname = os_machine_name(); + B32 hostname_good = (hostname.size > 0); + U8 x_char; + for (int i=0; inext->next->string.str); + // Path query tests + String8List syspath = {0}; + os_string_list_from_system_path( g_arena, OS_SystemPath_Binary, &syspath ); + MANUAL_CHECKF( "os_string_list_from_system_path( OS_SystemPath_Binary ): %s", + syspath.first->string.str ); // Fail + os_string_list_from_system_path( g_arena, OS_SystemPath_Initial, &syspath ); + MANUAL_CHECKF( "os_string_list_from_system_path( OS_SystemPath_Initial ): %s", + syspath.first->string.str ); + os_string_list_from_system_path( g_arena, OS_SystemPath_UserProgramData, &syspath ); + MANUAL_CHECKF( "os_string_list_from_system_path( OS_SystemPath_UserProgramData ): %s", + syspath.first->string.str ); + // TODO: Not implimented on Linux + // os_string_list_from_system_path( g_arena, OS_SystemPath_ModuleLoad, &syspath ); + // MANUAL_CHECKF( "os_string_list_from_system_path( OS_SystemPath_ModuleLoad ): %s", + // syspath.first->string.str ); + test( 0, "os_string_list_from_system_path( OS_SystemPath_ModuleLoad )" ); + + // Test setting thread name + LNX_thread thread_self = pthread_self(); + String8 thread_name = str8_lit( "test_thread_name" ); + MemoryZeroArray( g_buffer ); + os_set_thread_name( thread_name ); + prctl( PR_GET_NAME , g_buffer ); + // Thread name capped to 16 on Linux + if (OS_WINDOWS) + { res = MemoryCompare( thread_name.str, g_buffer, thread_name.size ); } + else if (OS_LINUX) + { res = MemoryCompare( thread_name.str, g_buffer, Min(thread_name.size, 15) ); } + + test( res == 0, "os_set_thread_name" ); + MANUAL_CHECKF( "Current thread name: %s", g_buffer ); + + SECTION( "OS File Handling" ); + // Do an open, read/write, close test + OS_Handle file = {0}; + String8 qbf = str8_lit( "The quick brown fox jumped over the lazy dog" ); + String8 filename = str8_lit("os_file_test.txt"); + MemoryZeroArray( g_buffer ); + file = os_file_open( OS_AccessFlag_Read | OS_AccessFlag_Write, filename ); + test( *file.u64 != 0, "os_file_open" ); + os_file_write( file, rng_1u64(0, qbf.size), qbf.str ); + os_file_read( file, rng_1u64(0, qbf.size), g_buffer ); + res = 0 == MemoryCompare( qbf.str, g_buffer, qbf.size ); + test( res, "os_file_read"); test( res, "os_file_write"); + os_file_close( file ); + res = os_delete_file_at_path( filename ); test( res, "os_delete_file_at_path" ); + + void* filebuf = NULL; + OS_Handle filemap = {0}; + filename = str8_lit("os_file_map_test.txt"); + file = os_file_open( OS_AccessFlag_Read | OS_AccessFlag_Write, filename ); + filemap = os_file_map_open( 0x0, file ); + test( *filemap.u64 != 0, "os_file_map_open" ); + + filebuf = os_file_map_view_open( filemap, 0x0, rng_1u64(0, 1024) ); + test( filebuf, "os_file_map_view_open" ); + os_file_map_view_close( filemap, filebuf ); test( 1, "os_file_map_view_close" ); + os_file_map_close( filemap ); test( 1, "os_file_map_close" ); + + // File Iteration Tests + OS_FileIter* file_iter = NULL; + OS_FileInfo iter_info = {0}; + file_iter = os_file_iter_begin( g_arena, str8_lit("."), 0x0 ); + res = os_file_iter_next( g_arena, file_iter, &iter_info ); + test( res, "os_file_iter_next" ); test( res, "os_file_iter_begin iteration" ); + MANUAL_CHECKF( "os_file_iter_next filename: %s", iter_info.name ); + os_file_iter_end( file_iter ); + + // Make Directory Tests + String8 mkdir_file = str8_lit( "os_mkdir_test" ); + res = os_make_directory( mkdir_file ); test( res, "os_make_directory" ); + res = os_delete_file_at_path( mkdir_file ); test( res, "os_delete_file_at_path delete directory" ); + + MANUAL_CHECKF( "os_now_unix (Wall Clock): %lu", os_now_unix() ); + DateTime time_universal = os_now_universal_time(); + DateTime time_to_local = os_local_time_from_universal_time( &time_universal ); + DateTime time_to_universal = os_universal_time_from_local_time( &time_to_local ); + MANUAL_CHECKF( "os_now_universal_time: year: %d \n" + "month: %d \n" + "week: %d \n" + "day: %d \n" + "time: %d:%d:%d:%d:%d \n", + time_universal.year, + time_universal.mon, + time_universal.wday, + time_universal.day, + time_universal.hour, + time_universal.min, + time_universal.sec, + time_universal.msec, + time_universal.micro_sec ); + MANUAL_CHECKF( "os_universal_time_from_local_time: year: %d \n" + "month: %d \n" + "day of week: %d \n" + "day: %d \n" + "time: %d:%d:%d:%d:%d", + time_to_universal.year, + time_to_universal.mon, + time_to_universal.wday, + time_to_universal.day, + time_to_universal.hour, + time_to_universal.min, + time_to_universal.sec, + time_to_universal.msec, + time_to_universal.micro_sec ); + MANUAL_CHECKF( "os_local_time_from_universal_time: year: %d \n" + "month: %d \n" + "day of week: %d \n" + "day: %d \n" + "time: %d:%d:%d:%d:%d", + time_to_local.year, + time_to_local.mon, + time_to_local.wday, + time_to_local.day, + time_to_local.hour, + time_to_local.min, + time_to_local.sec, + time_to_local.msec, + time_to_local.micro_sec ); + MANUAL_CHECKF( "os_now_microseconds (CPU clock): %lu", os_now_microseconds() ); + + U64 sleep_start = os_now_microseconds(); + os_sleep_milliseconds( 1 ); + U64 sleep_elapsed_us = (os_now_microseconds() - sleep_start); + test( sleep_elapsed_us > 999, "os_sleep_milliseconds(1)" ); + MANUAL_CHECKF( "Sleep Time: %lu us", sleep_elapsed_us ); + + // Launch Process + OS_Handle process = {0}; + String8List process_command = {0}; + OS_LaunchOptions process_config = {0}; + if (OS_WINDOWS) + { + // Untested + str8_list_push( g_arena, &process_command, str8_lit("C:/Windows/cmd.exe") ); + } + else + { + str8_list_push( g_arena, &process_command, str8_lit("/usr/bin/echo") ); + str8_list_push( g_arena, &process_command, str8_lit("Make me a sandwich!") ); + } + process_config.cmd_line = process_command; + /* res = os_launch_process( &process_config, &process ); test( res, "os_launch_process" ); */ + + SECTION( "Thread Syncronization" ); + OS_Handle mut = {0}; + OS_Handle thread2 = {0}; + struct Dummy_Payload dummy_payload = {0}; + U32* last_lock = (U32*)g_shared; g_shared += sizeof(U32); + *last_lock = 1; + + // Basic Mutex Test + mut = os_mutex_alloc(); + dummy_payload.mutex = mut; + dummy_payload.last_lock = last_lock; + os_mutex_take_( mut ); + thread2 = os_launch_thread( dummy_routine, &dummy_payload, NULL ); + *last_lock = 1; + os_sleep_milliseconds( 100 ); + *last_lock = 1; + os_mutex_drop_( mut ); + os_sleep_milliseconds( 100 ); test( *last_lock == 2, "os_mutex thread ordering check" ); + os_mutex_release( mut ); + /* os_release_thread_handle( thread2 ); */ + + // Basic rwlock Test + // NOTE: Bogus test, don't know how to check sanely + g_share_data->rwlock_last = 0; + g_share_data->rwlock = os_rw_mutex_alloc(); + os_rw_mutex_take_w( g_share_data->rwlock ); + OS_Handle rwlock_thread_1 = os_launch_thread( rwlock_routine, g_share_data, 0x0 ); + OS_Handle rwlock_thread_2 = os_launch_thread( rwlock_routine, g_share_data, 0x0 ); + os_sleep_milliseconds( 50 ); + os_rw_mutex_drop_w( g_share_data->rwlock ); + os_sleep_milliseconds( 50 ); + MANUAL_CHECKF( "rwlock value: %u", g_share_data->rwlock_last ); + os_release_thread_handle( rwlock_thread_1 ); + os_release_thread_handle( rwlock_thread_2 ); + + // Finalization + SECTION( "Summary" ); + total = passes + manual_checks; + printf( "\x1b[32mPassed\x1b[39m: %d \n", passes ); + printf( "\x1b[31mFailed\x1b[39m: %d \n", failed ); + printf( "\x1b[1mManual Checks Run\x1b[39m: %d \n", manual_checks ); + printf( "\x1b[1mTotal Tests Run: %d \n", total ); + + os_exit_process( 0 ); test( 0, "os_exit_process" ); + return 1; +} diff --git a/src/text_cache/text_cache.c b/src/text_cache/text_cache.c index 119243540..093e7a7bb 100644 --- a/src/text_cache/text_cache.c +++ b/src/text_cache/text_cache.c @@ -1929,7 +1929,7 @@ txt_parse_thread__entry_point(void *p) { if(u128_match(n->hash, hash) && n->lang == lang) { - got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); + got_task = !ins_atomic_u32_eval_cond_assign((U32*)&n->is_working, 1, 0); break; } } diff --git a/src/texture_cache/texture_cache.c b/src/texture_cache/texture_cache.c index 744e21758..e92878f8c 100644 --- a/src/texture_cache/texture_cache.c +++ b/src/texture_cache/texture_cache.c @@ -306,7 +306,7 @@ tex_xfer_thread__entry_point(void *p) { if(u128_match(n->hash, hash) && MemoryMatchStruct(&top, &n->topology)) { - got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); + got_task = !ins_atomic_u32_eval_cond_assign((U32*)&n->is_working, 1, 0); break; } } diff --git a/src/third_party/rad_lzb_simple/rad_lzb_simple.c b/src/third_party/rad_lzb_simple/rad_lzb_simple.c index 3e6e35168..bc5a15d83 100644 --- a/src/third_party/rad_lzb_simple/rad_lzb_simple.c +++ b/src/third_party/rad_lzb_simple/rad_lzb_simple.c @@ -840,7 +840,7 @@ static SINTa rr_lzb_simple_encode_fast_sub(rr_lzb_simple_context * fh, } //------------------------------- - found_match: + found_match: ; // found something @@ -957,7 +957,7 @@ static SINTa rr_lzb_simple_encode_fast_sub(rr_lzb_simple_context * fh, if ( 0 ) { - lazy_found_match: + lazy_found_match: ; lazyrp += 4; @@ -1100,7 +1100,7 @@ static SINTa rr_lzb_simple_encode_fast_sub(rr_lzb_simple_context * fh, #endif } - done: + done: ; int cur_lrl = rrPtrDiff32(rpEnd - literals_start); #if LZB_END_WITH_LITERALS @@ -1278,7 +1278,7 @@ static SINTa rr_lzb_simple_encode_veryfast_sub(rr_lzb_simple_context * fh, } //------------------------------- - found_match: + found_match: ; // found something @@ -1355,7 +1355,7 @@ static SINTa rr_lzb_simple_encode_veryfast_sub(rr_lzb_simple_context * fh, goto done; } - done: + done: ; int cur_lrl = rrPtrDiff32(rpEnd - literals_start); #if LZB_END_WITH_LITERALS diff --git a/src/third_party/rad_lzb_simple/rad_lzb_simple.h b/src/third_party/rad_lzb_simple/rad_lzb_simple.h index c1e5e96e5..2d71e6d8a 100644 --- a/src/third_party/rad_lzb_simple/rad_lzb_simple.h +++ b/src/third_party/rad_lzb_simple/rad_lzb_simple.h @@ -84,6 +84,12 @@ typedef S32 RAD_S32; # define Expect(expr, val) (expr) #endif +#if defined(__clang__) + #define RR_TRAP() __builtin_trap() +#elif defined(_MSC_VER) + #define RR_TRAP() __debug_break() +#endif + #define RAD_LIKELY(expr) Expect(expr,1) #define RAD_UNLIKELY(expr) Expect(expr,0) @@ -91,8 +97,8 @@ typedef S32 RAD_S32; #define RAD_PTRBYTES 8 #define RR_MIN(a,b) ( (a) < (b) ? (a) : (b) ) #define RR_MAX(a,b) ( (a) > (b) ? (a) : (b) ) -#define RR_ASSERT_ALWAYS(c) do{if(!(c)) {__debugbreak();}}while(0) -#define RR_ASSERT(c) RR_ASSERT_ALWAYS(c) +#define RR_ASSERT_ALWAYS(c) do{if(!(c)) { RR_TRAP(); }}while(0) +#define RR_ASSERT(c) #define RR_PUT16_LE(ptr,val) *((U16 *)(ptr)) = (U16)(val) #define RR_GET16_LE_UNALIGNED(ptr) *((const U16 *)(ptr)) diff --git a/src/third_party/stb/stb_image_write.h b/src/third_party/stb/stb_image_write.h new file mode 100644 index 000000000..e4b32ed1b --- /dev/null +++ b/src/third_party/stb/stb_image_write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/