From 6f1e5554becf8bd6f3ed887029beacabcbc93d72 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sun, 23 Jun 2024 00:49:19 +0100 Subject: [PATCH 01/30] Fixed: Few old ends with out of date parts of the Linux code --- .../core/linux/metagen_os_core_linux.c | 38 ++++++++++--------- .../core/linux/metagen_os_core_linux.h | 7 +++- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c index f9c2af025..09a0a63f5 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -801,7 +801,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); @@ -939,7 +939,8 @@ os_machine_name(void){ for (S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1){ - scratch.restore(); + /* Why? Isn't this redundant? */ + /* scratch.restore(); */ buffer = push_array_no_zero(scratch.arena, U8, cap); size = gethostname((char*)buffer, cap); if (size < cap){ @@ -1052,7 +1053,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); } @@ -1167,7 +1168,7 @@ 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); + String8 name_copy = push_str8_copy(scratch.arena, path); if (remove((char*)name_copy.str) != -1){ result = true; } @@ -1406,7 +1407,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 +1447,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); } @@ -1468,8 +1469,11 @@ os_mutex_drop_(OS_Handle mutex){ internal OS_Handle os_rw_mutex_alloc(void) { - OS_Handle result = {0}; - NotImplemented; + LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Mutex); + // Use default pthread attributes for now + pthread_mutex_init(&entity->mutex, NULL); + + OS_Handle result = { IntFromPtr(entity) }; return result; } @@ -1527,7 +1531,7 @@ 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); } @@ -1535,8 +1539,8 @@ os_condition_variable_release(OS_Handle cv){ 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); + LNX_Entity *entity_cond = (LNX_Entity*)PtrFromInt(cv.u64[0]); + LNX_Entity *entity_mutex = (LNX_Entity*)PtrFromInt(mutex.u64[0]); // TODO(allen): implement the time control pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex); return(result); @@ -1558,13 +1562,13 @@ os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) 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); + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); DontCompile; } @@ -1629,7 +1633,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 +1643,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); } 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..ffcf57342 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 @@ -26,6 +26,7 @@ //////////////////////////////// //~ NOTE(allen): File Iterator +typedef struct LNX_FileIter LNX_FileIter; struct LNX_FileIter{ int fd; DIR *dir; @@ -34,7 +35,7 @@ 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, @@ -42,6 +43,7 @@ enum LNX_EntityKind{ LNX_EntityKind_ConditionVariable, }; +typedef struct LNX_Entity LNX_Entity; struct LNX_Entity{ LNX_Entity *next; LNX_EntityKind kind; @@ -60,6 +62,7 @@ struct LNX_Entity{ //////////////////////////////// //~ NOTE(allen): Safe Call Chain +typedef struct LNX_SafeCallChain LNX_SafeCallChain; struct LNX_SafeCallChain{ LNX_SafeCallChain *next; OS_ThreadFunctionType *fail_handler; @@ -83,6 +86,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 From a90684fc42f7e84fb4be514f3dce6e6e9f6309dd Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sun, 23 Jun 2024 00:50:31 +0100 Subject: [PATCH 02/30] Fixed: [linux] Some compile errors with hostname and attempted rewrite for simplicity --- src/os/core/linux/os_core_linux.c | 50 +++++++++++-------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 8a6db8434..07e4724d0 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -923,42 +923,28 @@ os_free_ring_buffer(void *ring_buffer, U64 actual_size) internal String8 os_machine_name(void){ - local_persist B32 first = true; + local_persist B32 invalid = true; 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){ + const U32 hostname_limit = 255; // Max reasonable size for hostname + + // NOTE(mallchad): There was a lot of complicated code here but I could not + // figure out what the purpose was for such a simple syscall + if (invalid){ + pthread_mutex_lock(&lnx_mutex); + 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; + U8 *tmp = push_array_no_zero(scratch.arena, U8, hostname_limit); + S32 error = gethostname((char*)tmp, hostname_limit); + + // No Errors + if (error == 0){ + String8 tmp_string = str8_cstring(tmp); + name = push_str8_copy(lnx_perm_arena, tmp_string); + invalid = false; } - scratch_end(scratch); + pthread_mutex_unlock(&lnx_mutex); } - pthread_mutex_unlock(&lnx_mutex); return(name); } @@ -1052,7 +1038,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); } From 2b82eae407d01dafea7e8ee39f55f94162887ed0 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sun, 30 Jun 2024 22:40:05 +0100 Subject: [PATCH 03/30] [metagen_linux] Added: Various fixes and missing implimentations for the linux layer [metagen_w32] Fixed: Don't modify file creation time This isn't really a big deal but I feel like ist not the intention of os_file_set_times and its also just bad form in general to modify access times beacuse it obliterates the very limited context the OS will save about our files. Feel free to revert. [metagen_linux] Added: Auto allocation of 1GB huge_pages on os_reserve_large I don't know if we actually want this or not but I figured I would at least throw it out there because its not unusual to run up multi-gigabyte debug data on very complex projects so its absolutely a thing that would be worth looking into for performance / OS-level fragmentation and also crash resistance [metagen_strings] Added: Function to find first-occurance of a substring 'str8_match_substr' This returns a range of where the match was found. [metagen_linux] Added: Dependencies 'libpthread libuuid' [metagen_linux] Fixed: Leftover bool usage was changed to B32 and int usage NOTE: Nothing has been tested at this point, too annoying, too many missing functions, hard to test when it doesn't build --- .../metagen_base/metagen_base_string.c | 38 +++ .../metagen_base/metagen_base_string.h | 3 + .../core/linux/metagen_os_core_linux.c | 236 ++++++++++++++---- .../core/linux/metagen_os_core_linux.h | 6 + .../core/win32/metagen_os_core_win32.c | 2 +- 5 files changed, 239 insertions(+), 46 deletions(-) diff --git a/src/metagen/metagen_base/metagen_base_string.c b/src/metagen/metagen_base/metagen_base_string.c index ac9745998..24b822b22 100644 --- a/src/metagen/metagen_base/metagen_base_string.c +++ b/src/metagen/metagen_base/metagen_base_string.c @@ -290,6 +290,44 @@ 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; + + for (U8 x_char, U8 x_expr_char U64 i_target=0; i_charnext; if (node == 0){ if (p < list.total_size){ - success = false; + success = 0; } break; } @@ -809,6 +813,17 @@ lnx_safe_call_sig_handler(int _){ abort(); } +// Helper function to return a timespec using a adjtime stable high precision clock +internal timespec +lnx_now_precision_timespec() +{ + timespec result; + clock_gettime(CLOCK_MONOTONIC_RAW, &result); + + return result; +} + + //////////////////////////////// //~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) @@ -844,6 +859,9 @@ os_init(int argc, char **argv) // NOTE(rjf): Setup command line args lnx_cmd_line_args = os_string_list_from_argcv(lnx_perm_arena, argc, argv); + + // Load Shared Objects + os_library_open("pthread.so"); } //////////////////////////////// @@ -857,21 +875,31 @@ os_reserve(U64 size){ internal B32 os_commit(void *ptr, U64 size){ - mprotect(ptr, size, PROT_READ|PROT_WRITE); + U32 error = mprotect(ptr, size, PROT_READ|PROT_WRITE); // TODO(allen): can we test this? - return(true); + return (error == 0); } internal void* os_reserve_large(U64 size){ - NotImplemented; - return 0; + F32 huge_threshold = 0.5f; + F32 huge_use_1GB = size > (1e9f * huge_threshold); + U32 page_size = huge_use_1GB ? MAP_HUGE_1GB : MAP_HUGE_2MB; + void *result; + if (lnx_huge_page_enabled) + { + result = mmap(0, size, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETBL | page_size, + -1, 0); + } else { result os_commit(size); } + + return result; } internal B32 os_commit_large(void *ptr, U64 size){ - NotImplemented; - return 0; + U32 error = mprotect(ptr, size, PROT_READ|PROT_WRITE); + return (error == 0); } internal void @@ -885,37 +913,77 @@ os_release(void *ptr, U64 size){ munmap(ptr, size); } +// 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 void 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... + OS_Handle meminfo_handle = os_file_open(OS_AccessFlag_Read, str8_cstring("/proc/meminfo/")); + String8 meminfo = os_file_map_open(); + Rng1U64 match = str8_match_substr(str8_cstring("Huge")); 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 + U8* base = "metagen_ring_xxxx"; + U8* base_length = cstring8_length(base); + U8* filename_anonymous = push_array(scratch.arena, U8, 50); + MemoryCopy(filename_anonymous, filename_base, base_length); + base16_from_data(filename_anonymous + base_length -4, + 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(filename_anonymous, flag_huge); + result = mmap(NULL, 2* size, flag_prot, flag_huge2 | MAP_ANONYMOUS, -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(2* ring_buffer, actual_size); } //////////////////////////////// @@ -923,17 +991,17 @@ 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; @@ -944,7 +1012,7 @@ os_machine_name(void){ 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; } } @@ -1025,17 +1093,17 @@ 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; @@ -1045,7 +1113,7 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o 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; } } @@ -1117,21 +1185,52 @@ internal OS_Handle os_file_open(OS_AccessFlags flags, String8 path) { OS_Handle file = {0}; - NotImplemented; + // read/write flags are mutually exclusie on Linux `open()` + if (flags & OS_AccessFlag_Write & OS_AccessFlag_Read) { access_flags |= O_RDWR | O_CREAT; } + else if (flags & OS_AccessFlag_Read) { access_flags |= O_RDONLY; } + else if (flags & OS_AccessFlag_Write) { access_flags |= 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) {} + + /* NOTE: 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. */ + String8 file_dir = str8_chop_last_slash(path); + dir_fd = open(file_dir, O_PATH); + S32 fd = openat(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; + lseek(fd, rng.min, SEEK_SET); + U64 read_bytes = read(fd, out_data, ClampTop(filesize, rng.max)); + + return read_bytes; } internal void @@ -1143,7 +1242,13 @@ os_file_write(OS_Handle file, Rng1U64 rng, void *data) internal B32 os_file_set_times(OS_Handle file, DateTime time) { - NotImplemented; + S32 fd = *file.u64; + struct timespec access_and_modification[2]; + access_and_modification[1] = access_and_modification[0]; + lnx_tm_from_date_time(&time, &access_and_modification); + B32 error = futimes( fd, &access_and_modification); + + return (error != -1) } internal FileProperties @@ -1167,10 +1272,10 @@ internal B32 os_delete_file_at_path(String8 path) { Temp scratch = scratch_begin(0, 0); - B32 result = false; + 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); @@ -1204,8 +1309,14 @@ os_file_path_exists(String8 path) internal OS_Handle os_file_map_open(OS_AccessFlags flags, OS_Handle file) { - NotImplemented; - OS_Handle handle = {0}; + S32 fd = *file.u64; + struct stat file_info; + fstat(fd, &file_info) + U64 filesize = file_info->st_size; + void *address = mmap(0, filsize, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + OS_Handle result = {0}; + *handle.u64 = IntFromPtr(address); + return handle; } @@ -1256,10 +1367,10 @@ internal B32 os_make_directory(String8 path) { Temp scratch = scratch_begin(0, 0); - B32 result = false; + B32 result = 0; String8 name_copy = push_str8_copy(scratch.arena, name); if (mkdir((char*)name_copy.str, 0777) != -1){ - result = true; + result = 1; } scratch_end(scratch); return(result); @@ -1359,7 +1470,10 @@ 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); } @@ -1376,7 +1490,7 @@ internal B32 os_launch_process(OS_LaunchOptions *options){ // TODO(allen): I want to redo this API before I bother implementing it here NotImplemented; - return(false); + return(0); } //////////////////////////////// @@ -1538,7 +1652,7 @@ os_condition_variable_release(OS_Handle cv){ internal B32 os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ - B32 result = false; + B32 result = 0; LNX_Entity *entity_cond = (LNX_Entity*)PtrFromInt(cv.u64[0]); LNX_Entity *entity_mutex = (LNX_Entity*)PtrFromInt(mutex.u64[0]); // TODO(allen): implement the time control @@ -1578,41 +1692,73 @@ internal OS_Handle os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) { OS_Handle result = {0}; - NotImplemented; + + // create | error if pre-existing , rw-rw---- + sem_t* semaphore = sem_open(name, 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.64[0] = IntFromPtr(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; + // Same thing as os_semaphore_alloc but reuse the existing shared mem object + S64 fd = shm_open(name, O_RDWR ); + LNX_Entity* handle = lnx_alloc_entity(LNX_EntityKind_Semaphore); + B32 init_result = sem_init(semaphore, 1, max_count); + handle->semaphore = semaphore; + result.64[0] = IntFromPtr(handle); + + if (init_result == -1) (void)0; /* open failed, what now */ return result; } internal void os_semaphore_close(OS_Handle semaphore) { - NotImplemented; + LNX_Entity* entity = (U64*)PtrFromInt(*semaphore.u64); + sem_t* 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; + struct timespec wait_until = lnx_now_precision_timespec(); + wait_until->tv_nsec += endt_us; + + LNX_Entity* entity = (U64*)PtrFromInt(*semaphore.u64); + struct semaphore = entity->semaphore; + // We have to impliment max_count ourselves + S32 current_value = 0; + sem_getvalue(semaphore->handle, ¤t_value) + if (semaphore->max_value > current_value) + { + sem_timedwait(handle, wait_until); + } + return (wait_result != -1); } internal void os_semaphore_drop(OS_Handle semaphore) { - NotImplemented; + LNX_Entity* entity (U64*)PtrFromInt(*semaphore.u64); + sem_t* _semaphore = entity->semaphore->handle; + sem_post(_semaphore); } //////////////////////////////// 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 ffcf57342..ae91e6fba 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 @@ -22,6 +22,7 @@ #include #include #include +#include //////////////////////////////// //~ NOTE(allen): File Iterator @@ -41,6 +42,7 @@ enum LNX_EntityKind{ LNX_EntityKind_Thread, LNX_EntityKind_Mutex, LNX_EntityKind_ConditionVariable, + LNX_EntityKind_Semaphore }; typedef struct LNX_Entity LNX_Entity; @@ -54,6 +56,10 @@ struct LNX_Entity{ void *ptr; pthread_t handle; } thread; + struct{ + sem_t* handle; + U32 max_value; + } semaphore; pthread_mutex_t mutex; pthread_cond_t cond; }; 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; } From af52d980f97ea8722ca18c40287dcaa0e9e7726d Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sat, 6 Jul 2024 18:11:06 +0100 Subject: [PATCH 04/30] [metagen_linux] Added: Various fixes and missing implimentations for the linux layer Documentation: Changed some links to be on their own comment line. This makes it easier to copy and discern there's a usable link present Added: Implementation for os_id_from_file from TODO TODO(nick): querry struct stat with fstat(2) and use st_dev and st_ino as ids Added: Implementation for os_full_path_from_path from TODO TODO: realpath can be used to resolve full path Fixed: Some erroneous dot (.) and arrow (->) notation Added: Some helper typedefs for long and hard to remember Linux-specific types --- src/metagen/metagen_base/metagen_base_arena.c | 2 +- .../metagen_base/metagen_base_string.c | 17 +- .../core/linux/metagen_os_core_linux.c | 456 +++++++++++++----- .../core/linux/metagen_os_core_linux.h | 49 +- 4 files changed, 392 insertions(+), 132 deletions(-) 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 24b822b22..f54e277cb 100644 --- a/src/metagen/metagen_base/metagen_base_string.c +++ b/src/metagen/metagen_base/metagen_base_string.c @@ -298,31 +298,34 @@ str8_match_substr(String8 target, String8 expression, StringMatchFlags flags) result.max = 0; B32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive); B32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive); - U8* first_char = target.str[0]; + U8 first_char = target.str[0]; // Can't match an expression larger than the target (obviously) if (expression.size > target.size) return result; - for (U8 x_char, U8 x_expr_char U64 i_target=0; i_char - + /* TODO(mallchad): want to add asserts for 'kind' of LNX_Entity */ //////////////////////////////// //~ rjf: Globals @@ -17,6 +16,19 @@ global B32 lnx_huge_page_enabled = 1; 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}; + +////////////////////////////////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 @@ -81,6 +93,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}; @@ -90,6 +112,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); @@ -101,6 +159,59 @@ lnx_file_properties_from_stat(FileProperties *out, struct stat *in){ } } +internal U32 +lnx_mmap_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; } + 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(""); @@ -814,15 +925,22 @@ lnx_safe_call_sig_handler(int _){ } // Helper function to return a timespec using a adjtime stable high precision clock -internal timespec +internal LNX_timespec lnx_now_precision_timespec() { - timespec result; + 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) @@ -854,14 +972,44 @@ 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]; + U8 _initial_size = readlink("/proc/self/exe", (char*)_initial_path, 1000); + String8 _initial_tmp = str8_array_fixed(_initial_path); + str8_chop_last_slash(_initial_tmp); + + if (_initial_size > 0) + { lnx_initial_path = push_str8_copy(lnx_perm_arena, _initial_tmp); } + // else - It failed, its a relative path now, have fun. + // NOTE(rjf): Setup command line args lnx_cmd_line_args = os_string_list_from_argcv(lnx_perm_arena, argc, argv); // Load Shared Objects - os_library_open("pthread.so"); + os_library_open( str8_lit("pthread.so") ); + + // 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 0); } /* NOTE: The size seems to be consistent across Linux systems, it's configurable @@ -941,7 +1095,7 @@ os_large_pages_enabled(void) internal U64 os_large_page_size(void) { - U64 size = (lnx_huge_page_use_1GB ? GB(1) ? MB(2)); + U64 size = (lnx_huge_page_use_1GB ? GB(1) : MB(2)); return size; } @@ -952,24 +1106,22 @@ os_alloc_ring_buffer(U64 size, U64 *actual_size_out) void* result = NULL; // Make fle descriptor - U8* base = "metagen_ring_xxxx"; - U8* base_length = cstring8_length(base); - U8* filename_anonymous = push_array(scratch.arena, U8, 50); - MemoryCopy(filename_anonymous, filename_base, base_length); - base16_from_data(filename_anonymous + base_length -4, - lnx_ring_buffers_created, + 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); + 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_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(filename_anonymous, flag_huge); + S32 fd = memfd_create((const char*)filename_anonymous.str, flag_huge1); result = mmap(NULL, 2* size, flag_prot, flag_huge2 | MAP_ANONYMOUS, -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); @@ -983,7 +1135,7 @@ os_alloc_ring_buffer(U64 size, U64 *actual_size_out) internal void os_free_ring_buffer(void *ring_buffer, U64 actual_size) { - munmap(2* ring_buffer, actual_size); + munmap(ring_buffer, 2* actual_size); } //////////////////////////////// @@ -1007,8 +1159,6 @@ os_machine_name(void){ for (S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1){ - /* Why? Isn't this redundant? */ - /* scratch.restore(); */ buffer = push_array_no_zero(scratch.arena, U8, cap); size = gethostname((char*)buffer, cap); if (size < cap){ @@ -1043,6 +1193,7 @@ os_allocation_granularity(void) { // On linux there is no equivalent of "dwAllocationGranularity" os_page_size(); + return os_page_size(); } internal U64 @@ -1081,9 +1232,7 @@ os_get_tid(void){ internal String8List os_get_environment(void) { - NotImplemented; - String8List result = {0}; - return result; + return lnx_environment; } internal U64 @@ -1109,7 +1258,6 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o 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){ @@ -1185,23 +1333,21 @@ internal OS_Handle os_file_open(OS_AccessFlags flags, String8 path) { OS_Handle file = {0}; - // read/write flags are mutually exclusie on Linux `open()` - if (flags & OS_AccessFlag_Write & OS_AccessFlag_Read) { access_flags |= O_RDWR | O_CREAT; } - else if (flags & OS_AccessFlag_Read) { access_flags |= O_RDONLY; } - else if (flags & OS_AccessFlag_Write) { access_flags |= 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) {} + U32 access_flags = lnx_open_from_os_flags(flags); - /* NOTE: 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. */ - String8 file_dir = str8_chop_last_slash(path); - dir_fd = open(file_dir, O_PATH); - S32 fd = openat(path.str, access_flags); - close(dirfd); + /* 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) @@ -1219,18 +1365,24 @@ os_file_close(OS_Handle file) } // Aparently there is a race condition in relation to `stat` and `lstat` so -// using fstat instead https://cwe.mitre.org/data/definitions/367.html +// using fstat instead +// https://cwe.mitre.org/data/definitions/367.html internal U64 os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) { S32 fd = *file.u64; struct stat file_info; - fstat(fd, &file_info) - U64 filesize = file_info->st_size; - lseek(fd, rng.min, SEEK_SET); - U64 read_bytes = read(fd, out_data, ClampTop(filesize, rng.max)); + fstat(fd, &file_info); + U64 filesize = file_info.st_size; - return read_bytes; + // 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 @@ -1243,29 +1395,45 @@ internal B32 os_file_set_times(OS_Handle file, DateTime time) { S32 fd = *file.u64; - struct timespec access_and_modification[2]; + 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]; - lnx_tm_from_date_time(&time, &access_and_modification); - B32 error = futimes( fd, &access_and_modification); + B32 error = futimes(fd, access_and_modification); - return (error != -1) + 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 @@ -1284,24 +1452,47 @@ 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 @@ -1309,15 +1500,16 @@ os_file_path_exists(String8 path) internal OS_Handle os_file_map_open(OS_AccessFlags flags, OS_Handle file) { + // TODO: Implement access flags S32 fd = *file.u64; struct stat file_info; - fstat(fd, &file_info) - U64 filesize = file_info->st_size; - void *address = mmap(0, filsize, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + fstat(fd, &file_info); + U64 filesize = file_info.st_size; + void *address = mmap(0, filesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); OS_Handle result = {0}; - *handle.u64 = IntFromPtr(address); + *result.u64 = IntFromPtr(address); - return handle; + return result; } internal void @@ -1366,13 +1558,10 @@ os_file_iter_end(OS_FileIter *iter) internal B32 os_make_directory(String8 path) { - Temp scratch = scratch_begin(0, 0); B32 result = 0; - String8 name_copy = push_str8_copy(scratch.arena, name); - if (mkdir((char*)name_copy.str, 0777) != -1){ + if (mkdir((char*)path.str, 0777) != -1){ result = 1; } - scratch_end(scratch); return(result); } @@ -1472,7 +1661,8 @@ os_now_microseconds(void){ struct timespec 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 + // 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); @@ -1487,7 +1677,8 @@ os_sleep_milliseconds(U32 msec){ //~ rjf: @os_hooks Child Processes (Implemented Per-OS) internal B32 -os_launch_process(OS_LaunchOptions *options){ +os_launch_process(OS_LaunchOptions *options, OS_Handle *handle_out) +{ // TODO(allen): I want to redo this API before I bother implementing it here NotImplemented; return(0); @@ -1583,42 +1774,60 @@ os_mutex_drop_(OS_Handle mutex){ internal OS_Handle os_rw_mutex_alloc(void) { - LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Mutex); - // Use default pthread attributes for now - pthread_mutex_init(&entity->mutex, NULL); - - OS_Handle result = { IntFromPtr(entity) }; + OS_Handle result = {0}; + 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 @@ -1632,6 +1841,8 @@ os_condition_variable_alloc(void){ 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_destroy(&attr); if (pthread_result == -1){ lnx_free_entity(entity); @@ -1653,10 +1864,13 @@ os_condition_variable_release(OS_Handle cv){ internal B32 os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ B32 result = 0; - LNX_Entity *entity_cond = (LNX_Entity*)PtrFromInt(cv.u64[0]); - LNX_Entity *entity_mutex = (LNX_Entity*)PtrFromInt(mutex.u64[0]); - // TODO(allen): implement the time control - pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex); + LNX_Entity *entity_cond = lnx_entity_from_handle(cv); + LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex); + LNX_timespec timeout_stamp = lnx_now_system_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); } @@ -1683,7 +1897,7 @@ os_condition_variable_signal_(OS_Handle cv){ internal void os_condition_variable_broadcast_(OS_Handle cv){ LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); - DontCompile; + NotImplemented; } //- rjf: cross-process semaphores @@ -1693,14 +1907,15 @@ os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) { OS_Handle result = {0}; + // Create the named semaphore // create | error if pre-existing , rw-rw---- - sem_t* semaphore = sem_open(name, O_CREAT | O_EXCL, 660, initial_count); + 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.64[0] = IntFromPtr(entity); + entity->semaphore.handle = semaphore; + entity->semaphore.max_value = max_count; + result = lnx_handle_from_entity(entity); } return result; } @@ -1715,40 +1930,39 @@ internal OS_Handle os_semaphore_open(String8 name) { OS_Handle result = {0}; - // Same thing as os_semaphore_alloc but reuse the existing shared mem object - S64 fd = shm_open(name, O_RDWR ); LNX_Entity* handle = lnx_alloc_entity(LNX_EntityKind_Semaphore); - B32 init_result = sem_init(semaphore, 1, max_count); - handle->semaphore = semaphore; - result.64[0] = IntFromPtr(handle); + LNX_semaphore* semaphore; + semaphore = sem_open((char*)name.str, 0x0); + handle->semaphore.handle = semaphore; + Assert("Failed to open POSIX semaphore." || semaphore != SEM_FAILED); - if (init_result == -1) (void)0; /* open failed, what now */ + result = lnx_handle_from_entity(handle); return result; } internal void os_semaphore_close(OS_Handle semaphore) { - LNX_Entity* entity = (U64*)PtrFromInt(*semaphore.u64); - sem_t* semaphore = entity->semaphore->handle; - sem_close(semaphore); + 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, U64 endt_us) { U32 wait_result = 0; - struct timespec wait_until = lnx_now_precision_timespec(); - wait_until->tv_nsec += endt_us; + LNX_timespec wait_until = lnx_now_precision_timespec(); + wait_until.tv_nsec += endt_us; - LNX_Entity* entity = (U64*)PtrFromInt(*semaphore.u64); - struct semaphore = entity->semaphore; + 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->handle, ¤t_value) - if (semaphore->max_value > current_value) + sem_getvalue(_semaphore, ¤t_value); + if (entity->semaphore.max_value > current_value) { - sem_timedwait(handle, wait_until); + sem_timedwait(_semaphore, &wait_until); } return (wait_result != -1); } @@ -1756,8 +1970,8 @@ os_semaphore_take(OS_Handle semaphore, U64 endt_us) internal void os_semaphore_drop(OS_Handle semaphore) { - LNX_Entity* entity (U64*)PtrFromInt(*semaphore.u64); - sem_t* _semaphore = entity->semaphore->handle; + LNX_Entity* entity = lnx_entity_from_handle(semaphore); + sem_t* _semaphore = entity->semaphore.handle; sem_post(_semaphore); } 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 ae91e6fba..a754c34a2 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 @@ -14,7 +14,9 @@ #include #include #include -#include +#include +#include +#include #include #include #include @@ -24,6 +26,23 @@ #include #include +////////////////////////////////// + // Helper Typedefs +// 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 Statistics +typedef struct stat lnx_fstat; + +// 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; + //////////////////////////////// //~ NOTE(allen): File Iterator @@ -41,6 +60,7 @@ enum LNX_EntityKind{ LNX_EntityKind_Null, LNX_EntityKind_Thread, LNX_EntityKind_Mutex, + LNX_EntityKind_Rwlock, LNX_EntityKind_ConditionVariable, LNX_EntityKind_Semaphore }; @@ -60,7 +80,8 @@ struct LNX_Entity{ sem_t* handle; U32 max_value; } semaphore; - pthread_mutex_t mutex; + LNX_mutex mutex; + LNX_rwlock rwlock; pthread_cond_t cond; }; }; @@ -79,12 +100,34 @@ struct LNX_SafeCallChain{ //~ NOTE(allen): Helpers internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list); +// Get high percision CPU clock based timestamp, unaffected by time settings +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); +void LNX_timespec_from_timeval(LNX_timespec* out, LNX_timeval* in); +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_mmap_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); From f2996ecac95f32a43724d31287179b299ff0234b Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sun, 7 Jul 2024 20:32:45 +0100 Subject: [PATCH 05/30] Added: Initial build.sh for Linux --- build.sh | 192 ++++++++++++++++++++++++++++++++++ documentation/build_system.md | 25 +++++ 2 files changed, 217 insertions(+) create mode 100755 build.sh create mode 100644 documentation/build_system.md diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..1a47e04c6 --- /dev/null +++ b/build.sh @@ -0,0 +1,192 @@ +#!/usr/bin/env bash + +# --- 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 + +# --- Random Init ----------------------------------------------------------- +# cd to script directory +self_directory="$(dirname $(readlink -f $0))" +echo "cd to '${self_directory}'" +cd "${self_directory}" +if [[ -n ${RAD_BUILD_DEBUG} ]] ; then + shellcheck $0 +fi + +# --- 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= && echo "[debug mode]" ; fi +if [[ -n "${release}" ]] ; then debug= && echo "[release mode]" ; fi +if [[ -n "${msvc}" ]] ; then clang= && echo "[msvc compile]" ; fi +if [[ -n "${clang}" ]] ; then msvc= && echo "[clang compile]" ; fi +if [[ -z "$1" ]] ; then echo "[default mode, assuming 'raddbg' build]" && raddbg=1 ; fi +if [[ "$1" == "release" && -z "$2" ]] ; then + echo "[default mode, assuming 'raddbg' build]" && raddbg=1 ; +fi +if [[ -n "${msvc}" ]] ; then + clang=0 + echo "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" && + echo "[telemetry profiling enabled]" +[[ -n "${asan}" ]] && auto_compile_flags="${auto_compile_flags} -fsanitize=address" && + "echo [asan enabled]" + +# --- Compile/Link Line Definitions ------------------------------------------ + cl_common="/I../src/ /I../local/ /nologo /FC /Z7" + clang_common="-I../src/ -I../local/ -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" + 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} ${auto_compile_flags}" +clang_release="clang -g -O2 -DBUILD_DEBUG=0 ${clang_common} ${auto_compile_flags}" + cl_link="/link /MANIFEST:EMBED /INCREMENTAL:NO \ +/natvis:'${self_directory}/src/natvis/base.natvis' logo.res" + clang_link="-fuse-ld=lld" + 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 ------------------------------------------------- +pushd build +# TODO(mallchad): "Linux cannot embed icons in exes :( build a .desktop file instead +popd + +# --- 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)" + +# --- Build & Run Metaprogram ------------------------------------------------ +if [[ -n "${no_meta}" ]] ; then + echo "[skipping metagen]" +else + pushd build + pwd + ${compile_debug} "../src/metagen/metagen_main.c" ${compile_link} "${out}metagen" || exit 1 + metagen.exe || exit 1 + popd +fi + +# --- Build Everything (@build_targets) -------------------------------------- + +# Exit if RAD_BUILD_ALL is nonzero length +function finish() +{ + [[ -z "${RAD_BUILD_ALL}" ]] && exit 1 +} + +# @param $1 - name of file to compile +# @param $2 - name of executable to output as +# @param $@ - rest is any arguments provided +function build_single() +{ + didbuild=1 && + ${compile} "$1" ${@:3:100} ${compile_link} "${out}$2" || finish + return $? +} + +function build_dll() +{ + didbuild=1 && + ${compile} "$1" ${compile_link} ${@:3:100} ${link_dll} "${out}$2" || finish + return $? +} + +pushd 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 +[[ -n "${mule_main}" ]] && + 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.obj mule_o2.obj ${compile_link} ${no_aslr} / + ${out}mule_main.exe || exit 1 + +RAD_BUILD_ALL="" +[[ -n "${mule_module}" ]] && build_dll ../src/mule/mule_module.cpp mule_module.dll || exit 1 +[[ -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 || exit exit 1 + +if [[ "${mule_peb_trample}"=="1" ]] ; 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 +popd + +# --- Unset ------------------------------------------------------------------ +# NOTE: Shouldn't need to unset, bash variables are volatile, even environment variables + +# --- Warn On No Builds ------------------------------------------------------ +if [[ -z "${didbuild}" ]] ; then + echo "[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 +fi diff --git a/documentation/build_system.md b/documentation/build_system.md new file mode 100644 index 000000000..f5bad1ad8 --- /dev/null +++ b/documentation/build_system.md @@ -0,0 +1,25 @@ + +# 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 + +## 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) +``` From 42f9765a0099a090db0bb934cf1fdd13108dc07c Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sun, 7 Jul 2024 21:24:59 +0100 Subject: [PATCH 06/30] [Build] Added: Linux build now compiles cleanly from new build.sh Added: Linking for pthread, dl, and rt Tweak: Changed RAD_BUILD_ALL to just build_all. Should be more consistent with other variables now and is documented Fixed: Random pushd and pwd commands echoing unwanted directories Documentation: Added some basic comments / thoughts about the build system --- build.sh | 27 ++++++++++++++------------- documentation/build_system.md | 10 +++++++++- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/build.sh b/build.sh index 1a47e04c6..2abcaa52f 100755 --- a/build.sh +++ b/build.sh @@ -24,6 +24,7 @@ # - `asan`: enable address sanitizer # - `telemetry`: enable RAD telemetry profiling support # - `gcodeview`: generate codeview symbols instead of DRWARF for clang +# - `build_all`: don't stop on a succesful compile and build everyting # --- Random Init ----------------------------------------------------------- # cd to script directory @@ -63,7 +64,7 @@ set auto_compile_flags= # --- Compile/Link Line Definitions ------------------------------------------ cl_common="/I../src/ /I../local/ /nologo /FC /Z7" - clang_common="-I../src/ -I../local/ -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" + clang_common="-I../src/ -I../local/ -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 -lpthread -ldl -lrt" 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} ${auto_compile_flags}" @@ -104,9 +105,9 @@ mkdir -p "build" mkdir -p "local" # --- Produce Logo Icon File ------------------------------------------------- -pushd build +cd build # TODO(mallchad): "Linux cannot embed icons in exes :( build a .desktop file instead -popd +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\" @@ -117,11 +118,10 @@ compile="${compile} -DBUILD_GIT_HASH=$(git describe --always --dirty)" if [[ -n "${no_meta}" ]] ; then echo "[skipping metagen]" else - pushd build - pwd - ${compile_debug} "../src/metagen/metagen_main.c" ${compile_link} "${out}metagen" || exit 1 + cd build + ${compile_debug} "../src/metagen/metagen_main.c" ${compile_link} "${out}metagen.exe" || exit 1 metagen.exe || exit 1 - popd + cd ${self_directory} fi # --- Build Everything (@build_targets) -------------------------------------- @@ -129,7 +129,7 @@ fi # Exit if RAD_BUILD_ALL is nonzero length function finish() { - [[ -z "${RAD_BUILD_ALL}" ]] && exit 1 + [[ -z "${build_all}" ]] && exit 1 } # @param $1 - name of file to compile @@ -149,7 +149,7 @@ function build_dll() return $? } -pushd build +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 @@ -158,14 +158,15 @@ pushd build [[ -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 +echo ${compile_debug} [[ -n "${mule_main}" ]] && 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.obj mule_o2.obj ${compile_link} ${no_aslr} / - ${out}mule_main.exe || exit 1 + ${compile_debug} ${EHsc} ../src/mule/mule_main.cpp ../src/mule/mule_c.c \ + mule_inline.obj mule_o2.obj ${compile_link} ${no_aslr} \ + ${out} mule_main.exe || exit 1 RAD_BUILD_ALL="" [[ -n "${mule_module}" ]] && build_dll ../src/mule/mule_module.cpp mule_module.dll || exit 1 @@ -180,7 +181,7 @@ if [[ "${mule_peb_trample}"=="1" ]] ; then build_single "../src/mule/mule_peb_trample.c" "mule_peb_trample_new.exe" mv "mule_peb_trample_new.exe" "mule_peb_trample.exe" fi -popd +cd "${self_directory}" # --- Unset ------------------------------------------------------------------ # NOTE: Shouldn't need to unset, bash variables are volatile, even environment variables diff --git a/documentation/build_system.md b/documentation/build_system.md index f5bad1ad8..3a48b5b5c 100644 --- a/documentation/build_system.md +++ b/documentation/build_system.md @@ -1,3 +1,6 @@ +# 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 @@ -15,8 +18,13 @@ this can be turned into a `.desktop` file. ## TODO - [ ] Make a `.desktop` file on build +## Compiler Flags +* -lpthread | POSIX threading library +* -dl | dynamic linking library +* -rt | realtime time library for clock functionality + ## 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. +* `-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 From 7fbafe19a94188638a4a0754f4f426f5fb9fbef5 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Tue, 9 Jul 2024 14:49:49 +0100 Subject: [PATCH 07/30] Fixed: Mistakes with conversion function signatures --- .../metagen_os/core/linux/metagen_os_core_linux.c | 12 ++++++------ .../metagen_os/core/linux/metagen_os_core_linux.h | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c index a8adb2d81..d3835ed12 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -93,12 +93,12 @@ 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) +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) +internal void lnx_timeval_from_date_time(LNX_timeval* out, DateTime* in) { NotImplemented; } @@ -113,7 +113,7 @@ lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in){ } internal void -LNX_timeval_from_dense_time(LNX_timeval* out, DenseTime* in) +lnx_timeval_from_dense_time(LNX_timeval* out, DenseTime* in) { // Miliseconds to Microseconds, should be U64 to long out->tv_sec = 0; @@ -121,7 +121,7 @@ LNX_timeval_from_dense_time(LNX_timeval* out, DenseTime* in) } internal void -LNX_timespec_from_dense_time(LNX_timespec* out, DenseTime* in) +lnx_timespec_from_dense_time(LNX_timespec* out, DenseTime* in) { // Miliseconds to Seconds, should be U64 to long out->tv_sec = (*in / 1000); @@ -129,14 +129,14 @@ LNX_timespec_from_dense_time(LNX_timespec* out, DenseTime* in) } void -LNX_timespec_from_timeval(LNX_timespec* out, LNX_timeval* in ) +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 ) +lnx_timeval_from_timespec(LNX_timeval* out, LNX_timespec* in ) { out->tv_sec = in->tv_sec; out->tv_usec = in->tv_nsec * 1000; 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 a754c34a2..ff8a2a204 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 @@ -108,13 +108,13 @@ 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_timespec_from_date_time(LNX_timespec* out, DateTime* in); -internal void LNX_timeval_from_date_time(LNX_timeval* out, DateTime* 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); -void LNX_timespec_from_timeval(LNX_timespec* out, LNX_timeval* in); -void LNX_timeval_from_timespec(LNX_timeval* 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 From e317fc9c96a164d08ba8640d54958f7c212fc18a Mon Sep 17 00:00:00 2001 From: Mallchad Date: Fri, 12 Jul 2024 16:55:16 +0100 Subject: [PATCH 08/30] [Metagen Linux] Added: Checkpoint - Yet more missing implimentations Focused mainly around memory allocations this time [Metagen Linux] Fixed: Wrong lnx function name call [Build System] Fixed: Wrong syntax for calling metagen.exe in current directory --- build.sh | 2 +- .../core/linux/metagen_os_core_linux.c | 110 +++++++++++++++--- .../core/linux/metagen_os_core_linux.h | 11 +- 3 files changed, 104 insertions(+), 19 deletions(-) diff --git a/build.sh b/build.sh index 2abcaa52f..79a9682bf 100755 --- a/build.sh +++ b/build.sh @@ -120,7 +120,7 @@ if [[ -n "${no_meta}" ]] ; then else cd build ${compile_debug} "../src/metagen/metagen_main.c" ${compile_link} "${out}metagen.exe" || exit 1 - metagen.exe || exit 1 + ./metagen.exe || exit 1 cd ${self_directory} fi diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c index d3835ed12..a2ca093a0 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -12,6 +12,8 @@ global LNX_Entity lnx_entity_buffer[1024]; 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; global B32 lnx_huge_page_enabled = 1; global B32 lnx_huge_page_use_1GB = 0; global U16 lnx_ring_buffers_created = 0; @@ -160,11 +162,12 @@ lnx_file_properties_from_stat(FileProperties *out, struct stat *in){ } internal U32 -lnx_mmap_from_os_flags(OS_AccessFlags flags) +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; } @@ -1010,6 +1013,8 @@ os_init(int argc, char **argv) } } scratch_end(scratch); + + lnx_page_size = (U64)getpagesize(); } //////////////////////////////// @@ -1182,10 +1187,11 @@ os_machine_name(void){ return(name); } +// Precomptued at init internal U64 -os_page_size(void){ - int size = getpagesize(); - return((U64)size); +os_page_size(void) +{ + return lnx_page_size; } internal U64 @@ -1388,7 +1394,17 @@ os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) 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 @@ -1397,7 +1413,7 @@ os_file_set_times(OS_Handle file, DateTime time) 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); + lnx_timeval_from_dense_time(access_and_modification, &tmp); access_and_modification[1] = access_and_modification[0]; B32 error = futimes(fd, access_and_modification); @@ -1497,38 +1513,84 @@ os_file_path_exists(String8 path) //- 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) { // TODO: Implement access flags + LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_MemoryMap); S32 fd = *file.u64; - struct stat file_info; - fstat(fd, &file_info); - U64 filesize = file_info.st_size; - void *address = mmap(0, filesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + entity->map.fd = fd; OS_Handle result = {0}; - *result.u64 = IntFromPtr(address); + *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); + 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); + + 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) ); + munmap(entity->map.data, entity->map.size); } //- rjf: directory iteration @@ -1572,7 +1634,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; + 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; } @@ -1588,6 +1664,8 @@ internal void os_shared_memory_close(OS_Handle handle) { NotImplemented; + LNX_Entity entity = *lnx_entity_from_handle(handle); + munmap(entity.map.data, entity.map.size); } internal void * 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 ff8a2a204..975acd25c 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 @@ -62,7 +62,8 @@ enum LNX_EntityKind{ LNX_EntityKind_Mutex, LNX_EntityKind_Rwlock, LNX_EntityKind_ConditionVariable, - LNX_EntityKind_Semaphore + LNX_EntityKind_Semaphore, + LNX_EntityKind_MemoryMap, }; typedef struct LNX_Entity LNX_Entity; @@ -80,6 +81,12 @@ struct LNX_Entity{ sem_t* handle; U32 max_value; } semaphore; + struct{ + S32 fd; + U32 flags; + void* data; + U64 size; + } map; LNX_mutex mutex; LNX_rwlock rwlock; pthread_cond_t cond; @@ -118,7 +125,7 @@ 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_mmap_from_os_flags(OS_AccessFlags 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); From 5f262dadf927d76850894959ad5315c617d82410 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Mon, 15 Jul 2024 00:00:59 +0100 Subject: [PATCH 09/30] [Metagen Linux] Added: Checkpoint - Yet more missing implimentations [Metagen Linux] Fixed: os_commit should now be backed by physical memory like in Windows --- .../core/linux/metagen_os_core_linux.c | 36 +++++++++++++++++-- .../core/linux/metagen_os_core_linux.h | 10 ++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c index a2ca093a0..478368be4 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -20,6 +20,11 @@ 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 @@ -1015,6 +1020,18 @@ os_init(int argc, char **argv) scratch_end(scratch); lnx_page_size = (U64)getpagesize(); + + // Kernel, Hostname and Architecture Info + struct utsname kernel = {0}; + S32 uname_error = uname(&kernel); + + lnx_hostname = push_str8_copy( lnx_perm_arena, str8_cstring(kernel.nodename) ); + lnx_kernel_type = push_str8_copy( lnx_perm_arena, str8_cstring(kernel.sysname) ) ; + lnx_kernel_version.major; + lnx_kernel_version.minor; + lnx_kernel_version.patch; + lnx_kernel_version.string = push_str8_copy( lnx_perm_arena, str8_cstring(kernel.release) ); + lnx_architecture = push_str8_copy( lnx_perm_arena, str8_cstring(kernel.machine) ); } //////////////////////////////// @@ -1026,13 +1043,19 @@ os_reserve(U64 size){ return(result); } +/* NOTE(mallchad): I wanted to use MADV_POPULATE_READ/WRITE to fault the pages + into memory here but my kernel was *just* old enough to not have the headers + for this feature so I will use a trick with mlock instead. */ internal B32 os_commit(void *ptr, U64 size){ U32 error = mprotect(ptr, size, PROT_READ|PROT_WRITE); + mlock(ptr, size); + munlock(ptr, size); // TODO(allen): can we test this? return (error == 0); } +// TODO: Need to check if enough huge pages are available or it will just straight up fail internal void* os_reserve_large(U64 size){ F32 huge_threshold = 0.5f; @@ -1041,6 +1064,9 @@ os_reserve_large(U64 size){ 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); @@ -1052,6 +1078,8 @@ os_reserve_large(U64 size){ internal B32 os_commit_large(void *ptr, U64 size){ U32 error = mprotect(ptr, size, PROT_READ|PROT_WRITE); + mlock(ptr, size); + munlock(ptr, size); return (error == 0); } @@ -1187,7 +1215,10 @@ os_machine_name(void){ return(name); } -// Precomptued at init +/* 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) { @@ -1197,7 +1228,8 @@ os_page_size(void) 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(); } 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 975acd25c..7d663dc5c 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 @@ -25,6 +25,7 @@ #include #include #include +#include ////////////////////////////////// // Helper Typedefs @@ -106,6 +107,15 @@ 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; +}; + internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list); // Get high percision CPU clock based timestamp, unaffected by time settings internal LNX_timespec lnx_now_precision_timespec(); From 20b848ea5e15bab54fe9982482c4f04ac1a09e79 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sun, 21 Jul 2024 10:10:33 +0100 Subject: [PATCH 10/30] [Metagen Linux] Added: Checkpoint - Yet more missing implimentations Removed: Hugepage support temporarily, it can't function without additional work Fixed: Potential edge case where munmap doesn't sync to disk Tweak: Try to populate all mmap calls on either commit or view open for performance Maintenance: Renamed lnx_fstat and lnx_date to LNX_fstat to be closer in line with other types Fixed: Potential fix for setting up condition variable in wrong order [Build Batch] Added: Debug environmental variable for skipping running metagen NOTE: This is mostly because I didn't want to deal with erroring and crashing programs but wanted to still get compiler output for metagen. --- build.sh | 9 +- .../core/linux/metagen_os_core_linux.c | 108 ++++++++++++++---- .../core/linux/metagen_os_core_linux.h | 14 ++- 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/build.sh b/build.sh index 79a9682bf..6f5f23639 100755 --- a/build.sh +++ b/build.sh @@ -34,6 +34,7 @@ cd "${self_directory}" if [[ -n ${RAD_BUILD_DEBUG} ]] ; then shellcheck $0 fi +# RAD_META_COMPILE_ONLY # --- Unpack Command Line Build Arguments ------------------------------------ # NOTE: With this setup you can use environment variables or arguments and @@ -120,7 +121,8 @@ if [[ -n "${no_meta}" ]] ; then else cd build ${compile_debug} "../src/metagen/metagen_main.c" ${compile_link} "${out}metagen.exe" || exit 1 - ./metagen.exe || exit 1 + # Skip if compile only + [[ -z "${RAD_META_COMPILE_ONLY}" ]] && ./metagen.exe || exit 1 cd ${self_directory} fi @@ -129,7 +131,7 @@ fi # Exit if RAD_BUILD_ALL is nonzero length function finish() { - [[ -z "${build_all}" ]] && exit 1 + [[ -n "${build_all}" ]] && exit 1 } # @param $1 - name of file to compile @@ -168,7 +170,8 @@ echo ${compile_debug} mule_inline.obj mule_o2.obj ${compile_link} ${no_aslr} \ ${out} mule_main.exe || exit 1 -RAD_BUILD_ALL="" +# Continue building the rest line normal +RAD_BUILD_ALL="1" [[ -n "${mule_module}" ]] && build_dll ../src/mule/mule_module.cpp mule_module.dll || exit 1 [[ -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 || exit exit 1 diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c index 478368be4..3be3a11d7 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -14,7 +14,8 @@ global String8 lnx_initial_path = {0}; thread_static LNX_SafeCallChain *lnx_safe_call_chain = 0; global U64 lnx_page_size = 4096; -global B32 lnx_huge_page_enabled = 1; +// 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; @@ -932,7 +933,6 @@ lnx_safe_call_sig_handler(int _){ abort(); } -// Helper function to return a timespec using a adjtime stable high precision clock internal LNX_timespec lnx_now_precision_timespec() { @@ -1025,6 +1025,7 @@ os_init(int argc, char **argv) struct utsname kernel = {0}; S32 uname_error = uname(&kernel); + // TODO Parse the string lnx_hostname = push_str8_copy( lnx_perm_arena, str8_cstring(kernel.nodename) ); lnx_kernel_type = push_str8_copy( lnx_perm_arena, str8_cstring(kernel.sysname) ) ; lnx_kernel_version.major; @@ -1045,7 +1046,8 @@ os_reserve(U64 size){ /* NOTE(mallchad): I wanted to use MADV_POPULATE_READ/WRITE to fault the pages into memory here but my kernel was *just* old enough to not have the headers - for this feature so I will use a trick with mlock instead. */ + for this feature so I will use a trick with mlock instead. Since that will + work better for users on very old kernels */ internal B32 os_commit(void *ptr, U64 size){ U32 error = mprotect(ptr, size, PROT_READ|PROT_WRITE); @@ -1109,6 +1111,7 @@ internal B32 os_large_pages_enabled(void) { // 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}; @@ -1118,7 +1121,8 @@ os_large_pages_enabled(void) Rng1U64 match = str8_match_substr( meminfo, str8_cstring("Huge"), 0x0 ); - return (match.max > 0); + // return (match.max > 0); + return 0; } /* NOTE: The size seems to be consistent across Linux systems, it's configurable @@ -1155,7 +1159,7 @@ os_alloc_ring_buffer(U64 size, U64 *actual_size_out) 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, -1, 0); + 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 @@ -1427,7 +1431,7 @@ internal void os_file_write(OS_Handle file, Rng1U64 rng, void *data) { S32 fd = lnx_fd_from_handle(file); - lnx_fstat file_info; + LNX_fstat file_info; fstat(fd, &file_info); U64 filesize = file_info.st_size; U64 write_size = rng.max - rng.min; @@ -1457,7 +1461,7 @@ os_properties_from_file(OS_Handle file) { FileProperties result = {0}; S32 fd = *file.u64; - lnx_fstat props = {0}; + LNX_fstat props = {0}; B32 error = fstat(fd, &props); if (error == 0) @@ -1472,7 +1476,7 @@ os_id_from_file(OS_Handle file) { OS_FileID result = {0}; U32 fd = *file.u64; - lnx_fstat props = {0}; + LNX_fstat props = {0}; B32 error = fstat(fd, &props); if (error == 0) @@ -1502,7 +1506,7 @@ os_copy_file_path(String8 dst, String8 src) { S32 source_fd = open((char*)src.str, 0x0, O_RDONLY); S32 dest_fd = creat((char*)dst.str, O_WRONLY); - lnx_fstat props = {0}; + LNX_fstat props = {0}; S32 filesize = 0; S32 bytes_written = 0; @@ -1538,7 +1542,7 @@ os_full_path_from_path(Arena *arena, String8 path) internal B32 os_file_path_exists(String8 path) { - lnx_fstat _stub; + LNX_fstat _stub; B32 exists = (0 == stat((char*)path.str, &_stub)); return exists; } @@ -1567,6 +1571,7 @@ internal void os_file_map_close(OS_Handle map) { 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. */ @@ -1601,7 +1606,8 @@ os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) */ U64 page_size = lnx_page_size; U32 page_flag = 0x0; - U32 map_flags = page_flag | (flags & OS_AccessFlag_Shared ? MAP_SHARED : MAP_PRIVATE); + 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); @@ -1622,6 +1628,9 @@ os_file_map_view_close(OS_Handle map, void *ptr) { 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); } @@ -1630,21 +1639,59 @@ 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; + LNX_dir* directory = opendir((char*)path.str); + MemoryCopyTyped(result->memory, directory, 1); + + // 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; + if (iter == NULL) { return 0; } + + LNX_dir* directory = (LNX_dir*)iter->memory; + LNX_dir_entry* file = NULL; + FileProperties props = {0}; + LNX_fstat stats = {0}; + B32 no_file = 0; + if (directory == NULL) { 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; } + if (iter->flags & OS_FileIterFlag_SkipHiddenFiles && file->d_name[0] == '.' ) { continue; } + break; + } + + S32 fd = open(file->d_name, O_RDONLY, 0x0); + Assert(fd == 0); + S32 stats_err = fstat(fd, &stats); + Assert(stats_err == 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_dir* directory = (LNX_dir*)iter->memory; + int failure = closedir(directory); + Assert(failure == 0); } //- rjf: directory creation @@ -1674,7 +1721,7 @@ os_shared_memory_alloc(U64 size, String8 name) 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; + 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); @@ -1950,10 +1997,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; @@ -1976,7 +2024,7 @@ os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ 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_system_timespec(); + 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 @@ -1987,15 +2035,29 @@ os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ 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 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 7d663dc5c..ba2b7a4dd 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 @@ -33,9 +33,13 @@ typedef struct timespec LNX_timespec; typedef struct timeval LNX_timeval; // Deconstructed Date-Time -typedef struct tm lnx_date; +typedef struct tm LNX_date; // File Statistics -typedef struct stat lnx_fstat; +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; // Syncronization Primitives typedef sem_t LNX_semaphore; @@ -43,6 +47,7 @@ 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 @@ -90,7 +95,7 @@ struct LNX_Entity{ } map; LNX_mutex mutex; LNX_rwlock rwlock; - pthread_cond_t cond; + LNX_cond cond; }; }; @@ -117,7 +122,8 @@ struct LNX_version { }; internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list); -// Get high percision CPU clock based timestamp, unaffected by time settings + /* 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(); From 79b463d596d7cd7747618bc140a53a0bb7bd7688 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Fri, 26 Jul 2024 00:42:40 +0100 Subject: [PATCH 11/30] [Metagen Linux] Added: More missing Linux implimentations, metagen compiles and runs I haven't verified how well much of the code works but the one thing metagen uses, the OS filter iterator, seems happy and I checked a good few of the values it spat out. The only thing left is the condition variable broadcast and a few long winded things I don't know how to compelte the implementation on. I don't really know what a condition variable is or what a broadcast for it is so moving to testing other things first. Also the OS_SystemPath_ModuleLoad is not done. Maintenance: Sorted include lines- because nice. [Build Batch] Fixed: Exiting with fail status (1) on successful compile instead of success --- build.sh | 5 +- .../core/linux/metagen_os_core_linux.c | 119 ++++++++++++++---- .../core/linux/metagen_os_core_linux.h | 47 ++++--- 3 files changed, 131 insertions(+), 40 deletions(-) diff --git a/build.sh b/build.sh index 6f5f23639..47a60b35c 100755 --- a/build.sh +++ b/build.sh @@ -122,7 +122,8 @@ 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 + [[ -z "${RAD_META_COMPILE_ONLY}" ]] && (./metagen.exe || exit 1) + exit 0 cd ${self_directory} fi @@ -174,7 +175,7 @@ echo ${compile_debug} RAD_BUILD_ALL="1" [[ -n "${mule_module}" ]] && build_dll ../src/mule/mule_module.cpp mule_module.dll || exit 1 [[ -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 || exit exit 1 +build_dll ../src/mule/mule_hotload_module_main.c mule_hotload_module.dll || exit 1 if [[ "${mule_peb_trample}"=="1" ]] ; then didbuild=1 diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c index 3be3a11d7..ca42d5578 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -1641,8 +1641,12 @@ os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) { 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); - MemoryCopyTyped(result->memory, directory, 1); + 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; @@ -1653,14 +1657,20 @@ os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) internal B32 os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) { + MemoryZeroStruct(info_out); if (iter == NULL) { return 0; } - LNX_dir* directory = (LNX_dir*)iter->memory; + 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; - if (directory == NULL) { return 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 (;;) @@ -1671,15 +1681,17 @@ os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) if (iter->flags & OS_FileIterFlag_SkipFiles && file->d_type == DT_REG ) { continue; } if (iter->flags & OS_FileIterFlag_SkipFolders && file->d_type == DT_DIR ) { continue; } - if (iter->flags & OS_FileIterFlag_SkipHiddenFiles && file->d_name[0] == '.' ) { 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; } - - S32 fd = open(file->d_name, O_RDONLY, 0x0); - Assert(fd == 0); + 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); + 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); @@ -1689,9 +1701,10 @@ os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) internal void os_file_iter_end(OS_FileIter *iter) { - LNX_dir* directory = (LNX_dir*)iter->memory; - int failure = closedir(directory); - Assert(failure == 0); + 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 @@ -1735,29 +1748,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); - munmap(entity.map.data, entity.map.size); + 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); } //////////////////////////////// @@ -1833,12 +1867,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_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(0); + 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; } //////////////////////////////// @@ -2068,8 +2132,8 @@ os_condition_variable_signal_(OS_Handle cv){ internal void os_condition_variable_broadcast_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); NotImplemented; + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); } //- rjf: cross-process semaphores @@ -2213,6 +2277,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 ba2b7a4dd..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,41 +7,51 @@ //////////////////////////////// //~ 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 +#include +#include ////////////////////////////////// - // Helper Typedefs +// 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; -// Syncronization Primitives +// -- 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; @@ -89,9 +99,10 @@ struct LNX_Entity{ } semaphore; struct{ S32 fd; - U32 flags; + U32 flags; // TODO: Maybe delete void* data; U64 size; + String8 shm_name; } map; LNX_mutex mutex; LNX_rwlock rwlock; @@ -121,6 +132,12 @@ struct LNX_version { 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*/ From cbe4d4f3ba70e8d565c786417c1b8d2be25519af Mon Sep 17 00:00:00 2001 From: Mallchad Date: Fri, 26 Jul 2024 21:46:22 +0100 Subject: [PATCH 12/30] Fixed: Various fixes for raddbg and transferred over work from metagen [Build Batch] Fixed: All targets specified should build like it's supposed to in the windows version Fixed: metagen section exiting script early [Third Party] [rad_lzb_simple] Fixed: RR_ASSERT did not support clang traps which was preventing compiles Fixed: Using standard breaking label (done:) syntax without a statement (done: statement;) RANT: So aparently this entire time MSVC and gcc were using incompatible standard syntax for the label syntax, notice how I said syntax not statement. Because goto is not a staement- it's a "labelled-staement" whatever that means, which is just ever so slightly different to a regular statement, which is ended by a semicolon (;), gcc and clang (as far I know) just auto-completes the statement. And this is your daily reminder that the C/C++ Standards Commitee are stupid and failed to create a stable and portable language, and you should never take anything for granted unless its explicitly stated in the standard. Have. Fun. Programming. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf [Metagen Linux] Removed: 'pthread.so' load, it was a mistake the compiler sets it up automatically Fixed: Maybe fix for 'lnx_initial_path' not being setup properly or having integer under/overflow [Raddbg Linux] Removed: 'pthread.so' load, it was a mistake the compiler sets it up automatically Fixed: Maybe fix for 'lnx_initial_path' not being setup properly or having integer under/overflow Removed: Depreceated os_get_command_line_arguments it seems to be unused and the API changed. [Base Strings] Added: str8_substring_match from metagen --- build.sh | 12 +- src/base/base_strings.c | 41 + src/base/base_strings.h | 3 + .../core/linux/metagen_os_core_linux.c | 16 +- src/os/core/linux/os_core_linux.c | 980 ++++++++++++++---- src/os/core/linux/os_core_linux.h | 130 ++- .../rad_lzb_simple/rad_lzb_simple.c | 10 +- .../rad_lzb_simple/rad_lzb_simple.h | 10 +- 8 files changed, 973 insertions(+), 229 deletions(-) diff --git a/build.sh b/build.sh index 47a60b35c..1fcc8c2a1 100755 --- a/build.sh +++ b/build.sh @@ -24,7 +24,6 @@ # - `asan`: enable address sanitizer # - `telemetry`: enable RAD telemetry profiling support # - `gcodeview`: generate codeview symbols instead of DRWARF for clang -# - `build_all`: don't stop on a succesful compile and build everyting # --- Random Init ----------------------------------------------------------- # cd to script directory @@ -123,16 +122,14 @@ else ${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) - exit 0 cd ${self_directory} fi # --- Build Everything (@build_targets) -------------------------------------- -# Exit if RAD_BUILD_ALL is nonzero length function finish() { - [[ -n "${build_all}" ]] && exit 1 + exit 1 } # @param $1 - name of file to compile @@ -152,6 +149,11 @@ function build_dll() return $? } +if [[ -n ${RAD_BUILD_DEBUG} ]] ; then + echo "Compile Command: " + echo ${compile} +fi + 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 @@ -161,7 +163,6 @@ cd build [[ -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 -echo ${compile_debug} [[ -n "${mule_main}" ]] && didbuild=1 && rm -v vc*.pdb mule*.pdb && @@ -172,7 +173,6 @@ echo ${compile_debug} ${out} mule_main.exe || exit 1 # Continue building the rest line normal -RAD_BUILD_ALL="1" [[ -n "${mule_module}" ]] && build_dll ../src/mule/mule_module.cpp mule_module.dll || exit 1 [[ -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 || exit 1 diff --git a/src/base/base_strings.c b/src/base/base_strings.c index 355cc62ef..026f3b73c 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 0) - { lnx_initial_path = push_str8_copy(lnx_perm_arena, _initial_tmp); } - // else - It failed, its a relative path now, have fun. + { + 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); - // Load Shared Objects - os_library_open( str8_lit("pthread.so") ); - // Environment initialization Temp scratch = scratch_begin(0, 0); String8 env; diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 07e4724d0..6a687e798 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1,31 +1,55 @@ // Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#include - + /* TODO(mallchad): want to add asserts for 'kind' of LNX_Entity */ //////////////////////////////// //~ 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_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 +// 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 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 +60,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,7 +75,7 @@ lnx_write_list_to_file_descriptor(int fd, String8List list){ } } } - + return(success); } @@ -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(""); @@ -785,12 +909,12 @@ 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 @@ -801,7 +925,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 +933,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) @@ -834,16 +975,61 @@ 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; + 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 +1093,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,43 +1177,62 @@ os_free_ring_buffer(void *ring_buffer, U64 actual_size) internal String8 os_machine_name(void){ - local_persist B32 invalid = true; + local_persist B32 first = 1; local_persist String8 name = {0}; - const U32 hostname_limit = 255; // Max reasonable size for hostname - - // NOTE(mallchad): There was a lot of complicated code here but I could not - // figure out what the purpose was for such a simple syscall - if (invalid){ - pthread_mutex_lock(&lnx_mutex); + // 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); - U8 *tmp = push_array_no_zero(scratch.arena, U8, hostname_limit); - S32 error = gethostname((char*)tmp, hostname_limit); - - // No Errors - if (error == 0){ - String8 tmp_string = str8_cstring(tmp); - name = push_str8_copy(lnx_perm_arena, tmp_string); - invalid = false; + first = 0; + + // get name + B32 got_final_result = 0; + U8 *buffer = 0; + int size = 0; + for (S64 cap = 4096, r = 0; + r < 4; + cap *= 2, r += 1){ + buffer = push_array_no_zero(scratch.arena, U8, cap); + size = gethostname((char*)buffer, cap); + if (size < cap){ + got_final_result = 1; + 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); - pthread_mutex_unlock(&lnx_mutex); } - + pthread_mutex_unlock(&lnx_mutex); + 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 @@ -970,12 +1243,12 @@ os_logical_core_count(void) } //////////////////////////////// -//~ rjf: @os_hooks Process & Thread Info (Implemented Per-OS) +//~ rjf: @os_hooks Process Info (Implemented Per-OS) internal String8List os_get_command_line_arguments(void) { - return lnx_cmd_line_args; + NotImplemented; /* This doesn't appear to be used any longer */ } internal S32 @@ -998,65 +1271,62 @@ os_get_tid(void){ internal String8List os_get_environment(void) { - NotImplemented; - String8List result = {0}; - return result; + return lnx_environment; } 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; 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 = 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); @@ -1065,7 +1335,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"); @@ -1073,7 +1343,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 @@ -1081,7 +1351,7 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o NotImplemented; }break; } - + return(result); } @@ -1102,60 +1372,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); @@ -1164,61 +1501,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; -} - -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 */ 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 @@ -1226,21 +1636,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 @@ -1248,13 +1709,10 @@ 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; + B32 result = 0; + if (mkdir((char*)path.str, 0777) != -1){ + result = 1; } - scratch_end(scratch); return(result); } @@ -1265,7 +1723,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; } @@ -1273,27 +1745,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); } //////////////////////////////// @@ -1324,7 +1819,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); @@ -1342,7 +1837,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); @@ -1352,7 +1847,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); } @@ -1365,11 +1864,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; } //////////////////////////////// @@ -1382,7 +1912,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); @@ -1392,7 +1922,7 @@ 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); @@ -1400,7 +1930,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 @@ -1421,7 +1951,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); @@ -1432,7 +1962,7 @@ os_mutex_alloc(void){ lnx_free_entity(entity); entity = 0; } - + // cast to opaque handle OS_Handle result = {IntFromPtr(entity)}; return(result); @@ -1440,20 +1970,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); } @@ -1463,38 +1993,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 @@ -1503,17 +2054,20 @@ 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); + // 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; } - + // cast to opaque handle OS_Handle result = {IntFromPtr(entity)}; return(result); @@ -1521,45 +2075,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 @@ -1568,41 +2139,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, 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); } //////////////////////////////// @@ -1623,7 +2226,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); @@ -1633,7 +2236,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); } @@ -1646,21 +2249,21 @@ os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, v 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); } @@ -1671,6 +2274,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/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index 9899f94a0..c916030f0 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -7,41 +7,72 @@ //////////////////////////////// //~ 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 - -//////////////////////////////// -//~ 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; + +// -- 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): 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 +83,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 +113,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 +165,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/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)) From d208f2ca28a12c63358e90edf262279ad5f5f2cb Mon Sep 17 00:00:00 2001 From: Mallchad Date: Thu, 1 Aug 2024 09:49:09 +0100 Subject: [PATCH 13/30] [Raddbg Linux] Fixed: 'tctx_release()' being removed possibly erroneously Fixed: 'OS_AccessFlag_Shared' not being valid in this context Added: Minor assert for 'lnx_entity_from_handle' [Metagen Linux Linux] Fixed: 'tctx_release()' being removed possibly erroneously [Build Batch] Fixed: Missing quoting for git hash --- build.sh | 2 +- .../core/linux/metagen_os_core_linux.c | 3 +- src/os/core/linux/os_core_linux.c | 63 ++++++++++--------- src/os/core/linux/os_core_linux.h | 2 +- 4 files changed, 37 insertions(+), 33 deletions(-) diff --git a/build.sh b/build.sh index 1fcc8c2a1..8bd6a0602 100755 --- a/build.sh +++ b/build.sh @@ -112,7 +112,7 @@ 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)" +compile="${compile} -DBUILD_GIT_HASH=\"$(git describe --always --dirty)\"" # --- Build & Run Metaprogram ------------------------------------------------ if [[ -n "${no_meta}" ]] ; then diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c index b4631c101..9003ba8e8 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -912,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); diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 6a687e798..73f526545 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1,7 +1,6 @@ // Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) - /* TODO(mallchad): want to add asserts for 'kind' of LNX_Entity */ //////////////////////////////// //~ rjf: Globals @@ -209,9 +208,10 @@ lnx_handle_from_fd(U32 fd) } internal LNX_Entity* -lnx_entity_from_handle(OS_Handle handle) +lnx_entity_from_handle(OS_Handle handle, LNX_EntityKind type) { LNX_Entity* result = (LNX_Entity*)PtrFromInt(*handle.u64); + Assert(result->kind == type); return result; } internal OS_Handle lnx_handle_from_entity(LNX_Entity* entity) @@ -914,6 +914,7 @@ lnx_thread_base(void *ptr){ tctx_init_and_equip(&tctx_); func(thread_ptr); + tctx_release(); // remove my bit U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x2); @@ -1022,7 +1023,7 @@ os_init(void) struct utsname kernel = {0}; S32 uname_error = uname(&kernel); - // TODO Parse the string + // TODO: Parse the string lnx_hostname = push_str8_copy( lnx_perm_arena, str8_cstring(kernel.nodename) ); lnx_kernel_type = push_str8_copy( lnx_perm_arena, str8_cstring(kernel.sysname) ) ; lnx_kernel_version.major; @@ -1229,9 +1230,10 @@ os_page_size(void) internal U64 os_allocation_granularity(void) { - /* 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(); + /* 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(); } @@ -1243,7 +1245,7 @@ os_logical_core_count(void) } //////////////////////////////// -//~ rjf: @os_hooks Process Info (Implemented Per-OS) +//~ rjf: @os_hooks Process & Thread Info (Implemented Per-OS) internal String8List os_get_command_line_arguments(void) @@ -1567,7 +1569,7 @@ os_file_map_open(OS_AccessFlags flags, OS_Handle file) internal void os_file_map_close(OS_Handle map) { - LNX_Entity* entity = lnx_entity_from_handle(map); + 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 @@ -1586,7 +1588,7 @@ page-boundary aligned boundary before offset */ internal void * os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) { - LNX_Entity* entity = lnx_entity_from_handle(map); + LNX_Entity* entity = lnx_entity_from_handle(map, LNX_EntityKind_MemoryMap); S32 fd = entity->map.fd; struct stat file_info; fstat(fd, &file_info); @@ -1603,8 +1605,9 @@ os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) */ 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 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); @@ -1623,7 +1626,7 @@ os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) internal void os_file_map_view_close(OS_Handle map, void *ptr) { - LNX_Entity* entity = lnx_entity_from_handle(map); + 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. */ @@ -1759,7 +1762,7 @@ os_shared_memory_open(String8 name) internal void os_shared_memory_close(OS_Handle handle) { - LNX_Entity* entity = lnx_entity_from_handle(handle); + LNX_Entity* entity = lnx_entity_from_handle(handle, LNX_EntityKind_MemoryMap); shm_unlink( (char*)(entity->map.shm_name.str) ); } @@ -1767,7 +1770,7 @@ internal void * os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) { void* result = NULL; - LNX_Entity* entity = lnx_entity_from_handle(handle); + 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); @@ -1787,7 +1790,7 @@ os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) internal void os_shared_memory_view_close(OS_Handle handle, void *ptr) { - LNX_Entity entity = *lnx_entity_from_handle(handle); + LNX_Entity entity = *lnx_entity_from_handle(handle, LNX_EntityKind_MemoryMap); munmap(entity.map.data, entity.map.size); } @@ -2013,7 +2016,7 @@ os_rw_mutex_alloc(void) internal void os_rw_mutex_release(OS_Handle rw_mutex) { - LNX_Entity* entity = lnx_entity_from_handle(rw_mutex); + LNX_Entity* entity = lnx_entity_from_handle(rw_mutex, LNX_EntityKind_Mutex); pthread_rwlock_destroy(&entity->rwlock); } @@ -2021,7 +2024,7 @@ internal void os_rw_mutex_take_r_(OS_Handle mutex) { // Is blocking varient - LNX_Entity* entity = lnx_entity_from_handle(mutex); + LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Mutex); pthread_rwlock_rdlock(&entity->rwlock); } @@ -2030,14 +2033,14 @@ os_rw_mutex_drop_r_(OS_Handle mutex) { // 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_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Mutex); pthread_rwlock_unlock(&entity->rwlock); } internal void os_rw_mutex_take_w_(OS_Handle mutex) { - LNX_Entity* entity = lnx_entity_from_handle(mutex); + LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Mutex); pthread_rwlock_rdlock(&entity->rwlock); } @@ -2083,8 +2086,8 @@ os_condition_variable_release(OS_Handle cv){ internal B32 os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ B32 result = 0; - LNX_Entity *entity_cond = lnx_entity_from_handle(cv); - LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex); + 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 = lnx_now_precision_timespec(); timeout_stamp.tv_nsec += endt_us * 1000; @@ -2097,8 +2100,8 @@ internal B32 os_condition_variable_wait_rw_r_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) { B32 result = 0; - LNX_Entity *entity_cond = lnx_entity_from_handle(cv); - LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex_rw); + LNX_Entity *entity_cond = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); + LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex_rw, LNX_EntityKind_Mutex); LNX_timespec timeout_stamp = lnx_now_precision_timespec(); timeout_stamp.tv_nsec += endt_us * 1000; @@ -2111,8 +2114,8 @@ internal B32 os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) { B32 result = 0; - LNX_Entity *entity_cond = lnx_entity_from_handle(cv); - LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex_rw); + LNX_Entity *entity_cond = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); + LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex_rw, LNX_EntityKind_Mutex); LNX_timespec timeout_stamp = lnx_now_precision_timespec(); timeout_stamp.tv_nsec += endt_us * 1000; @@ -2123,14 +2126,14 @@ os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) internal void os_condition_variable_signal_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); + LNX_Entity *entity = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); pthread_cond_signal(&entity->cond); } internal void os_condition_variable_broadcast_(OS_Handle cv){ NotImplemented; - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); + LNX_Entity *entity = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); } //- rjf: cross-process semaphores @@ -2176,7 +2179,7 @@ os_semaphore_open(String8 name) internal void os_semaphore_close(OS_Handle semaphore) { - LNX_Entity* entity = lnx_entity_from_handle(semaphore); + LNX_Entity* entity = lnx_entity_from_handle(semaphore, LNX_EntityKind_Semaphore); LNX_semaphore* _semaphore = entity->semaphore.handle; sem_close(_semaphore); } @@ -2188,7 +2191,7 @@ os_semaphore_take(OS_Handle semaphore, U64 endt_us) LNX_timespec wait_until = lnx_now_precision_timespec(); wait_until.tv_nsec += endt_us; - LNX_Entity* entity = lnx_entity_from_handle(semaphore); + 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; @@ -2203,7 +2206,7 @@ os_semaphore_take(OS_Handle semaphore, U64 endt_us) internal void os_semaphore_drop(OS_Handle semaphore) { - LNX_Entity* entity = lnx_entity_from_handle(semaphore); + LNX_Entity* entity = lnx_entity_from_handle(semaphore, LNX_EntityKind_Semaphore); sem_t* _semaphore = entity->semaphore.handle; sem_post(_semaphore); } diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index c916030f0..5b796d701 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -155,7 +155,7 @@ 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 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); From 1521008b2318aa2ddf5d556e25099989bc5a486c Mon Sep 17 00:00:00 2001 From: Mallchad Date: Mon, 5 Aug 2024 13:10:14 +0100 Subject: [PATCH 14/30] [OS Core] Added: Atomic gcc intrinsics implimentations for Linux Fixed: '__VA_ARGS' on various macros erroring with zero arguments. So use '##__VA_ARGS' extension instead. [DF GFX] Fixed: Superflous OS-specific 'Sleep()' function [Build Batch] Added: Erorr-by-default for invalid memory ordering --- build.sh | 7 ++-- src/base/base_core.h | 33 ++++++++++++++++++- src/base/base_log.h | 6 ++-- src/df/gfx/df_gfx.c | 2 +- .../core/linux/metagen_os_core_linux.c | 2 +- src/render/stub/render_stub.c | 2 +- 6 files changed, 42 insertions(+), 10 deletions(-) diff --git a/build.sh b/build.sh index 8bd6a0602..9af815df6 100755 --- a/build.sh +++ b/build.sh @@ -64,11 +64,12 @@ set auto_compile_flags= # --- Compile/Link Line Definitions ------------------------------------------ cl_common="/I../src/ /I../local/ /nologo /FC /Z7" - clang_common="-I../src/ -I../local/ -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 -lpthread -ldl -lrt" + clang_common="-I../src/ -I../local/ -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 -lpthread -ldl -lrt -latomic" + clang_errors="-Werror=atomic-memory-ordering" 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} ${auto_compile_flags}" -clang_release="clang -g -O2 -DBUILD_DEBUG=0 ${clang_common} ${auto_compile_flags}" + clang_debug="clang -g -O0 -DBUILD_DEBUG=1 ${clang_common} ${clang_errors} ${auto_compile_flags}" +clang_release="clang -g -O2 -DBUILD_DEBUG=0 ${clang_common} ${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=lld" diff --git a/src/base/base_core.h b/src/base/base_core.h index cb8904bc7..6a41c238e 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -178,7 +178,38 @@ # 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 ); +} + +U64 +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 ); +} + +#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 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/df/gfx/df_gfx.c b/src/df/gfx/df_gfx.c index ee7bfa06f..c79eb6b4e 100644 --- a/src/df/gfx/df_gfx.c +++ b/src/df/gfx/df_gfx.c @@ -14187,7 +14187,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/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c index 9003ba8e8..8d5f84ec7 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -914,7 +914,7 @@ lnx_thread_base(void *ptr){ tctx_init_and_equip(&tctx_); func(thread_ptr); - tctx_release(); + // tctx_release(); // remove my bit U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x2); 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 From c40a4ebce8897896bbba32c54e3ea630717e9539 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Tue, 13 Aug 2024 00:33:31 +0100 Subject: [PATCH 15/30] [Tests] Added: A bunch of tests for the os_core platform layer [Core Linux] Fixed: Slightly different os_set_large_pages semantics to win32 API Fixed: 'actual_size_out' not being written to with ring buffers Fixed: 'os_machine_name' being completely broken NOTE: The logic looks sort of correct but a return code was interpreted wrong, and the rest was completely irrelevant, the hostname spec caps to 253 ASCII chars precisely I believe. So I just replaced nearly the whole function with a minimal and working version Fixed: 'lnx_environment' not being set properly because of Linux's unspecified virtual filesize Fixed: 'os_file_open' Typo Fixed: 'os_file_open' file descriptor mode being in decimal '660' when it should be octal '0600' Added: Some "Zero-Is-Initialization" style error handling for some things Maintenance: Tagged some functions as out of date/stale/needing review for validity Fixed: Several asorted unspecified fixes for Linux platform layer [Misc] Added: Some missing from metagen base_string --- src/base/base_strings.c | 59 +++++++++ src/base/base_strings.h | 6 + src/os/core/linux/os_core_linux.c | 154 ++++++++++++++--------- src/os/core/linux/os_core_linux.h | 3 +- src/tests/test_os_core.c | 202 ++++++++++++++++++++++++++++++ 5 files changed, 363 insertions(+), 61 deletions(-) create mode 100644 src/tests/test_os_core.c diff --git a/src/base/base_strings.c b/src/base/base_strings.c index 026f3b73c..dc1b2378e 100644 --- a/src/base/base_strings.c +++ b/src/base/base_strings.c @@ -582,6 +582,65 @@ try_s64_from_str8_c_rules(String8 string, S64 *x){ return(is_integer); } +//- rjf: string -> 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 f0a870a5a..f93926a1c 100644 --- a/src/base/base_strings.h +++ b/src/base/base_strings.h @@ -239,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/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 73f526545..e38fd2983 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -40,6 +40,7 @@ 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 = 1; @@ -78,6 +79,7 @@ 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; @@ -90,6 +92,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; @@ -181,11 +184,12 @@ 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; } + 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 + // 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) {} @@ -211,7 +215,7 @@ internal LNX_Entity* lnx_entity_from_handle(OS_Handle handle, LNX_EntityKind type) { LNX_Entity* result = (LNX_Entity*)PtrFromInt(*handle.u64); - Assert(result->kind == type); + if (*handle.u64) { Assert(result->kind == type); } return result; } internal OS_Handle lnx_handle_from_entity(LNX_Entity* entity) @@ -221,6 +225,7 @@ internal OS_Handle lnx_handle_from_entity(LNX_Entity* entity) return result; } +// TODO: Marked as old / review internal String8 lnx_string_from_signal(int signum){ String8 result = str8_lit(""); @@ -353,6 +358,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(""); @@ -885,6 +891,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); @@ -896,6 +903,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; @@ -904,6 +912,7 @@ 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; @@ -925,6 +934,7 @@ lnx_thread_base(void *ptr){ return(0); } +// TODO: Marked as old / review internal void lnx_safe_call_sig_handler(int _){ LNX_SafeCallChain *chain = lnx_safe_call_chain; @@ -954,6 +964,7 @@ internal LNX_timespec lnx_now_system_timespec() //////////////////////////////// //~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) +// TODO: Marked as old / review internal void os_init(void) { @@ -998,21 +1009,25 @@ os_init(void) // 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); + 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 != NULL && x_string 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_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); - 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); + Assert(fd != -1); + 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); } @@ -1170,51 +1199,32 @@ os_alloc_ring_buffer(U64 size, U64 *actual_size_out) internal void os_free_ring_buffer(void *ring_buffer, U64 actual_size) { - munmap(ring_buffer, 2* actual_size); + 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 = 1; - local_persist String8 name = {0}; + String8 result = {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 = 0; - - // get name - B32 got_final_result = 0; - U8 *buffer = 0; - int size = 0; - for (S64 cap = 4096, r = 0; - r < 4; - cap *= 2, r += 1){ - buffer = push_array_no_zero(scratch.arena, U8, cap); - size = gethostname((char*)buffer, cap); - if (size < cap){ - got_final_result = 1; - break; - } - } + Temp scratch = scratch_begin(0, 0); - // 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; - } + // 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); - scratch_end(scratch); + 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 @@ -1276,6 +1286,7 @@ os_get_environment(void) 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; @@ -1296,6 +1307,8 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o 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){ @@ -1357,6 +1370,12 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o return(result); } +internal void +os_set_thread_name(String8 string) +{ + NotImplemented; +} + //////////////////////////////// //~ rjf: @os_hooks Process Control (Implemented Per-OS) @@ -1381,13 +1400,14 @@ os_file_open(OS_AccessFlags flags, String8 path) 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*/ + 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); - S32 fd = openat(AT_FDCWD, (char*)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 @@ -1423,12 +1443,14 @@ os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) S64 read_bytes = read(fd, out_data, read_amount); // Return 0 instead of -1 on error - return ClampBot(0, read_bytes); + return (read_bytes > 0 ? read_bytes : 0) ; } internal void os_file_write(OS_Handle file, Rng1U64 rng, void *data) { + // 0 file descriptor is stdin, bail + if (*file.u64 == 0) { return; } S32 fd = lnx_fd_from_handle(file); LNX_fstat file_info; fstat(fd, &file_info); @@ -1487,6 +1509,7 @@ os_id_from_file(OS_Handle file) return result; } +// TODO: Marked as old / review internal B32 os_delete_file_at_path(String8 path) { @@ -1528,6 +1551,8 @@ internal String8 os_full_path_from_path(Arena *arena, String8 path) { 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); @@ -1548,15 +1573,17 @@ os_file_path_exists(String8 path) //- rjf: file maps -/* Does not map a view of the file into memory until a view is opened */ +/* 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) { - // TODO: Implement access flags + 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; - OS_Handle result = {0}; *result.u64 = IntFromPtr(entity); return result; @@ -1589,6 +1616,8 @@ internal void * os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) { 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); @@ -1615,10 +1644,11 @@ os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) 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 NULL on error + if (address == (void*)-1) { result = NULL; } return result; } @@ -1686,6 +1716,7 @@ os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) 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); @@ -1727,8 +1758,8 @@ os_shared_memory_alloc(U64 size, String8 name) { OS_Handle result = {0}; 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); + // Create, fail if already open, mode: rw-rw---- + S32 fd = shm_open((char*)name.str, O_RDWR | O_CREAT | O_EXCL, 0660); Assert("Failed to alloc shared memory" && fd != -1); B32 use_huge = (size > os_large_page_size() && lnx_huge_page_enabled); @@ -2145,7 +2176,7 @@ os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) // 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); + sem_t* semaphore = sem_open((char*)name.str, O_CREAT | O_EXCL, 0660, initial_count); if (semaphore != SEM_FAILED) { LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_Semaphore); @@ -2214,6 +2245,7 @@ os_semaphore_drop(OS_Handle semaphore) //////////////////////////////// //~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) +// TODO: Marked as old / review internal OS_Handle os_library_open(String8 path) { @@ -2225,6 +2257,7 @@ os_library_open(String8 path) return lib; } +// TODO: Marked as old / review internal VoidProc * os_library_load_proc(OS_Handle lib, String8 name) { @@ -2246,6 +2279,7 @@ os_library_close(OS_Handle lib) //////////////////////////////// //~ 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}; diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index 5b796d701..83579db87 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -51,7 +51,8 @@ typedef struct dirent LNX_dir_entry; // -- Other -- typedef uuid_t LNX_uuid; -// -- Syncronization Primitives-- +// -- 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; diff --git a/src/tests/test_os_core.c b/src/tests/test_os_core.c new file mode 100644 index 000000000..13590f804 --- /dev/null +++ b/src/tests/test_os_core.c @@ -0,0 +1,202 @@ + +#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" + +static int passes = 0; +static int failed = 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; + 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__ ) + +void +SECTION( char* label ) +{ + printf( "\n\x1b[36;1m[%s]\x1b[0m\n", label ); +} + +int main() +{ + TCTX tctx_; + tctx_init_and_equip(&tctx_); + os_init(); test( 1, "os_init" ); + Arena* g_arena = arena_alloc(); + + B32 res = 0; + U64* g_buffer[4096]; + 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, 103, 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() ); + + 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 ); + // 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 = pthread_self(); + String8 thread_name = str8_lit( "test_thread_name" ); + MemoryZeroArray( g_buffer ); + // os_set_thread_name( thread_name ); + pthread_getname_np( thread, g_buffer, 4096 ); + res = MemoryCompare( thread_name.str, g_buffer, thread_name.size ); + test( res == 0, "os_set_thread_name" ); + + SECTION( "OS File Handling" ); + 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" ); + *filemap.u64 = 0; + + filebuf = os_file_map_view_open( filemap, 0x0, rng_1u64(0, 1024) ); + + // Show Passed/Failed + printf( "\n\x1b[32mPassed\x1b[39m: %d \n", passes ); + printf( "\x1b[31mFailed\x1b[39m: %d \n", failed ); + + os_exit_process( 0 ); test( 0, "os_exit_process" ); +} From 63e31479b9078f8b4ae1a9a569acf1827fb079ec Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sun, 25 Aug 2024 21:51:53 +0100 Subject: [PATCH 16/30] [Tests] Added: Some more transient tests for os_linux [OS Linux] Added: Rudimentary thread name setting Documentation: Added 'Here Be Dragons' warning Fixed: 'os_launch_process' Fixed: Some assorted fixes and documentation/note tweaks --- src/os/core/linux/os_core_linux.c | 127 ++++++++++----- src/os/core/linux/os_core_linux.h | 1 + src/tests/test_os_core.c | 253 ++++++++++++++++++++++++++++-- 3 files changed, 334 insertions(+), 47 deletions(-) diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index e38fd2983..4be07148a 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1181,6 +1181,8 @@ os_alloc_ring_buffer(U64 size, U64 *actual_size_out) 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); @@ -1370,10 +1372,17 @@ os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *o 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) { - NotImplemented; + LNX_thread self = pthread_self(); + prctl(PR_SET_NAME, string.str); } //////////////////////////////// @@ -1425,9 +1434,12 @@ os_file_close(OS_Handle file) 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 +/* 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) { @@ -1605,13 +1617,16 @@ os_file_map_close(OS_Handle map) 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] +/* 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 */ + 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) { @@ -1660,7 +1675,7 @@ os_file_map_view_close(OS_Handle map, void *ptr) 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); + msync(entity->map.data, entity->map.size, MS_SYNC); munmap(entity->map.data, entity->map.size); } @@ -1711,7 +1726,7 @@ os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) 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. + // 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; @@ -1735,6 +1750,7 @@ os_file_iter_end(OS_FileIter *iter) 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); } @@ -1758,8 +1774,11 @@ os_shared_memory_alloc(U64 size, String8 name) { OS_Handle result = {0}; LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_MemoryMap); - // Create, fail if already open, mode: rw-rw---- - S32 fd = shm_open((char*)name.str, O_RDWR | O_CREAT | O_EXCL, 0660); + // 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); @@ -1771,6 +1790,8 @@ os_shared_memory_alloc(U64 size, String8 name) 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; } @@ -1780,11 +1801,11 @@ os_shared_memory_open(String8 name) { OS_Handle result = {0}; LNX_Entity* entity = lnx_alloc_entity(LNX_EntityKind_MemoryMap); - LNX_fd fd = shm_open((char*)name.str, O_RDWR, 0x0 ); + LNX_fd fd = shm_open((char*)name.str, O_RDWR, 0660 ); Assert(fd != -1); entity->map.fd = fd; - entity->map.shm_name = name; + entity->map.shm_name = push_str8_copy( lnx_perm_arena, name ); *result.u64 = IntFromPtr(entity); return result; @@ -1814,6 +1835,7 @@ os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) 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; } @@ -1908,11 +1930,16 @@ with an execl variant. */ internal B32 os_launch_process(OS_LaunchOptions *options, OS_Handle *handle_out) { - B32 success = 0; 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 String8List cmdline_list = options->cmd_line; - char** cmdline = (char**)push_array(scratch.arena, char*, cmdline_list.node_count); + char** cmdline = (char**)push_array(scratch.arena, char*, cmdline_list.node_count + 4); String8Node* x_node = cmdline_list.first; for (int i=0; iinherit_env) { NotImplemented; }; - if (options->consoleless == 0) { NotImplemented; }; - U32 pid = 0; + if (options->consoleless == 1) { NotImplemented; }; + S32 pid = 0; pid = fork(); // Child if (pid) { - execvp((char*)options->path.str, cmdline); - success = 1; + *child_pid = pid; + execv(*cmdline, cmdline); + *success_shared = 0; exit(0); - } - // Parent - else { wait(NULL); } + } // Parent + else { waitpid(*child_pid, NULL, 0x0); } // TODO: Heck? Can't figure out how to wait on 'exec' + 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; +} + +internal void +os_process_release_handle(OS_Handle handle) +{ + NotImplemented; +} + //////////////////////////////// //~ 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 @@ -1962,6 +2006,15 @@ os_launch_thread(OS_ThreadFunctionType *func, void *ptr, void *params){ 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; +} + +// TODO: Marked as old / review internal void os_release_thread_handle(OS_Handle thread){ LNX_Entity *entity = (LNX_Entity*)PtrFromInt(thread.u64[0]); @@ -2012,7 +2065,9 @@ os_mutex_release(OS_Handle mutex){ internal void os_mutex_take_(OS_Handle mutex){ LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.u64[0]); - pthread_mutex_lock(&entity->mutex); + S32 error = 0; + while(error = pthread_mutex_lock(&entity->mutex)) { printf("error %d\n", error ); } + printf("error %d\n", error ); } internal void @@ -2047,7 +2102,7 @@ os_rw_mutex_alloc(void) internal void os_rw_mutex_release(OS_Handle rw_mutex) { - LNX_Entity* entity = lnx_entity_from_handle(rw_mutex, LNX_EntityKind_Mutex); + LNX_Entity* entity = lnx_entity_from_handle(rw_mutex, LNX_EntityKind_Rwlock); pthread_rwlock_destroy(&entity->rwlock); } @@ -2055,7 +2110,7 @@ internal void os_rw_mutex_take_r_(OS_Handle mutex) { // Is blocking varient - LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Mutex); + LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Rwlock); pthread_rwlock_rdlock(&entity->rwlock); } @@ -2064,22 +2119,22 @@ os_rw_mutex_drop_r_(OS_Handle mutex) { // 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_Mutex); + 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) { - LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Mutex); - pthread_rwlock_rdlock(&entity->rwlock); + 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) { - // NOTE: Should be the same thing - os_rw_mutex_drop_r_(mutex); + LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Rwlock); + pthread_rwlock_unlock(&(entity->rwlock)); } //- rjf: condition variables @@ -2175,8 +2230,8 @@ os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) OS_Handle result = {0}; // Create the named semaphore - // create | error if pre-existing , rw-rw---- - sem_t* semaphore = sem_open((char*)name.str, O_CREAT | O_EXCL, 0660, initial_count); + // 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); @@ -2199,7 +2254,7 @@ os_semaphore_open(String8 name) OS_Handle result = {0}; LNX_Entity* handle = lnx_alloc_entity(LNX_EntityKind_Semaphore); LNX_semaphore* semaphore; - semaphore = sem_open((char*)name.str, 0x0); + semaphore = sem_open((char*)name.str, 0600); handle->semaphore.handle = semaphore; Assert("Failed to open POSIX semaphore." || semaphore != SEM_FAILED); diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index 83579db87..45598e6fa 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/src/tests/test_os_core.c b/src/tests/test_os_core.c index 13590f804..36d114080 100644 --- a/src/tests/test_os_core.c +++ b/src/tests/test_os_core.c @@ -1,4 +1,5 @@ +// 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" @@ -26,8 +27,12 @@ #include "pdb/pdb.c" #include "pdb/pdb_stringize.c" -static int passes = 0; -static int failed = 0; +// Tests only + +internal int total = 0; +internal int passes = 0; +internal int failed = 0; +internal int manual_checks = 0; /* ASNI Color Codes @@ -42,6 +47,7 @@ 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; @@ -54,7 +60,8 @@ test( B32 passed, char* description ) } } -#define MANUAL_CHECKF(fmt, ...) printf( "\x1b[1mManual Check| \x1b[0m" fmt "\n" , ##__VA_ARGS__ ) +#define MANUAL_CHECKF(fmt, ...) printf( "\x1b[1mManual Check| \x1b[0m" fmt "\n" , ##__VA_ARGS__ ); \ + ++manual_checks void SECTION( char* label ) @@ -62,15 +69,85 @@ 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 ); +} + +/* 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 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; @@ -116,7 +193,7 @@ int main() "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, 103, 4000 ); + MemorySet( res_ring+2048, (char)seed, 4000 ); test( MemoryCompare( res_ring, res_ring+4096, 128) == 0, "os_alloc_ring_buffer write over boundary" ); @@ -125,6 +202,25 @@ int main() 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(); @@ -136,6 +232,7 @@ int main() if ( char_is_alpha( x_char ) || char_is_digit( x_char, 10 ) || x_char == '-') { continue; } hostname_good = 0; } test( hostname_good, "os_machine_name" ); + MANUAL_CHECKF( "os_machine_name: %s", os_machine_name().str ); MANUAL_CHECKF( "os_allocation_granularity: %lu", os_allocation_granularity() ); MANUAL_CHECKF( "os_logical_core_count: %lu", os_logical_core_count() ); @@ -156,21 +253,29 @@ int main() 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 = pthread_self(); + LNX_thread thread_self = pthread_self(); String8 thread_name = str8_lit( "test_thread_name" ); MemoryZeroArray( g_buffer ); - // os_set_thread_name( thread_name ); - pthread_getname_np( thread, g_buffer, 4096 ); - res = MemoryCompare( thread_name.str, g_buffer, thread_name.size ); + 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"); @@ -190,13 +295,139 @@ int main() 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" ); - *filemap.u64 = 0; 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" + "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" + "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 ); - // Show Passed/Failed - printf( "\n\x1b[32mPassed\x1b[39m: %d \n", passes ); + // 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" ); } From 142cc24d8fb30d83f44957994aaab95683606868 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Mon, 2 Sep 2024 11:48:27 +0100 Subject: [PATCH 17/30] Added: Basic rough out for freetype font provider and many random fixes to get linux build going [OS Linux] Added: Missing implementation for 'os_properties_from_file_path' Fixed: Accidentily leftover debugging code Added: os_condition_variable_broadcast_ Removed: Some leftover debug stuff Added: Misisng main function [Font Freetype] Added: Stubbed out files [Demon Linux] Added: Stubbed out files Added: Implimented some functionality [Build Script] Added: freetype2 directory to include path Added: freetype2 dynamic link [Documentation] Documentation: Wrote some notes with compiler flags [Random] Fixed: Clang complainting about unrelated B32 and U32 atomic types --- build.sh | 80 ++++--- documentation/build_system.md | 10 +- src/base/base_core.h | 3 +- src/ctrl/ctrl_core.c | 2 +- src/dasm_cache/dasm_cache.c | 2 +- src/demon/demon_core.h | 4 + src/demon/demon_inc.c | 6 +- src/demon/demon_inc.h | 6 +- src/demon/linux/demon_core_linux.c | 199 ++++++++++++++++++ src/demon/linux/demon_core_linux.h | 15 ++ src/demon/win32/demon_core_win32.c | 1 + src/file_stream/file_stream.c | 4 +- src/font_provider/font_provider_inc.c | 4 +- src/font_provider/font_provider_inc.h | 7 +- .../freetype/font_provider_freetype.c | 140 ++++++++++++ .../freetype/font_provider_freetype.h | 16 ++ src/geo_cache/geo_cache.c | 2 +- src/os/core/linux/os_core_linux.c | 34 ++- src/render/render_inc.h | 4 +- src/text_cache/text_cache.c | 2 +- src/texture_cache/texture_cache.c | 2 +- 21 files changed, 490 insertions(+), 53 deletions(-) create mode 100644 src/demon/linux/demon_core_linux.c create mode 100644 src/demon/linux/demon_core_linux.h create mode 100644 src/font_provider/freetype/font_provider_freetype.c create mode 100644 src/font_provider/freetype/font_provider_freetype.h diff --git a/build.sh b/build.sh index 9af815df6..ac96278d1 100755 --- a/build.sh +++ b/build.sh @@ -24,16 +24,35 @@ # - `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))" -echo "cd to '${self_directory}'" +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 +# RAD_META_COMPILE_ONLY=1 # --- Unpack Command Line Build Arguments ------------------------------------ # NOTE: With this setup you can use environment variables or arguments and @@ -41,31 +60,31 @@ fi 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= && echo "[debug mode]" ; fi -if [[ -n "${release}" ]] ; then debug= && echo "[release mode]" ; fi -if [[ -n "${msvc}" ]] ; then clang= && echo "[msvc compile]" ; fi -if [[ -n "${clang}" ]] ; then msvc= && echo "[clang compile]" ; 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 - echo "[default mode, assuming 'raddbg' build]" && raddbg=1 ; + rad_log "[default mode, assuming 'raddbg' build]" && raddbg=1 ; fi if [[ -n "${msvc}" ]] ; then clang=0 - echo "You're going to have to figure out something else if you want to use \ + 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" && - echo "[telemetry profiling enabled]" + rad_log "[telemetry profiling enabled]" [[ -n "${asan}" ]] && auto_compile_flags="${auto_compile_flags} -fsanitize=address" && - "echo [asan enabled]" + "rad_log [asan enabled]" # --- Compile/Link Line Definitions ------------------------------------------ cl_common="/I../src/ /I../local/ /nologo /FC /Z7" - clang_common="-I../src/ -I../local/ -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 -lpthread -ldl -lrt -latomic" - clang_errors="-Werror=atomic-memory-ordering" + 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 -lpthread -ldl -lrt -latomic -lm -lfreetype" + 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_errors} ${auto_compile_flags}" @@ -117,7 +136,7 @@ compile="${compile} -DBUILD_GIT_HASH=\"$(git describe --always --dirty)\"" # --- Build & Run Metaprogram ------------------------------------------------ if [[ -n "${no_meta}" ]] ; then - echo "[skipping metagen]" + rad_log "[skipping metagen]" else cd build ${compile_debug} "../src/metagen/metagen_main.c" ${compile_link} "${out}metagen.exe" || exit 1 @@ -130,6 +149,7 @@ fi function finish() { + rad_log "Some unexpected command failed unexpectedly. ${ansi_red}Line: $1 ${ansi_reset}" exit 1 } @@ -145,15 +165,13 @@ function build_single() function build_dll() { - didbuild=1 && - ${compile} "$1" ${compile_link} ${@:3:100} ${link_dll} "${out}$2" || finish + didbuild=1 + ${compile} "$1" ${compile_link} ${@:3:100} ${link_dll} "${out}$2" return $? } -if [[ -n ${RAD_BUILD_DEBUG} ]] ; then - echo "Compile Command: " - echo ${compile} -fi +rad_debug_log "Compile Command: " +rad_debug_log ${compile} cd build [[ -n "${raddbg}" ]] && build_single ../src/raddbg/raddbg_main.c raddbg.exe @@ -164,21 +182,22 @@ cd build [[ -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 -[[ -n "${mule_main}" ]] && - didbuild=1 && - rm -v vc*.pdb mule*.pdb && - ${compile_release} ${only_compile} ../src/mule/mule_inline.cpp && +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.obj mule_o2.obj ${compile_link} ${no_aslr} \ + "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 || exit 1 -[[ -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 || exit 1 +[[ -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 [[ "${mule_peb_trample}"=="1" ]] ; then +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" @@ -193,6 +212,9 @@ cd "${self_directory}" # --- Warn On No Builds ------------------------------------------------------ if [[ -z "${didbuild}" ]] ; then - echo "[WARNING] no valid build target specified; must use build target names as arguments to this script, like `build raddbg` or `build rdi_from_pdb`." + 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/build_system.md b/documentation/build_system.md index 3a48b5b5c..243103188 100644 --- a/documentation/build_system.md +++ b/documentation/build_system.md @@ -20,8 +20,14 @@ this can be turned into a `.desktop` file. ## Compiler Flags * -lpthread | POSIX threading library -* -dl | dynamic linking library -* -rt | realtime time library for clock functionality +* -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. ## 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. diff --git a/src/base/base_core.h b/src/base/base_core.h index 6a41c238e..d745e53e0 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -192,7 +192,7 @@ ins_atomic_u64_eval_cond_assign__impl(volatile U64* x, U64 k, U64 c) 0, __ATOMIC_ACQ_REL, __ATOMIC_RELEASE ); } -U64 +U32 ins_atomic_u32_eval_cond_assign__impl(volatile U32* x, U32 k, U32 c) { U32 _temp_expected = k; @@ -200,6 +200,7 @@ ins_atomic_u32_eval_cond_assign__impl(volatile U32* x, U32 k, U32 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 ) 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..d8ead91e9 --- /dev/null +++ b/src/demon/linux/demon_core_linux.c @@ -0,0 +1,199 @@ +// 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) +{ + // Boolean return, just says the context is valid + DMN_CtrlCtx* ctx = (DMN_CtrlCtx* )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; +} + +internal B32 +dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) +{ + NotImplemented; +} + +internal B32 +dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code) +{ + NotImplemented; +} + +internal B32 +dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process) +{ + NotImplemented; +} + +internal DMN_EventList +dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) +{ + NotImplemented; +} + +//////////////////////////////// +//~ @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) +{ + NotImplemented; +} +internal void +dmn_access_close(void) +{ + NotImplemented; +} + +//- processes +internal U64 +dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) +{ + NotImplemented; +} + +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; +} + +internal B32 +dmn_process_write(DMN_Handle process, Rng1U64 range, void *src) +{ + NotImplemented; +} + +//- threads +internal Architecture dmn_arch_from_thread(DMN_Handle handle) +{ + NotImplemented; +} +internal U64 dmn_stack_base_vaddr_from_thread(DMN_Handle handle) +{ + NotImplemented; +} +internal U64 dmn_tls_root_vaddr_from_thread(DMN_Handle handle) +{ + NotImplemented; +} +internal B32 +dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block) +{ + NotImplemented; +} + +internal B32 +dmn_thread_write_reg_block(DMN_Handle handle, void *reg_block) +{ + NotImplemented; +} + +//- 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; +} + +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..e40dcb67e --- /dev/null +++ b/src/demon/linux/demon_core_linux.h @@ -0,0 +1,15 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +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; 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/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..230219fef --- /dev/null +++ b/src/font_provider/freetype/font_provider_freetype.c @@ -0,0 +1,140 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal FT_Library freetype_instance = NULL; +internal Arena* freetype_arena = NULL; + +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; +} + +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; + S32 error = FT_New_Face( freetype_instance, (char*)path.str, face_index, &face); + Assert(error == 0); + + // TODO: DELETE + U32 test_index = FT_Get_Char_Index(face, (FT_ULong)'A'); + B32 error_sizing = FT_Set_Char_Size( face, 50 * 64, 0, 100, 0 ); + 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; + + // 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); + Assert(error == 0); + +// TODO: DELETE + U32 test_index = FT_Get_Char_Index(face, (FT_ULong)'A'); + B32 error_attach = FT_Attach_File(face, (char*)data_ptr->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 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); + + // 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; + Temp scratch = scratch_begin(0, 0); + FreeType_FontFace face = freetype_face_from_handle(font); + U32* charmap_indices = push_array(scratch.arena, U32, 4+ string.size); + U32 errors = 0; + U8 x_char = 0; + U32 i = 0; + for (; i < string.size; ++i) + { + x_char = string.str[i]; + charmap_indices[i] = FT_Get_Char_Index(face, (FT_ULong)x_char); + + // Undefined character code or NULL + if (charmap_indices == 0) { ++errors; } + } + scratch_end(scratch); +} 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..233aef3e9 --- /dev/null +++ b/src/font_provider/freetype/font_provider_freetype.h @@ -0,0 +1,16 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +// Breaks part of freetype headers +#undef internal +#include +#include +// Restore internal macro +#define internal static + +// 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); 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/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 4be07148a..7f77c59e9 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1461,8 +1461,8 @@ os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) internal void os_file_write(OS_Handle file, Rng1U64 rng, void *data) { - // 0 file descriptor is stdin, bail - if (*file.u64 == 0) { return; } + // Zero Valid Argument + if (*file.u64 == 0 || data == NULL) { return; } S32 fd = lnx_fd_from_handle(file); LNX_fstat file_info; fstat(fd, &file_info); @@ -1504,6 +1504,19 @@ os_properties_from_file(OS_Handle file) 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) { @@ -2066,8 +2079,7 @@ internal void os_mutex_take_(OS_Handle mutex){ LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.u64[0]); S32 error = 0; - while(error = pthread_mutex_lock(&entity->mutex)) { printf("error %d\n", error ); } - printf("error %d\n", error ); + while(error = pthread_mutex_lock(&entity->mutex)) { } } internal void @@ -2211,15 +2223,17 @@ os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) } internal void -os_condition_variable_signal_(OS_Handle cv){ +os_condition_variable_signal_(OS_Handle cv) +{ LNX_Entity *entity = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); pthread_cond_signal(&entity->cond); } internal void -os_condition_variable_broadcast_(OS_Handle cv){ - NotImplemented; +os_condition_variable_broadcast_(OS_Handle cv) +{ LNX_Entity *entity = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); + pthread_cond_broadcast(&entity->cond); } //- rjf: cross-process semaphores @@ -2378,3 +2392,9 @@ os_make_guid(void) return result; } + +int +main(int argc, char** argv) +{ + main_thread_base_entry_point(entry_point, argv, (U64)argc); +} diff --git a/src/render/render_inc.h b/src/render/render_inc.h index 95ab1a2fb..37aff7216 100644 --- a/src/render/render_inc.h +++ b/src/render/render_inc.h @@ -14,7 +14,9 @@ //~ rjf: Decide On Backend #if !defined(R_BACKEND) && OS_WINDOWS -# define R_BACKEND R_BACKEND_D3D11 + #define R_BACKEND R_BACKEND_D3D11 +#else + #define R_BACKEND R_BACKEND_STUB #endif //////////////////////////////// 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; } } From 76fe659087bda22b1a80336a4362cfe61d21d1d3 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Tue, 19 Nov 2024 00:02:32 +0000 Subject: [PATCH 18/30] Bump: Save WIP freetype stuff Added: stb_image (could be temporary, not important right now) [OS Core] Fixed: Missing include guards [Build Script] Fixed: Erroring on a logging command Added: Some logging to the build setup --- build.sh | 10 +- src/demon/linux/demon_core_linux.h | 5 + src/demon/linux/demon_os_linux.c | 57 + .../freetype/font_provider_freetype.c | 273 ++- .../freetype/font_provider_freetype.h | 39 +- src/third_party/stb/stb_image_write.h | 1724 +++++++++++++++++ 6 files changed, 2090 insertions(+), 18 deletions(-) create mode 100644 src/third_party/stb/stb_image_write.h diff --git a/build.sh b/build.sh index ac96278d1..b7f696952 100755 --- a/build.sh +++ b/build.sh @@ -79,7 +79,7 @@ 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]" + rad_log "[asan enabled]" # --- Compile/Link Line Definitions ------------------------------------------ cl_common="/I../src/ /I../local/ /nologo /FC /Z7" @@ -158,13 +158,17 @@ function finish() # @param $@ - rest is any arguments provided function build_single() { - didbuild=1 && - ${compile} "$1" ${@:3:100} ${compile_link} "${out}$2" || finish + local binary=$2 + rad_log "Building '${binary}'" + didbuild=1 + ${compile} "$1" ${@:3:100} ${compile_link} "${out}$2" || finish return $? } function build_dll() { + local binary=$2 + rad_log "Building '${binary}'" didbuild=1 ${compile} "$1" ${compile_link} ${@:3:100} ${link_dll} "${out}$2" return $? diff --git a/src/demon/linux/demon_core_linux.h b/src/demon/linux/demon_core_linux.h index e40dcb67e..00a9a44b6 100644 --- a/src/demon/linux/demon_core_linux.h +++ b/src/demon/linux/demon_core_linux.h @@ -1,6 +1,9 @@ // 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 + typedef struct DMN_LNX_Shared DMN_LNX_Shared; struct DMN_LNX_Shared { @@ -13,3 +16,5 @@ struct DMN_LNX_Shared global Arena* dmn_lnx_arena = NULL; global DMN_LNX_Shared* dmn_lnx = NULL; + +#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/font_provider/freetype/font_provider_freetype.c b/src/font_provider/freetype/font_provider_freetype.c index 230219fef..c9944137e 100644 --- a/src/font_provider/freetype/font_provider_freetype.c +++ b/src/font_provider/freetype/font_provider_freetype.c @@ -1,6 +1,9 @@ // Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "third_party/stb/stb_image_write.h" + internal FT_Library freetype_instance = NULL; internal Arena* freetype_arena = NULL; @@ -33,12 +36,22 @@ fp_font_open(String8 path) 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_sizing = FT_Set_Char_Size( face, 50 * 64, 0, 100, 0 ); 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); @@ -55,6 +68,14 @@ fp_font_open_from_static_data_string(String8 *data_ptr) FT_Face face = {0}; FT_Open_Args args = {0}; S64 face_index = 0x0; + FT_Size_RequestRec sizing_request = {0}; + +// Pt Fractional Scaling + sizing_request.width = 300*64; + sizing_request.height = 300*64; + // DPI + sizing_request.horiResolution = 30; + sizing_request.vertResolution = 30; // Set memory file bit args.flags = FT_OPEN_MEMORY; @@ -63,13 +84,11 @@ fp_font_open_from_static_data_string(String8 *data_ptr) args.driver = NULL; // Try every font driver S32 error = FT_Open_Face( freetype_instance, &args, face_index, &face); - Assert(error == 0); + // Setup font scaling + B32 error_sizing = FT_Request_Size(face, &sizing_request); -// TODO: DELETE - U32 test_index = FT_Get_Char_Index(face, (FT_ULong)'A'); - B32 error_attach = FT_Attach_File(face, (char*)data_ptr->str); - B32 error_char = FT_Load_Char(face, (FT_ULong)'A', 0x0); - B32 error_glyph = FT_Load_Glyph(face, test_index, FT_LOAD_DEFAULT); + Assert(error == 0); + Assert(error_sizing == 0); result = freetype_handle_from_face(face); return result; @@ -89,8 +108,9 @@ fp_metrics_from_font(FP_Handle font) 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.ascent = face->ascender; // result.descender = face->descender; // result.line_gap = 0; // result.capital_height = face->ascender; // NOTE: It's the same thing. @@ -121,20 +141,245 @@ fp_raster(Arena *arena, FP_RasterMode mode, String8 string) { - NotImplemented; - Temp scratch = scratch_begin(0, 0); + /* NotImplemented; */ + /* NOTE(mallchad): I am assuming this function will only ever run for 1 glyph + at a time because that is the API usage. 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 face; } + + Temp scratch = scratch_begin(0, 0); U32* charmap_indices = push_array(scratch.arena, U32, 4+ string.size); - U32 errors = 0; + F32 win32_magic_dimensions = (96.f/72.f); + // 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; + U32 glyph_height = 60; + U32 glyph_width = 100; + U32 magic_dpi = 64; + + /* NOTE(mallchad): 'size' is the actual internal glyph "scale" not char count + Convert magic fixed point coordinate system to internal floating point representation */ + F32 glyph_advance_width = (face->max_advance_width * (96.f/72.f) * size) / face->units_per_EM; + F32 glyph_advance_height = (face->max_advance_height * (96.f/72.f) * size) / face->units_per_EM; + result.atlas_dim.x = glyph_advance_width; + result.atlas_dim.y = glyph_advance_height; + result.advance = glyph_advance_width; + result.atlas = push_array(arena, U8, result.atlas_dim.x * result.atlas_dim.y); + U8 x_char = 0; + U32 x_charmap = 0; U32 i = 0; + string.size = 1; for (; i < string.size; ++i) { x_char = string.str[i]; - charmap_indices[i] = FT_Get_Char_Index(face, (FT_ULong)x_char); + 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); + + err_char += (errors_glyph > 0); + err_glyph += (errors_glyph > 0); + err_render += (errors_render > 0); + } + Assert(!errors_glyph); + Assert(!errors_glyph); + Assert(!errors_render); + + static U32 null_errors = 0; + // WTF. Even if no errors came back??? + if (face->glyph->bitmap.buffer != NULL) + { + + // Debug Stuff + String8 debug_name = str8_lit("debug/test.bmp"); + freetype_write_bmp_monochrome_file(debug_name, + face->glyph->bitmap.buffer, + face->glyph->bitmap.pitch, + face->glyph->bitmap.rows); + scratch_end(scratch); + } else { ++null_errors; } + 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 = 4* width*height; + U8* buffer = push_array(scratch.arena, U8, greedy_filesize); + FreeType_BitmapHeader* header = (FreeType_BitmapHeader*)buffer; + U32 pixbuf_offset = (sizeof(FreeType_BitmapHeader)*2); + 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; - // Undefined character code or NULL - if (charmap_indices == 0) { ++errors; } + // 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 = 4* width*height; + U8* filebuf = push_array(scratch.arena, U8, greedy_filesize); + FreeType_BitmapHeader* header = (FreeType_BitmapHeader*)filebuf; + U32 pixbuf_offset = (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); + // 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_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 index 233aef3e9..fe258de8a 100644 --- a/src/font_provider/freetype/font_provider_freetype.h +++ b/src/font_provider/freetype/font_provider_freetype.h @@ -1,16 +1,53 @@ // 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 +#include +#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); + +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); + +#endif // FONT_PROVIDER_FREETYPE_H 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. +------------------------------------------------------------------------------ +*/ From 06e6dc28dcd11c7404785dddc28e736c18f36270 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Tue, 19 Nov 2024 00:13:16 +0000 Subject: [PATCH 19/30] Bump: Saved some WIP work Fixed: Added a zero-is-valid case for 'os_file_write' --- src/os/core/linux/os_core_linux.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 7f77c59e9..46d134c07 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1030,7 +1030,6 @@ os_init(void) } } } - scratch_end(scratch); lnx_page_size = (U64)getpagesize(); @@ -1052,6 +1051,7 @@ os_init(void) umask is *also* not thread-safe. Fun! */ umask(00); + scratch_end(scratch); } //////////////////////////////// @@ -1130,13 +1130,15 @@ os_set_large_pages(B32 flag) internal B32 os_large_pages_enabled(void) { + NotImplemented; // *facepalm* this is not done // 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 U8 buffer[5000]; - LNX_fd fd = open("/proc/meminfo", O_RDONLY); + LNX_fd fd = open("/proc/sys/vm/nr_hugepages", O_RDONLY); String8 meminfo = {0}; + AssertAlways(fd >= 0); meminfo.str = buffer; - meminfo.size = read(fd, buffer, 5000); + // meminfo.size = read(fd, buffer, 5000); Rng1U64 match = str8_match_substr( meminfo, str8_cstring("Huge"), 0x0 ); @@ -1462,7 +1464,7 @@ internal void os_file_write(OS_Handle file, Rng1U64 rng, void *data) { // Zero Valid Argument - if (*file.u64 == 0 || data == NULL) { return; } + 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); From a671e799a4d97598999ad5ea917ed2020e3ae12b Mon Sep 17 00:00:00 2001 From: Mallchad Date: Wed, 18 Dec 2024 00:02:32 +0000 Subject: [PATCH 20/30] Added: Main bulk out of freetype code, difficult to test so moving on for now Maintenance: Tweaked and added some commentary --- build.sh | 2 + .../freetype/font_provider_freetype.c | 73 +++++++++++++++---- .../freetype/font_provider_freetype.h | 7 ++ 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/build.sh b/build.sh index b7f696952..09570b465 100755 --- a/build.sh +++ b/build.sh @@ -1,5 +1,7 @@ #!/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 diff --git a/src/font_provider/freetype/font_provider_freetype.c b/src/font_provider/freetype/font_provider_freetype.c index c9944137e..5f944b66b 100644 --- a/src/font_provider/freetype/font_provider_freetype.c +++ b/src/font_provider/freetype/font_provider_freetype.c @@ -143,12 +143,12 @@ fp_raster(Arena *arena, { /* NotImplemented; */ /* NOTE(mallchad): I am assuming this function will only ever run for 1 glyph - at a time because that is the API usage. If anything else occurs the code - may not behave properly */ + 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 face; } + if (face == 0) { return result; } Temp scratch = scratch_begin(0, 0); U32* charmap_indices = push_array(scratch.arena, U32, 4+ string.size); @@ -182,21 +182,20 @@ fp_raster(Arena *arena, U32 glyph_height = 60; U32 glyph_width = 100; U32 magic_dpi = 64; - + U32 total_advance = 0; + U32 total_height = 0; + Vec2S16 atlas_dim = {0}; + U32 glyph_count = string.size; /* NOTE(mallchad): 'size' is the actual internal glyph "scale" not char count Convert magic fixed point coordinate system to internal floating point representation */ F32 glyph_advance_width = (face->max_advance_width * (96.f/72.f) * size) / face->units_per_EM; F32 glyph_advance_height = (face->max_advance_height * (96.f/72.f) * size) / face->units_per_EM; - result.atlas_dim.x = glyph_advance_width; - result.atlas_dim.y = glyph_advance_height; - result.advance = glyph_advance_width; - result.atlas = push_array(arena, U8, result.atlas_dim.x * result.atlas_dim.y); + U8* atlas = push_array_no_zero( scratch.arena, U8, 5* glyph_advance_width * glyph_advance_height ); U8 x_char = 0; U32 x_charmap = 0; U32 i = 0; - string.size = 1; - for (; i < string.size; ++i) + for (; i < glyph_count; ++i) { x_char = string.str[i]; x_charmap = FT_Get_Char_Index(face, (FT_ULong)x_char); @@ -210,7 +209,19 @@ fp_raster(Arena *arena, 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, total_height), + face->glyph->bitmap.pitch, + glyph_advance_width); + + // Section compied from dwrite + total_advance += glyph_advance_width; + total_height += glyph_advance_height; + 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); @@ -218,20 +229,32 @@ fp_raster(Arena *arena, 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 = total_height; + result.advance = glyph_advance_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??? + /* WTF. Even if no errors came back??? + leaving here as a guard against 3rd party NULL shenanegans */ if (face->glyph->bitmap.buffer != NULL) { - // Debug Stuff String8 debug_name = str8_lit("debug/test.bmp"); freetype_write_bmp_monochrome_file(debug_name, face->glyph->bitmap.buffer, face->glyph->bitmap.pitch, face->glyph->bitmap.rows); - scratch_end(scratch); + + } else { ++null_errors; } + scratch_end(scratch); + return result; } @@ -367,6 +390,28 @@ freetype_write_bmp_monochrome_file(String8 name, U8* data, U32 width, U32 height 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) { diff --git a/src/font_provider/freetype/font_provider_freetype.h b/src/font_provider/freetype/font_provider_freetype.h index fe258de8a..84144c1ca 100644 --- a/src/font_provider/freetype/font_provider_freetype.h +++ b/src/font_provider/freetype/font_provider_freetype.h @@ -49,5 +49,12 @@ 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 From 92a7f31f043c39ec29e5293a5525a27401b1134a Mon Sep 17 00:00:00 2001 From: Mallchad Date: Fri, 20 Dec 2024 13:48:02 +0000 Subject: [PATCH 21/30] [OS Core] Fixed: Linux implementation for 'os_large_pages_enabled' [Tests] Fixed: Buffer being of type pointer instead of U64 for some reason Fixed: Incorrect naming on time check [Documentation] Added: Some comments about codegen bugs --- documentation/bugs.md | 7 ++++ .../freetype/font_provider_freetype.c | 2 +- src/os/core/linux/os_core_linux.c | 40 +++++++++++++------ src/os/core/linux/os_core_linux.h | 2 + src/tests/test_os_core.c | 14 +++++-- 5 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 documentation/bugs.md 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/src/font_provider/freetype/font_provider_freetype.c b/src/font_provider/freetype/font_provider_freetype.c index 5f944b66b..de60100d6 100644 --- a/src/font_provider/freetype/font_provider_freetype.c +++ b/src/font_provider/freetype/font_provider_freetype.c @@ -241,7 +241,7 @@ fp_raster(Arena *arena, static U32 null_errors = 0; /* WTF. Even if no errors came back??? - leaving here as a guard against 3rd party NULL shenanegans */ + leaving here as a guard against 3rd party NULL shenanigans */ if (face->glyph->bitmap.buffer != NULL) { // Debug Stuff diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 46d134c07..bf7f234ce 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -18,6 +18,8 @@ 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}; @@ -1130,20 +1132,30 @@ os_set_large_pages(B32 flag) internal B32 os_large_pages_enabled(void) { - NotImplemented; // *facepalm* this is not done - // 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 - U8 buffer[5000]; + /* 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); - String8 meminfo = {0}; - AssertAlways(fd >= 0); - meminfo.str = buffer; - // meminfo.size = read(fd, buffer, 5000); + // 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); - Rng1U64 match = str8_match_substr( meminfo, str8_cstring("Huge"), 0x0 ); + U64 hugepage_count = 0; + hugepage_count = u64_from_str8(data, 10); + B32 enable_largepages = (hugepage_count > lnx_hugepage_count_min); - // return (match.max > 0); - return 0; + return enable_largepages; } /* NOTE: The size seems to be consistent across Linux systems, it's configurable @@ -2394,7 +2406,11 @@ os_make_guid(void) return result; } - +int +lnx_entry_point(int argc, char** argv) +{ + return 1; +} int main(int argc, char** argv) { diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index 45598e6fa..742deee28 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -130,6 +130,8 @@ struct LNX_file_iter { 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*/ diff --git a/src/tests/test_os_core.c b/src/tests/test_os_core.c index 36d114080..58e4710b8 100644 --- a/src/tests/test_os_core.c +++ b/src/tests/test_os_core.c @@ -120,12 +120,17 @@ rwlock_routine( void* restrict payload ) 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 main() +int test_main() { srand( time(NULL) ); U64 seed = rand(); @@ -145,7 +150,7 @@ int main() AssertAlways( g_shared != NULL ); B32 res = 0; - U64* g_buffer[4096]; + U64 g_buffer[4096]; Share* g_share_data = (Share*)g_shared; g_shared += sizeof(Share); SECTION( "OS Memory Management" ); @@ -335,7 +340,7 @@ int main() time_universal.micro_sec ); MANUAL_CHECKF( "os_universal_time_from_local_time: year: %d \n" "month: %d \n" - "week: %d \n" + "day of week: %d \n" "day: %d \n" "time: %d:%d:%d:%d:%d", time_to_universal.year, @@ -349,7 +354,7 @@ int main() time_to_universal.micro_sec ); MANUAL_CHECKF( "os_local_time_from_universal_time: year: %d \n" "month: %d \n" - "week: %d \n" + "day of week: %d \n" "day: %d \n" "time: %d:%d:%d:%d:%d", time_to_local.year, @@ -430,4 +435,5 @@ int main() printf( "\x1b[1mTotal Tests Run: %d \n", total ); os_exit_process( 0 ); test( 0, "os_exit_process" ); + return 1; } From 66ce417381bd162e9fd7f6b55f8b4f3126caa1e8 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sat, 21 Dec 2024 20:30:41 +0000 Subject: [PATCH 22/30] [Demon Core] Added: Some function block-out for demon control --- src/demon/linux/demon_core_linux.c | 35 ++++++++++++++++++++++++++---- src/demon/linux/demon_core_linux.h | 3 +++ src/os/core/linux/os_core_linux.c | 5 ++++- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/demon/linux/demon_core_linux.c b/src/demon/linux/demon_core_linux.c index d8ead91e9..56d160699 100644 --- a/src/demon/linux/demon_core_linux.c +++ b/src/demon/linux/demon_core_linux.c @@ -21,8 +21,9 @@ dmn_init(void) internal DMN_CtrlCtx *dmn_ctrl_begin(void) { - // Boolean return, just says the context is valid + // NOTE: Boolean return, just says the context is valid DMN_CtrlCtx* ctx = (DMN_CtrlCtx* )1; + dmn_lnx_ctrl_thread = 1; return ctx; } @@ -42,12 +43,31 @@ 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) { - NotImplemented; + S32 error = ptrace(PTRACE_ATTACH, pid); + return (error == 0); } internal B32 @@ -100,15 +120,22 @@ dmn_reg_gen(void) } //- non-blocking-control-thread access barriers + internal B32 dmn_access_open(void) { - NotImplemented; + // 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) { - NotImplemented; + if (dmn_lnx_ctrl_thread == 0) + { os_mutex_drop(dmn_lnx->mutex_access); } } //- processes diff --git a/src/demon/linux/demon_core_linux.h b/src/demon/linux/demon_core_linux.h index 00a9a44b6..926e44e7e 100644 --- a/src/demon/linux/demon_core_linux.h +++ b/src/demon/linux/demon_core_linux.h @@ -4,6 +4,8 @@ #ifndef DEMON_CORE_LINUX_H #define DEMON_CORE_LINUX_H +#include + typedef struct DMN_LNX_Shared DMN_LNX_Shared; struct DMN_LNX_Shared { @@ -16,5 +18,6 @@ struct DMN_LNX_Shared 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/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index bf7f234ce..4a4c17918 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1965,6 +1965,7 @@ os_launch_process(OS_LaunchOptions *options, OS_Handle *handle_out) *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; @@ -1975,18 +1976,20 @@ os_launch_process(OS_LaunchOptions *options, OS_Handle *handle_out) } if (options->inherit_env) { NotImplemented; }; - if (options->consoleless == 1) { 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); From 915812983037612ec8eb684ceef7814c0f2d5385 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Mon, 23 Dec 2024 17:31:16 +0000 Subject: [PATCH 23/30] [OS Graphics] Added: Bulk out basics for X11 and EGL Windowing [Build Script] Fixed: Debug printing of compile command not appearing if metagen fails [Base Core] Added: Definition for Trap() as SIGTRAP for clang / __builtin_debugtrap This should allow for continuing from asserts and breakpoints --- build.sh | 15 +- src/base/base_core.h | 12 +- src/os/gfx/linux/os_gfx_linux.c | 349 ++++++++++++++++++++++++++++++ src/os/gfx/linux/os_gfx_linux.h | 21 ++ src/os/gfx/linux/os_gfx_x11.c | 53 +++++ src/os/os_inc.c | 4 + src/os/os_inc.h | 3 + src/render/opengl/render_opengl.c | 39 ++++ 8 files changed, 486 insertions(+), 10 deletions(-) create mode 100644 src/os/gfx/linux/os_gfx_linux.c create mode 100644 src/os/gfx/linux/os_gfx_linux.h create mode 100644 src/os/gfx/linux/os_gfx_x11.c create mode 100644 src/render/opengl/render_opengl.c diff --git a/build.sh b/build.sh index 09570b465..038a3fa69 100755 --- a/build.sh +++ b/build.sh @@ -40,7 +40,7 @@ function rad_log () function rad_debug_log () { - if [[ -n ${RAD_DEBUG} ]] ; then + if [[ -n "${RAD_DEBUG}" ]] ; then # -e enable escape sequences rad_log "${@}" fi @@ -85,12 +85,13 @@ set auto_compile_flags= # --- 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 -lpthread -ldl -lrt -latomic -lm -lfreetype" + 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" + clang_dynamic="-lpthread -ldl -lrt -latomic -lm -lfreetype -lEGL -lX11 -lGL" 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_errors} ${auto_compile_flags}" -clang_release="clang -g -O2 -DBUILD_DEBUG=0 ${clang_common} ${clang_errors} ${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=lld" @@ -136,6 +137,9 @@ cd "${self_directory}" # 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]" @@ -176,9 +180,6 @@ function build_dll() return $? } -rad_debug_log "Compile Command: " -rad_debug_log ${compile} - 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 diff --git a/src/base/base_core.h b/src/base/base_core.h index d745e53e0..f7fa66df8 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 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..061e04278 --- /dev/null +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -0,0 +1,349 @@ + + +global B32 gfx_lnx_wayland_preferred = 0; +global B32 gfx_lnx_wayland_disabled = 1; +global S32 gfx_egl_version_major = 0; +global S32 gfx_egl_version_minor = 0; +global U8* gfx_default_window_name = (U8*)"raddebugger"; +GFX_Context 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_CLIENT_VERSION, + 2, + 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. + */ + +// Stub +B32 +wayland_graphical_init(GFX_Context* out) +{ NotImplemented; } + +internal void +os_graphical_init(void) +{ + gfx_context.window_name = gfx_default_window_name; + gfx_context.default_window_size = vec_2s32(500, 500); + gfx_context.window_size = gfx_context.default_window_size; + gfx_context.default_window_pos = vec_2s32(500, 500); + gfx_context.window_pos = gfx_context.default_window_pos; + + 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); + gfx_egl_draw_surface = eglCreateWindowSurface(gfx_egl_display, gfx_egl_config_available[0], + (EGLNativeWindowType)gfx_context.native_window, NULL); + gfx_egl_read_surface = eglCreateWindowSurface(gfx_egl_display, gfx_egl_config_available[0], + (EGLNativeWindowType)gfx_context.native_window, NULL); + 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); + eglMakeCurrent(gfx_egl_display, gfx_egl_draw_surface, gfx_egl_draw_surface, gfx_egl_context); + + Vec4F32 dark_magenta = vec_4f32( 0.2f, 0.f, 0.2f, 1.0f ); +while (1){ + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); + glClearColor(dark_magenta.x, dark_magenta.y, dark_magenta.z, dark_magenta.w); + S32 swap_result =eglSwapBuffers(gfx_egl_display, gfx_egl_draw_surface); + S32 err = eglGetError(); + Trap(); + static int i = 0; + ++i; +} +} + + +//////////////////////////////// +//~ 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) +{ + OS_Handle result = {0}; + return result; +NotImplemented;} +internal void +os_window_close(OS_Handle window) +{ + +NotImplemented;} + +internal void +os_window_first_paint(OS_Handle window) +{ + +NotImplemented;} +internal void +os_window_equip_repaint(OS_Handle window, OS_WindowRepaintFunctionType *repaint, void *user_data) +{ + +NotImplemented;} + +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) +{ + +NotImplemented;} + +internal void +os_window_set_maximized(OS_Handle window, B32 maximized) +{ + +NotImplemented;} + +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) +{ + +NotImplemented;} + +internal void +os_window_clear_custom_border_data(OS_Handle handle) +{ + +NotImplemented;} + +internal void +os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) +{ + +NotImplemented;} + +internal void +os_window_push_custom_edges(OS_Handle handle, F32 thickness) +{ + +NotImplemented;} + +internal void +os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) +{ + +NotImplemented;} + +internal Rng2F32 +os_rect_from_window(OS_Handle window) +{ + +NotImplemented;} + +internal Rng2F32 +os_client_rect_from_window(OS_Handle window) +{ + +NotImplemented;} + +internal F32 +os_dpi_from_window(OS_Handle window) +{ + return 0.f; +NotImplemented;} + + +//////////////////////////////// +//~ rjf: @os_hooks Monitors (Implemented Per-OS) + +internal OS_HandleArray +os_push_monitors_array(Arena *arena) +{ +NotImplemented;} + +internal OS_Handle +os_primary_monitor(void) +{ + OS_Handle result = {0}; + return result; + NotImplemented;} + +internal OS_Handle +os_monitor_from_window(OS_Handle window) +{ + NotImplemented; + OS_Handle result = {0}; + return result; +} + +internal String8 +os_name_from_monitor(Arena *arena, OS_Handle monitor) +{ + NotImplemented; +} + +internal Vec2F32 +os_dim_from_monitor(OS_Handle monitor) +{ + NotImplemented; +} + + +//////////////////////////////// +//~ rjf: @os_hooks Events (Implemented Per-OS) + +internal void +os_send_wakeup_event(void) +{ +NotImplemented;} + +internal OS_EventList +os_get_events(Arena *arena, B32 wait) +{ +NotImplemented;} + +internal OS_EventFlags +os_get_event_flags(void) +{ +NotImplemented;} + +internal B32 +os_key_is_down(OS_Key key) +{ +NotImplemented;} + +internal Vec2F32 +os_mouse_from_window(OS_Handle window) +{ +NotImplemented;} + + +//////////////////////////////// +//~ 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) +{ + return 0.f; +NotImplemented;} + + +//////////////////////////////// +//~ 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..844685c5a --- /dev/null +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -0,0 +1,21 @@ + +#ifndef GFX_LINUX_H +#define GFX_LINUX_H + +#include + +typedef struct GFX_Context GFX_Context; +struct GFX_Context +{ + + U8* window_name; + Vec2S32 default_window_size; + Vec2S32 window_size; + Vec2S32 default_window_pos; + Vec2S32 window_pos; + + EGLNativeDisplayType native_server; + EGLNativeWindowType native_window; +}; + +#endif /* GFX_LINUX_H */ 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..bf93c9445 --- /dev/null +++ b/src/os/gfx/linux/os_gfx_x11.c @@ -0,0 +1,53 @@ + +#include +#include +#include +#include + +#include + +typedef Display X11_Display; +typedef Window X11_Window; +// Forward Declares + +global X11_Display* x11_server = NULL; +global X11_Window x11_window = 0; + +B32 +x11_graphical_init(GFX_Context* out) +{ + // Initialize X11 + x11_server = XOpenDisplay(NULL); + Assert(x11_server != NULL); + if (x11_server == NULL) { return 0; } + S32 default_screen = XDefaultScreen(x11_server); + S32 default_depth = DefaultDepth(x11_server, default_screen); + XVisualInfo visual_info = {0}; + XMatchVisualInfo(x11_server, default_screen, default_depth, TrueColor, &visual_info); + X11_Window root_window = RootWindow(x11_server, default_screen); + XSetWindowAttributes window_attributes = {0}; + window_attributes.colormap = XCreateColormap( x11_server, + root_window, + visual_info.visual, AllocNone ); + window_attributes.background_pixmap = None ; + window_attributes.border_pixel = 0; + window_attributes.event_mask = StructureNotifyMask; + window_attributes.override_redirect = 1; + x11_window = XCreateWindow(x11_server, + root_window, + out->window_pos.x, out->window_pos.y, + out->window_size.x, out->window_size.y, 0, + visual_info.depth, + InputOutput, + visual_info.visual, + CWBorderPixel|CWColormap|CWEventMask, + &window_attributes); + + XStoreName(x11_server, x11_window, (char*)out->window_name); + // Make window viewable + XMapWindow(x11_server, x11_window); + + out->native_server = (EGLNativeDisplayType)x11_server; + out->native_window = (EGLNativeWindowType)x11_window; + return 1; +} diff --git a/src/os/os_inc.c b/src/os/os_inc.c index 7b2125aec..2cdc2fa37 100644 --- a/src/os/os_inc.c +++ b/src/os/os_inc.c @@ -24,6 +24,10 @@ # endif #elif OS_LINUX # include "core/linux/os_core_linux.c" +# if OS_FEATURE_GRAPHICAL && !OS_GFX_STUB +# 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..a44d1d0d5 100644 --- a/src/os/os_inc.h +++ b/src/os/os_inc.h @@ -36,6 +36,9 @@ # endif #elif OS_LINUX # include "core/linux/os_core_linux.h" +# if OS_FEATURE_GRAPHICAL && !OS_GFX_STUB +# include "gfx/linux/os_gfx_linux.h" +# endif #else # error no OS layer setup #endif diff --git a/src/render/opengl/render_opengl.c b/src/render/opengl/render_opengl.c new file mode 100644 index 000000000..8f3967d2a --- /dev/null +++ b/src/render/opengl/render_opengl.c @@ -0,0 +1,39 @@ + + +// NOTE(mallchad): This is Linux specific, whatever, we can think about making it agnostic later. +// Or not... + +r_hook void +r_init(CmdLine *cmdln) +{ +} + + + +//- 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); From 18806f2bb225f98ce7abd1e12ac771cd707a449f Mon Sep 17 00:00:00 2001 From: Mallchad Date: Tue, 24 Dec 2024 01:13:36 +0000 Subject: [PATCH 24/30] [OS Graphics] Added: 'os_window_open' definition --- src/os/core/linux/os_core_linux.c | 3 +- src/os/gfx/linux/os_gfx_linux.c | 70 ++++++++++++++++++++----------- src/os/gfx/linux/os_gfx_linux.h | 34 ++++++++++++--- src/os/gfx/linux/os_gfx_x11.c | 50 +++++++++++++++------- 4 files changed, 111 insertions(+), 46 deletions(-) diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 4a4c17918..f58d6be36 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -12,7 +12,8 @@ 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 +// 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; diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index 061e04278..073f0cf56 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -1,11 +1,12 @@ +Arena* gfx_lnx_arena = NULL; global B32 gfx_lnx_wayland_preferred = 0; global B32 gfx_lnx_wayland_disabled = 1; global S32 gfx_egl_version_major = 0; global S32 gfx_egl_version_minor = 0; global U8* gfx_default_window_name = (U8*)"raddebugger"; -GFX_Context gfx_context = {0}; +global GFX_LinuxContext gfx_context = {0}; global EGLContext gfx_egl_context = NULL; global EGLDisplay gfx_egl_display = NULL; @@ -43,18 +44,39 @@ global S32 gfx_egl_context_config[] = { * it. So be it. */ + +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; +} + // Stub B32 -wayland_graphical_init(GFX_Context* out) +wayland_window_open(GFX_LinuxContext* gfx_context, OS_Handle* result, + Vec2F32 resolution, OS_WindowFlags flags, String8 title) +{ NotImplemented; } + +B32 +wayland_graphical_init(GFX_LinuxContext* out) { NotImplemented; } internal void os_graphical_init(void) { + gfx_lnx_arena = arena_alloc(); gfx_context.window_name = gfx_default_window_name; - gfx_context.default_window_size = vec_2s32(500, 500); + gfx_context.default_window_size = vec_2f32(500, 500); gfx_context.window_size = gfx_context.default_window_size; - gfx_context.default_window_pos = vec_2s32(500, 500); + gfx_context.default_window_pos = vec_2f32(500, 500); gfx_context.window_pos = gfx_context.default_window_pos; B32 init_result = 0; @@ -76,10 +98,6 @@ os_graphical_init(void) &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); - gfx_egl_draw_surface = eglCreateWindowSurface(gfx_egl_display, gfx_egl_config_available[0], - (EGLNativeWindowType)gfx_context.native_window, NULL); - gfx_egl_read_surface = eglCreateWindowSurface(gfx_egl_display, gfx_egl_config_available[0], - (EGLNativeWindowType)gfx_context.native_window, NULL); 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); @@ -87,19 +105,6 @@ os_graphical_init(void) gfx_egl_context = eglCreateContext(gfx_egl_display, select_config, EGL_NO_CONTEXT, gfx_egl_context_config ); Assert(gfx_egl_context != EGL_NO_CONTEXT); - eglMakeCurrent(gfx_egl_display, gfx_egl_draw_surface, gfx_egl_draw_surface, gfx_egl_context); - - Vec4F32 dark_magenta = vec_4f32( 0.2f, 0.f, 0.2f, 1.0f ); -while (1){ - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); - glClearColor(dark_magenta.x, dark_magenta.y, dark_magenta.z, dark_magenta.w); - S32 swap_result =eglSwapBuffers(gfx_egl_display, gfx_egl_draw_surface); - S32 err = eglGetError(); - Trap(); - static int i = 0; - ++i; -} } @@ -125,9 +130,21 @@ NotImplemented;} 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); + + window->first_surface = eglCreateWindowSurface(gfx_egl_display, gfx_egl_config_available[0], + (EGLNativeWindowType)window->handle, NULL); + eglMakeCurrent(gfx_egl_display, gfx_egl_draw_surface, gfx_egl_draw_surface, gfx_egl_context); + return result; -NotImplemented;} +} internal void os_window_close(OS_Handle window) { @@ -142,8 +159,13 @@ NotImplemented;} internal void os_window_equip_repaint(OS_Handle window, OS_WindowRepaintFunctionType *repaint, void *user_data) { - -NotImplemented;} + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + Vec4F32 dark_magenta = vec_4f32( 0.2f, 0.f, 0.2f, 1.0f ); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); + glClearColor(dark_magenta.x, dark_magenta.y, dark_magenta.z, dark_magenta.w); + S32 swap_result = eglSwapBuffers(gfx_egl_display, _window->first_surface); +} internal void os_window_focus(OS_Handle window) diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index 844685c5a..cea707320 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -4,18 +4,40 @@ #include -typedef struct GFX_Context GFX_Context; -struct GFX_Context +typedef struct GFX_LinuxContext GFX_LinuxContext; +struct GFX_LinuxContext { U8* window_name; - Vec2S32 default_window_size; - Vec2S32 window_size; - Vec2S32 default_window_pos; - Vec2S32 window_pos; + Vec2F32 default_window_size; + Vec2F32 window_size; + Vec2F32 default_window_pos; + Vec2F32 window_pos; EGLNativeDisplayType native_server; EGLNativeWindowType native_window; }; +typedef struct GFX_LinuxWindow GFX_LinuxWindow; +struct GFX_LinuxWindow +{ + U64 handle; + String8 window_name; + Vec2F32 start_pos; + Vec2F32 pos; + Vec2F32 size; + U32 root_relative_depth; + EGLSurface first_surface; + EGLSurface second_surface; + B32 wayland_native; +}; + +extern Arena* gfx_lnx_arena; + + +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_x11.c b/src/os/gfx/linux/os_gfx_x11.c index bf93c9445..a55056b96 100644 --- a/src/os/gfx/linux/os_gfx_x11.c +++ b/src/os/gfx/linux/os_gfx_x11.c @@ -14,12 +14,23 @@ global X11_Display* x11_server = NULL; global X11_Window x11_window = 0; B32 -x11_graphical_init(GFX_Context* out) +x11_graphical_init(GFX_LinuxContext* out) { // Initialize X11 x11_server = XOpenDisplay(NULL); Assert(x11_server != NULL); if (x11_server == NULL) { return 0; } + + out->native_server = (EGLNativeDisplayType)x11_server; + return 1; +} + +B32 +x11_window_open(GFX_LinuxContext* out, OS_Handle* out_handle, + Vec2F32 resolution, OS_WindowFlags flags, String8 title) +{ + GFX_LinuxWindow* result = push_array_no_zero(gfx_lnx_arena, GFX_LinuxWindow, 1); + S32 default_screen = XDefaultScreen(x11_server); S32 default_depth = DefaultDepth(x11_server, default_screen); XVisualInfo visual_info = {0}; @@ -33,21 +44,30 @@ x11_graphical_init(GFX_Context* out) window_attributes.border_pixel = 0; window_attributes.event_mask = StructureNotifyMask; window_attributes.override_redirect = 1; - x11_window = XCreateWindow(x11_server, - root_window, - out->window_pos.x, out->window_pos.y, - out->window_size.x, out->window_size.y, 0, - visual_info.depth, - InputOutput, - visual_info.visual, - CWBorderPixel|CWColormap|CWEventMask, - &window_attributes); - - XStoreName(x11_server, x11_window, (char*)out->window_name); + X11_Window new_window = XCreateWindow(x11_server, + root_window, + out->default_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, x11_window, (char*)title.str); } + else + { XStoreName(x11_server, x11_window, (char*)out->window_name); } // Make window viewable XMapWindow(x11_server, x11_window); - out->native_server = (EGLNativeDisplayType)x11_server; - out->native_window = (EGLNativeWindowType)x11_window; - return 1; + result->handle = (U64)new_window; + result->window_name = push_str8_copy(gfx_lnx_arena, title); + result->start_pos = out->default_window_pos; + result->pos = result->start_pos; + result->size = resolution; + result->root_relative_depth = 0; + result->wayland_native = 0; + *out_handle = gfx_handle_from_window(result); + return 0; } From c06325e8fa18c0dd322d45c13d5494b621c6cd26 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sat, 28 Dec 2024 21:13:54 +0000 Subject: [PATCH 25/30] [OS Graphics] Added: Bulked out majority of event handling code NOTE: Needs other stuff to be tested properly Fixed: Whoops, forgot to commit header file for x11 Added: Error checking for unsupported window flag Added: Stub files for wayland backend --- src/os/gfx/linux/os_gfx_linux.c | 25 +++- src/os/gfx/linux/os_gfx_linux.h | 1 - src/os/gfx/linux/os_gfx_wayland.c | 0 src/os/gfx/linux/os_gfx_wayland.h | 0 src/os/gfx/linux/os_gfx_x11.c | 209 +++++++++++++++++++++++++++++- src/os/gfx/linux/os_gfx_x11.h | 209 ++++++++++++++++++++++++++++++ src/os/os_inc.h | 1 + 7 files changed, 437 insertions(+), 8 deletions(-) create mode 100644 src/os/gfx/linux/os_gfx_wayland.c create mode 100644 src/os/gfx/linux/os_gfx_wayland.h create mode 100644 src/os/gfx/linux/os_gfx_x11.h diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index 073f0cf56..4d96dc495 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -1,8 +1,13 @@ Arena* gfx_lnx_arena = NULL; +/// 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 U8* gfx_default_window_name = (U8*)"raddebugger"; @@ -65,10 +70,16 @@ wayland_window_open(GFX_LinuxContext* gfx_context, OS_Handle* result, Vec2F32 resolution, OS_WindowFlags flags, String8 title) { NotImplemented; } +// Stub B32 wayland_graphical_init(GFX_LinuxContext* out) { NotImplemented; } +// Stub +B32 +wayland_get_events(Arena *arena, B32 wait, OS_EventList* out) +{ NotImplemented; } + internal void os_graphical_init(void) { @@ -311,17 +322,25 @@ NotImplemented;} internal OS_EventList os_get_events(Arena *arena, B32 wait) { -NotImplemented;} + 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) { -NotImplemented;} + NotImplemented; +} internal B32 os_key_is_down(OS_Key key) { -NotImplemented;} + NotImplemented; +} internal Vec2F32 os_mouse_from_window(OS_Handle window) diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index cea707320..704b176c8 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -31,7 +31,6 @@ struct GFX_LinuxWindow EGLSurface second_surface; B32 wayland_native; }; - extern Arena* gfx_lnx_arena; 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..e69de29bb 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 index a55056b96..ef7eea1b5 100644 --- a/src/os/gfx/linux/os_gfx_x11.c +++ b/src/os/gfx/linux/os_gfx_x11.c @@ -2,7 +2,6 @@ #include #include #include -#include #include @@ -12,6 +11,11 @@ typedef Window X11_Window; global X11_Display* x11_server = NULL; global X11_Window x11_window = 0; +global Atom x11_atoms[100]; +global Atom x11_atom_tail = 0; +global U32 x11_unhandled_events = 0; +global U32 xdnd_supported_version = 5; + B32 x11_graphical_init(GFX_LinuxContext* out) @@ -20,8 +24,21 @@ x11_graphical_init(GFX_LinuxContext* out) x11_server = XOpenDisplay(NULL); Assert(x11_server != NULL); if (x11_server == NULL) { return 0; } + MemoryZeroArray(x11_atoms); out->native_server = (EGLNativeDisplayType)x11_server; + + // Setup atoms + Atom test_atom = 0; + for (int i=0; iwindow_name); } + { XStoreName(x11_server, new_window, (char*)out->window_name); } + + // 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); + // Make window viewable - XMapWindow(x11_server, x11_window); + XMapWindow(x11_server, new_window); result->handle = (U64)new_window; result->window_name = push_str8_copy(gfx_lnx_arena, title); @@ -71,3 +100,175 @@ x11_window_open(GFX_LinuxContext* out, OS_Handle* out_handle, *out_handle = gfx_handle_from_window(result); return 0; } + +OS_Key +x11_oskey_from_keycode(U32 keycode) +{ + U32 table_size = sizeof(x11_keysym); + U32 x_keycode = 0; + for (int i=0; ifirst == NULL) { out->first = x_node; }; + if (out->last != NULL) + { + out->last->next = x_node; + x_node->prev = out->last; + } + out->last = x_node; + ++(out->count); + + // Fill out Event + XEvent event; + XNextEvent(x11_server, &event); + GFX_LinuxWindow x_window = {0}; + x_window.handle = event.xany.window; + x_node->window = gfx_handle_from_window(&x_window); + + // TODO: Still missing wakeup event + 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); + // TODO(mallchad): Figure out modifiers, this is gonna be weird + // x_node->flags = ; + // 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); + // TODO(mallchad): Figure out modifiers, this is gonna be weird + // x_node->flags = ; + // 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 + break; + + case ButtonPress: + x_node->timestamp_us = (event.xmotion.time / 1000); // ms to us + x_node->kind = OS_EventKind_Press; + U32 button_code = event.xbutton.button; + x_node->key = x11_mouse[button_code]; + 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 <= 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; + + default: + ++x11_unhandled_events; + x_node->kind = OS_EventKind_Null; + break; + + } + } + 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..73137f111 --- /dev/null +++ b/src/os/gfx/linux/os_gfx_x11.h @@ -0,0 +1,209 @@ + +#ifndef GFX_X11_H +#define GFX_X11_H + +#include + + +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_DELETE_WINDOW, + 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[] = +{ + "WM_DELETE_WINDOW", + "XdndTypeList", + "XdndSelection", + "XdndEnter", + "XdndPosition", + "XdndStatus", + "XdndLeave", + "XdndDrop", + "XdndFinished", + "XdndActionCopy", + "text/uri-list", + "text/plain" +}; + +#endif // GFX_X11_H diff --git a/src/os/os_inc.h b/src/os/os_inc.h index a44d1d0d5..1e9cbde97 100644 --- a/src/os/os_inc.h +++ b/src/os/os_inc.h @@ -37,6 +37,7 @@ #elif OS_LINUX # include "core/linux/os_core_linux.h" # if OS_FEATURE_GRAPHICAL && !OS_GFX_STUB +# include "gfx/linux/os_gfx_x11.h" # include "gfx/linux/os_gfx_linux.h" # endif #else From 36a6537cf09dc52b4d2dec146b5588a5ad97d341 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Sat, 4 Jan 2025 02:13:12 +0000 Subject: [PATCH 26/30] [OS Graphics] Added: Bulked out 'os_push_monitors_array', 'os_primary_monitor' and 'os_monitor_from_window' [Build Script] Added: Line number reporting to build_single function Added: Compilation timing system --- build.sh | 22 ++++- src/os/gfx/linux/os_gfx_linux.c | 24 ++++-- src/os/gfx/linux/os_gfx_linux.h | 16 ++-- src/os/gfx/linux/os_gfx_wayland.c | 14 +++ src/os/gfx/linux/os_gfx_x11.c | 139 ++++++++++++++++++++++++++---- src/os/gfx/linux/os_gfx_x11.h | 11 ++- src/os/os_inc.c | 3 + src/os/os_inc.h | 3 + 8 files changed, 201 insertions(+), 31 deletions(-) diff --git a/build.sh b/build.sh index 038a3fa69..a4ffdd1d0 100755 --- a/build.sh +++ b/build.sh @@ -86,7 +86,7 @@ set auto_compile_flags= # --- 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" - clang_dynamic="-lpthread -ldl -lrt -latomic -lm -lfreetype -lEGL -lX11 -lGL" + clang_dynamic="-lpthread -ldl -lrt -latomic -lm -lfreetype -lEGL -lX11 -lGL -lXrandr" 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}" @@ -159,6 +159,11 @@ function finish() 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 @@ -167,7 +172,20 @@ function build_single() local binary=$2 rad_log "Building '${binary}'" didbuild=1 - ${compile} "$1" ${@:3:100} ${compile_link} "${out}$2" || finish + + 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 $? ; + fi return $? } diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index 4d96dc495..066773cc0 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -1,5 +1,6 @@ -Arena* gfx_lnx_arena = NULL; +global Arena* gfx_lnx_arena = NULL; +global U32 gfx_lnx_max_monitors = 6; /// Determines if wayland pathway should be used by default. We have seperate /// pathway so recompilation isn't necesscary @@ -10,7 +11,7 @@ global U32 gfx_lnx_event_limit = 5000; global S32 gfx_egl_version_major = 0; global S32 gfx_egl_version_minor = 0; -global U8* gfx_default_window_name = (U8*)"raddebugger"; +global String8 gfx_default_window_name = {0}; global GFX_LinuxContext gfx_context = {0}; global EGLContext gfx_egl_context = NULL; @@ -84,11 +85,12 @@ internal void os_graphical_init(void) { gfx_lnx_arena = arena_alloc(); - gfx_context.window_name = gfx_default_window_name; + gfx_context.default_window_name = gfx_default_window_name; gfx_context.default_window_size = vec_2f32(500, 500); - gfx_context.window_size = gfx_context.default_window_size; + gfx_context.default_window_size = gfx_context.default_window_size; gfx_context.default_window_pos = vec_2f32(500, 500); - gfx_context.window_pos = gfx_context.default_window_pos; + gfx_context.default_window_pos = gfx_context.default_window_pos; + gfx_default_window_name = str8_lit("raddebugger"); B32 init_result = 0; if (gfx_lnx_wayland_disabled) @@ -281,20 +283,30 @@ NotImplemented;} 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; NotImplemented;} 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) { - NotImplemented; OS_Handle result = {0}; + if (gfx_lnx_wayland_disabled) + { x11_monitor_from_window(window, &result); } + else + { wayland_monitor_from_window(window, &result); } return result; } diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index 704b176c8..4972a09b6 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -8,11 +8,9 @@ typedef struct GFX_LinuxContext GFX_LinuxContext; struct GFX_LinuxContext { - U8* window_name; + String8 default_window_name; Vec2F32 default_window_size; - Vec2F32 window_size; Vec2F32 default_window_pos; - Vec2F32 window_pos; EGLNativeDisplayType native_server; EGLNativeWindowType native_window; @@ -22,16 +20,22 @@ typedef struct GFX_LinuxWindow GFX_LinuxWindow; struct GFX_LinuxWindow { U64 handle; - String8 window_name; - Vec2F32 start_pos; + 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; B32 wayland_native; }; -extern Arena* gfx_lnx_arena; + +// Global Data +global Arena* gfx_lnx_arena; +global U32 gfx_lnx_max_monitors; internal GFX_LinuxWindow* gfx_window_from_handle(OS_Handle context); diff --git a/src/os/gfx/linux/os_gfx_wayland.c b/src/os/gfx/linux/os_gfx_wayland.c index e69de29bb..ca7db2a1c 100644 --- a/src/os/gfx/linux/os_gfx_wayland.c +++ b/src/os/gfx/linux/os_gfx_wayland.c @@ -0,0 +1,14 @@ + +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; +} diff --git a/src/os/gfx/linux/os_gfx_x11.c b/src/os/gfx/linux/os_gfx_x11.c index ef7eea1b5..7a93b6366 100644 --- a/src/os/gfx/linux/os_gfx_x11.c +++ b/src/os/gfx/linux/os_gfx_x11.c @@ -1,9 +1,4 @@ -#include -#include -#include - -#include typedef Display X11_Display; typedef Window X11_Window; @@ -11,11 +6,31 @@ typedef Window X11_Window; global X11_Display* x11_server = NULL; global X11_Window x11_window = 0; +global X11_Window x11_root_window = 0; global Atom x11_atoms[100]; global Atom x11_atom_tail = 0; global U32 x11_unhandled_events = 0; -global U32 xdnd_supported_version = 5; +global U32 x11_xdnd_supported_version = 5; +global RROutput* x11_monitors = 0; +global U32 x11_monitors_size = 0; + +/// Update window properties from X11 +B32 +x11_window_update_properties(GFX_LinuxWindow* out) +{ + XWindowAttributes props = {0}; + 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; + return 1; +} B32 x11_graphical_init(GFX_LinuxContext* out) @@ -39,6 +54,11 @@ x11_graphical_init(GFX_LinuxContext* out) else { Assert(0); } // Something is not going to work properly if an intern fails } + + // Initialize furthur internal things + x11_monitors = (RROutput*)push_array_no_zero(gfx_lnx_arena, RROutput, gfx_lnx_max_monitors); + S32 default_screen = XDefaultScreen(x11_server); + x11_root_window = RootWindow(x11_server, default_screen); return 1; } @@ -53,17 +73,16 @@ x11_window_open(GFX_LinuxContext* out, OS_Handle* out_handle, S32 default_depth = DefaultDepth(x11_server, default_screen); XVisualInfo visual_info = {0}; XMatchVisualInfo(x11_server, default_screen, default_depth, TrueColor, &visual_info); - X11_Window root_window = RootWindow(x11_server, default_screen); XSetWindowAttributes window_attributes = {0}; window_attributes.colormap = XCreateColormap( x11_server, - root_window, + x11_root_window, visual_info.visual, AllocNone ); window_attributes.background_pixmap = None ; window_attributes.border_pixel = 0; window_attributes.event_mask = StructureNotifyMask; window_attributes.override_redirect = 1; X11_Window new_window = XCreateWindow(x11_server, - root_window, + x11_root_window, out->default_window_pos.x, out->default_window_pos.y, resolution.x, resolution.y, 0, visual_info.depth, @@ -75,7 +94,7 @@ x11_window_open(GFX_LinuxContext* out, OS_Handle* out_handle, if (title.size) { XStoreName(x11_server, new_window, (char*)title.str); } else - { XStoreName(x11_server, new_window, (char*)out->window_name); } + { XStoreName(x11_server, new_window, (char*)out->default_window_name.str); } // Subscribe to NET_WM_DELETE events and disable auto XDestroyWindow() Atom enabled_protocols[] = { x11_atoms[ X11_Atom_WM_DELETE_WINDOW ] }; @@ -91,12 +110,11 @@ x11_window_open(GFX_LinuxContext* out, OS_Handle* out_handle, XMapWindow(x11_server, new_window); result->handle = (U64)new_window; - result->window_name = push_str8_copy(gfx_lnx_arena, title); - result->start_pos = out->default_window_pos; - result->pos = result->start_pos; - result->size = resolution; - result->root_relative_depth = 0; + 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; } @@ -203,7 +221,7 @@ x11_get_events(Arena *arena, B32 wait, OS_EventList* out) 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 <= xdnd_supported_version) + && xdnd_version <= x11_xdnd_supported_version) { x_node->kind = OS_EventKind_FileDrop; if (format_list) @@ -272,3 +290,92 @@ x11_get_events(Arena *arena, B32 wait, OS_EventList* out) } return 1; } + + +/* 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. +*/ +B32 +x11_push_monitors_array(Arena* arena, OS_HandleArray* monitor) +{ + (void)monitor; // NOTE(mallchad): Only really need one instance for now + RROutput x_monitor = {0}; + S32 x_monitor_count = 0; + XRRMonitorInfo* monitor_list = NULL; + MemoryZeroTyped(x11_monitors, gfx_lnx_max_monitors); + + // Get active monitors + monitor_list = XRRGetMonitors(x11_server, x11_root_window, 1, &x_monitor_count); + if (monitor_list == NULL) { return 0; } + x11_monitors_size = x_monitor_count; + if (x_monitor_count > gfx_lnx_max_monitors) + { + gfx_lnx_max_monitors = (2* x_monitor_count); // Greedy allocation to reduce leakage + x11_monitors = push_array_no_zero(gfx_lnx_arena, RROutput, gfx_lnx_max_monitors); + } + for (int i=0; iu64) = XRRGetOutputPrimary(x11_server, x11_root_window); + return 1; +} + +B32 +x11_monitor_from_window(OS_Handle window, OS_Handle* out_monitor) +{ + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + x11_window_update_properties(_window); + + B32 horizontal_inside; + B32 vertical_inside; + + // Monitor Measurements + S32 right_extent; + S32 left_extent; + S32 up_extent; + S32 down_extent; + + B32 found = 0; + XRRMonitorInfo* monitor_list = NULL; + S32 monitor_count = 0; + monitor_list = XRRGetMonitors(x11_server, x11_root_window, 1, &monitor_count); + XRRMonitorInfo x_monitor; + for (int i=0; ipos_mid.x > left_extent) && (_window->pos_mid.x > right_extent); + vertical_inside = (_window->pos_mid.y > up_extent) && (_window->pos_mid.y > down_extent); + + if (horizontal_inside && vertical_inside) { found = 1; break; } + } + Assert(found); + if (found == 0) { return 0; } + + *(out_monitor->u64) = x_monitor.outputs[0]; + return 1; +} diff --git a/src/os/gfx/linux/os_gfx_x11.h b/src/os/gfx/linux/os_gfx_x11.h index 73137f111..12ea3041b 100644 --- a/src/os/gfx/linux/os_gfx_x11.h +++ b/src/os/gfx/linux/os_gfx_x11.h @@ -1,9 +1,18 @@ #ifndef GFX_X11_H #define GFX_X11_H - +// X11 - Xorg Base Headers +#include +#include +#include #include +// X11 Extensions +#include + +// OpenGL +#include + global U32 x11_keysym[] = { XK_VoidSymbol, // OS_Key_Null diff --git a/src/os/os_inc.c b/src/os/os_inc.c index 2cdc2fa37..55b3c2815 100644 --- a/src/os/os_inc.c +++ b/src/os/os_inc.c @@ -25,6 +25,9 @@ #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 diff --git a/src/os/os_inc.h b/src/os/os_inc.h index 1e9cbde97..d348d9f5a 100644 --- a/src/os/os_inc.h +++ b/src/os/os_inc.h @@ -37,6 +37,9 @@ #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 From 619a0ddbd1dc590de1b202a0638ee53e85ab6fdb Mon Sep 17 00:00:00 2001 From: Mallchad Date: Tue, 7 Jan 2025 23:04:56 +0000 Subject: [PATCH 27/30] [OS Graphics] Added: Bulked out some more linux graphics server functions 'os_push_monitors_array', 'os_primary_monitor', 'os_monitor_from_window' 'os_window_set_monitor' [Build Script] Added: Line number reporting to build_single function Added: Compilation timing [General] Fixed: Stubbed out missing-return functions Fixed: Some indentation --- src/demon/linux/demon_core_linux.c | 14 +++ src/os/core/linux/os_core_linux.c | 4 + src/os/gfx/linux/os_gfx_linux.c | 119 +++++++++++-------- src/os/gfx/linux/os_gfx_linux.h | 39 +++++- src/os/gfx/linux/os_gfx_wayland.c | 31 +++++ src/os/gfx/linux/os_gfx_x11.c | 184 +++++++++++++++++++++-------- src/os/gfx/linux/os_gfx_x11.h | 25 ++++ 7 files changed, 319 insertions(+), 97 deletions(-) diff --git a/src/demon/linux/demon_core_linux.c b/src/demon/linux/demon_core_linux.c index 56d160699..cbbf4b655 100644 --- a/src/demon/linux/demon_core_linux.c +++ b/src/demon/linux/demon_core_linux.c @@ -74,18 +74,22 @@ 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; } //////////////////////////////// @@ -143,6 +147,7 @@ internal U64 dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) { NotImplemented; + return 0; } internal void @@ -173,37 +178,45 @@ 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 @@ -217,6 +230,7 @@ internal B32 dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out) { NotImplemented; + return 0; } internal void diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index f58d6be36..bb085c3b1 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1277,7 +1277,9 @@ os_logical_core_count(void) internal String8List os_get_command_line_arguments(void) { + String8List result = {0}; NotImplemented; /* This doesn't appear to be used any longer */ + return result; } internal S32 @@ -2001,6 +2003,7 @@ internal B32 os_process_wait(OS_Handle handle, U64 endt_us) { NotImplemented; + return 0; } internal void @@ -2043,6 +2046,7 @@ 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 diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index 066773cc0..e3d5a6885 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -50,6 +50,17 @@ global S32 gfx_egl_context_config[] = { * it. So be it. */ +GFX_LinuxMonitor* +gfx_monitor_from_handle(OS_Handle monitor) +{ + return (GFX_LinuxMonitor*)PtrFromInt(*monitor.u64); +} +OS_Handle gfx_handle_from_monitor(GFX_LinuxMonitor* monitor) +{ + OS_Handle result = {0}; + *(result.u64) = IntFromPtr(monitor); + return result; +} GFX_LinuxWindow* gfx_window_from_handle(OS_Handle context) @@ -65,22 +76,6 @@ gfx_handle_from_window(GFX_LinuxWindow* window) return result; } -// Stub -B32 -wayland_window_open(GFX_LinuxContext* gfx_context, OS_Handle* result, - Vec2F32 resolution, OS_WindowFlags flags, String8 title) -{ NotImplemented; } - -// Stub -B32 -wayland_graphical_init(GFX_LinuxContext* out) -{ NotImplemented; } - -// Stub -B32 -wayland_get_events(Arena *arena, B32 wait, OS_EventList* out) -{ NotImplemented; } - internal void os_graphical_init(void) { @@ -207,74 +202,80 @@ NotImplemented;} internal B32 os_window_is_maximized(OS_Handle window) { - -NotImplemented;} + NotImplemented; + return 0; +} internal void os_window_set_maximized(OS_Handle window, B32 maximized) { - -NotImplemented;} + NotImplemented; +} internal void os_window_minimize(OS_Handle window) { - -NotImplemented;} +NotImplemented; +} internal void os_window_bring_to_front(OS_Handle window) { - -NotImplemented;} +NotImplemented; +} internal void os_window_set_monitor(OS_Handle window, OS_Handle monitor) { - -NotImplemented;} +NotImplemented; +} internal void os_window_clear_custom_border_data(OS_Handle handle) { - -NotImplemented;} + NotImplemented; +} internal void os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) { - -NotImplemented;} + NotImplemented; +} internal void os_window_push_custom_edges(OS_Handle handle, F32 thickness) { - -NotImplemented;} + NotImplemented; +} internal void os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) { - -NotImplemented;} + NotImplemented; +} internal Rng2F32 os_rect_from_window(OS_Handle window) { - -NotImplemented;} + Rng2F32 result = {0}; + NotImplemented; + return result; +} internal Rng2F32 os_client_rect_from_window(OS_Handle window) { - -NotImplemented;} + Rng2F32 result = {0}; + NotImplemented; + return result; +} internal F32 os_dpi_from_window(OS_Handle window) { + NotImplemented; return 0.f; -NotImplemented;} +} //////////////////////////////// @@ -313,23 +314,36 @@ os_monitor_from_window(OS_Handle window) internal String8 os_name_from_monitor(Arena *arena, OS_Handle monitor) { - NotImplemented; + 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) { - NotImplemented; + 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) { -NotImplemented;} + return; +} internal OS_EventList os_get_events(Arena *arena, B32 wait) @@ -345,19 +359,25 @@ os_get_events(Arena *arena, B32 wait) internal OS_EventFlags os_get_event_flags(void) { + OS_EventFlags result = 0; NotImplemented; + return result; } internal B32 os_key_is_down(OS_Key key) { NotImplemented; + return 0; } internal Vec2F32 os_mouse_from_window(OS_Handle window) { -NotImplemented;} + Vec2F32 result = {0}; + NotImplemented; + return result; +} //////////////////////////////// @@ -366,7 +386,8 @@ NotImplemented;} internal void os_set_cursor(OS_Cursor cursor) { -NotImplemented;} + NotImplemented; +} //////////////////////////////// @@ -383,13 +404,15 @@ internal F32 os_caret_blink_time(void) { return 0.f; -NotImplemented;} + NotImplemented; +} internal F32 os_default_refresh_rate(void) { return 0.f; -NotImplemented;} + NotImplemented; +} //////////////////////////////// @@ -398,5 +421,5 @@ NotImplemented;} internal void os_graphical_message(B32 error, String8 title, String8 message) { - -NotImplemented;} + NotImplemented; +} diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index 4972a09b6..62ffc8476 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -16,10 +16,44 @@ struct GFX_LinuxContext 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 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 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 { U64 handle; + GFX_LinuxMonitor* monitor; String8 name; Vec2F32 pos; Vec2F32 pos_mid; @@ -37,10 +71,9 @@ struct GFX_LinuxWindow global Arena* gfx_lnx_arena; global U32 gfx_lnx_max_monitors; - +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 index ca7db2a1c..238f7a0dd 100644 --- a/src/os/gfx/linux/os_gfx_wayland.c +++ b/src/os/gfx/linux/os_gfx_wayland.c @@ -1,4 +1,27 @@ + +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) { @@ -11,4 +34,12 @@ 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; } diff --git a/src/os/gfx/linux/os_gfx_x11.c b/src/os/gfx/linux/os_gfx_x11.c index 7a93b6366..d5a5ae741 100644 --- a/src/os/gfx/linux/os_gfx_x11.c +++ b/src/os/gfx/linux/os_gfx_x11.c @@ -12,14 +12,63 @@ 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 GFX_LinuxMonitor* x11_monitors2 = 0; global U32 x11_monitors_size = 0; +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; + 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) + { + 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; } + 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) ; + /* out_monitor->refresh_rate = mode->dotClock; */ + /* 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; @@ -29,6 +78,44 @@ x11_window_update_properties(GFX_LinuxWindow* out) 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; + XRRMonitorInfo* monitor_list = NULL; + S32 monitor_count = 0; + monitor_list = XRRGetMonitors(x11_server, x11_root_window, 1, &monitor_count); + XRRMonitorInfo* one_monitor = XRRGetMonitors(x11_server, out->handle, 1, &monitor_count); + XRRMonitorInfo x_monitor; + for (int i=0; ipos_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 + { + // small leak + out->monitor = push_array(gfx_lnx_arena, GFX_LinuxMonitor, 1); + out->monitor->handle = x_monitor.outputs[0]; + x11_monitor_update_properties(out->monitor); + } return 1; } @@ -159,7 +246,7 @@ x11_get_events(Arena *arena, B32 wait, OS_EventList* out) x_window.handle = event.xany.window; x_node->window = gfx_handle_from_window(&x_window); - // TODO: Still missing wakeup event + // NOTE(mallchad): Don't think wakeup is relevant here switch (event.type) { case KeyPress: @@ -291,19 +378,6 @@ x11_get_events(Arena *arena, B32 wait, OS_EventList* out) return 1; } - -/* 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. -*/ B32 x11_push_monitors_array(Arena* arena, OS_HandleArray* monitor) { @@ -322,10 +396,10 @@ x11_push_monitors_array(Arena* arena, OS_HandleArray* monitor) gfx_lnx_max_monitors = (2* x_monitor_count); // Greedy allocation to reduce leakage x11_monitors = push_array_no_zero(gfx_lnx_arena, RROutput, gfx_lnx_max_monitors); } + GFX_LinuxMonitor* x_monitor2 = NULL; for (int i=0; iu64) = XRRGetOutputPrimary(x11_server, x11_root_window); + RROutput primary_output = XRRGetOutputPrimary(x11_server, x11_root_window); + _monitor->handle = primary_output; + x11_monitor_update_properties(_monitor); + *monitor = gfx_handle_from_monitor(_monitor); return 1; } @@ -345,37 +424,50 @@ B32 x11_monitor_from_window(OS_Handle window, OS_Handle* out_monitor) { GFX_LinuxWindow* _window = gfx_window_from_handle(window); - x11_window_update_properties(_window); - - B32 horizontal_inside; - B32 vertical_inside; - - // Monitor Measurements - S32 right_extent; - S32 left_extent; - S32 up_extent; - S32 down_extent; + if (_window->monitor != NULL) + { *out_monitor = gfx_handle_from_monitor(_window->monitor); } + else + { return 0; } + return 1; +} - B32 found = 0; - XRRMonitorInfo* monitor_list = NULL; - S32 monitor_count = 0; - monitor_list = XRRGetMonitors(x11_server, x11_root_window, 1, &monitor_count); - XRRMonitorInfo x_monitor; - for (int i=0; ipos_mid.x > left_extent) && (_window->pos_mid.x > right_extent); - vertical_inside = (_window->pos_mid.y > up_extent) && (_window->pos_mid.y > down_extent); +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; +} - if (horizontal_inside && vertical_inside) { found = 1; break; } - } - Assert(found); - if (found == 0) { return 0; } +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; } +} - *(out_monitor->u64) = x_monitor.outputs[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; } diff --git a/src/os/gfx/linux/os_gfx_x11.h b/src/os/gfx/linux/os_gfx_x11.h index 12ea3041b..bf6f62d57 100644 --- a/src/os/gfx/linux/os_gfx_x11.h +++ b/src/os/gfx/linux/os_gfx_x11.h @@ -14,6 +14,31 @@ #include +/* 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 From a60248e1ef54d607d7f56408f01d4c8af0568032 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Fri, 24 Jan 2025 23:17:45 +0000 Subject: [PATCH 28/30] [OS Graphics] Added: Bulked out some more os_gfx functions. Added: Make basic window swapchain work Added: A generic array container Fixed: Spurious issue where the code would only build correctly with GNU extensions enabled [Build Script] Fixed: issue where compile status wasn't being passed through script properly Fixed: Missing uuid library --- build.sh | 10 +- documentation/build_system.md | 3 + src/base/base_core.c | 60 ++++++ src/base/base_core.h | 59 ++++++ src/os/gfx/linux/os_gfx_linux.c | 105 +++++++--- src/os/gfx/linux/os_gfx_linux.h | 25 +++ src/os/gfx/linux/os_gfx_wayland.c | 27 +++ src/os/gfx/linux/os_gfx_x11.c | 335 +++++++++++++++++++++++------- src/os/gfx/linux/os_gfx_x11.h | 34 ++- src/os/gfx/os_gfx.h | 1 + src/raddbg/raddbg.c | 1 + 11 files changed, 553 insertions(+), 107 deletions(-) diff --git a/build.sh b/build.sh index a4ffdd1d0..f9c630fc2 100755 --- a/build.sh +++ b/build.sh @@ -85,8 +85,8 @@ set auto_compile_flags= # --- 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" - clang_dynamic="-lpthread -ldl -lrt -latomic -lm -lfreetype -lEGL -lX11 -lGL -lXrandr" + 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}" @@ -183,10 +183,10 @@ function build_single() $((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 $? ; +${ansi_red}Line: ${BASH_LINENO[$i]} ${ansi_reset}" ; + exit ${status} ; fi - return $? + return ${status} } function build_dll() diff --git a/documentation/build_system.md b/documentation/build_system.md index 243103188..c2d7dcf82 100644 --- a/documentation/build_system.md +++ b/documentation/build_system.md @@ -28,6 +28,9 @@ this can be turned into a `.desktop` file. * -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. diff --git a/src/base/base_core.c b/src/base/base_core.c index 2d40fe343..d0237b4e8 100644 --- a/src/base/base_core.c +++ b/src/base/base_core.c @@ -560,3 +560,63 @@ 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->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; +} + +B32 +_ArrayPushTail(GenericArray* target, void* item, U32 item_size) +{ + if (target->fast_bounded && (target->head + target->head_size >= target->size)) + { return 0; } + U8* new_tail = target->data; + new_tail += (target->head + (target->head_size * item_size)); + MemoryCopy(new_tail, item, item_size); + ++(target->head_size); + return 1; +} + +B32 +_ArrayPopTail(GenericArray* target, void* out, U32 item_size) +{ + if (target->fast_bounded && (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 f7fa66df8..31d7e41cf 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -710,6 +710,7 @@ struct DateTime U32 year; // 1 = 1 CE, 0 = 1 BC }; +/// Time in microseconds typedef U64 DenseTime; //////////////////////////////// @@ -817,4 +818,62 @@ 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_bounded; +}; + +/// Declare a typed dynamic array class +#define DeclareArray(NAME, ELEMENT_TYPE) \ + typedef struct NAME NAME; \ + struct NAME \ + { \ + ELEMENT_TYPE* data; \ + Arena* arena; \ + U64 size; \ + U64 head; \ + U64 head_size; \ + U32 item_size; \ + B32 fast_bounded; \ + }; + +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 +B32 _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])) + #endif // BASE_CORE_H diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index e3d5a6885..e3e79c396 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -1,6 +1,11 @@ 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}; /// Determines if wayland pathway should be used by default. We have seperate /// pathway so recompilation isn't necesscary @@ -50,15 +55,27 @@ global S32 gfx_egl_context_config[] = { * 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_LinuxMonitor*)PtrFromInt(*monitor.u64); + return gfx_monitor_from_id(*monitor.u64); } OS_Handle gfx_handle_from_monitor(GFX_LinuxMonitor* monitor) { OS_Handle result = {0}; - *(result.u64) = IntFromPtr(monitor); + *(result.u64) = monitor->id; return result; } @@ -79,7 +96,9 @@ gfx_handle_from_window(GFX_LinuxWindow* window) 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; @@ -87,6 +106,9 @@ os_graphical_init(void) 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); + B32 init_result = 0; if (gfx_lnx_wayland_disabled) { init_result = x11_graphical_init(&gfx_context); } @@ -146,11 +168,10 @@ os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 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); - eglMakeCurrent(gfx_egl_display, gfx_egl_draw_surface, gfx_egl_draw_surface, gfx_egl_context); - return result; } internal void @@ -162,16 +183,20 @@ NotImplemented;} internal void os_window_first_paint(OS_Handle window) { + // Nothing to do on first paint yet + NoOp; +} -NotImplemented;} internal void os_window_equip_repaint(OS_Handle window, OS_WindowRepaintFunctionType *repaint, void *user_data) { GFX_LinuxWindow* _window = gfx_window_from_handle(window); Vec4F32 dark_magenta = vec_4f32( 0.2f, 0.f, 0.2f, 1.0f ); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); + + eglMakeCurrent(gfx_egl_display, _window->first_surface, _window->first_surface, gfx_egl_context); + /* glBindFramebuffer(GL_FRAMEBUFFER, 0); */ 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 ); S32 swap_result = eglSwapBuffers(gfx_egl_display, _window->first_surface); } @@ -202,14 +227,21 @@ NotImplemented;} internal B32 os_window_is_maximized(OS_Handle window) { - NotImplemented; - return 0; + 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) { - NotImplemented; + if (gfx_lnx_wayland_disabled) + { x11_window_set_maximized(window, maximized); } + else + { wayland_window_set_maximized(window, maximized); } } internal void @@ -227,38 +259,53 @@ NotImplemented; internal void os_window_set_monitor(OS_Handle window, OS_Handle monitor) { -NotImplemented; + 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) { - NotImplemented; + // NOTE(mallchad): No practical use yet4 + NoOp; } internal void os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) { - NotImplemented; + /* 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 handle, F32 thickness) +os_window_push_custom_edges(OS_Handle window, F32 thickness) { - NotImplemented; + 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) { - NotImplemented; + /* 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}; - NotImplemented; + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + result.min = _window->pos; + result.max = add_2f32(_window->pos, _window->size); return result; } @@ -266,15 +313,17 @@ internal Rng2F32 os_client_rect_from_window(OS_Handle window) { Rng2F32 result = {0}; - NotImplemented; + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + result.max = _window->size; return result; } internal F32 os_dpi_from_window(OS_Handle window) { - NotImplemented; - return 0.f; + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + Assert(_window->monitor->dpi > 0.001 && _window->monitor->dpi < 1000); + return (_window->monitor->dpi); } @@ -290,7 +339,7 @@ os_push_monitors_array(Arena *arena) else { wayland_push_monitors_array(arena, &result); } return result; -NotImplemented;} +} internal OS_Handle os_primary_monitor(void) @@ -359,9 +408,7 @@ os_get_events(Arena *arena, B32 wait) internal OS_EventFlags os_get_event_flags(void) { - OS_EventFlags result = 0; - NotImplemented; - return result; + return gfx_lnx_modifier_state; } internal B32 @@ -374,9 +421,8 @@ os_key_is_down(OS_Key key) internal Vec2F32 os_mouse_from_window(OS_Handle window) { - Vec2F32 result = {0}; - NotImplemented; - return result; + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + return _window->mouse_pos; } @@ -410,8 +456,9 @@ os_caret_blink_time(void) internal F32 os_default_refresh_rate(void) { - return 0.f; - NotImplemented; + OS_Handle primary_monitor = os_primary_monitor(); + GFX_LinuxMonitor* monitor = gfx_monitor_from_handle(primary_monitor); + return monitor->refresh_rate; } diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index 62ffc8476..d1ce55e77 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -31,6 +31,7 @@ enum GFX_MonitorOrientation 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 @@ -40,6 +41,8 @@ struct GFX_LinuxMonitor 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; @@ -52,6 +55,9 @@ struct GFX_LinuxMonitor 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; @@ -64,12 +70,31 @@ struct GFX_LinuxWindow 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; GFX_LinuxMonitor* gfx_monitor_from_handle(OS_Handle monitor); OS_Handle gfx_handle_from_monitor(GFX_LinuxMonitor* monitor); diff --git a/src/os/gfx/linux/os_gfx_wayland.c b/src/os/gfx/linux/os_gfx_wayland.c index 238f7a0dd..5cabb3d1e 100644 --- a/src/os/gfx/linux/os_gfx_wayland.c +++ b/src/os/gfx/linux/os_gfx_wayland.c @@ -43,3 +43,30 @@ 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_x11.c b/src/os/gfx/linux/os_gfx_x11.c index d5a5ae741..04f425170 100644 --- a/src/os/gfx/linux/os_gfx_x11.c +++ b/src/os/gfx/linux/os_gfx_x11.c @@ -7,21 +7,40 @@ typedef Window X11_Window; 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 GFX_LinuxMonitor* x11_monitors2 = 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) { @@ -34,6 +53,22 @@ x11_monitor_update_properties(GFX_LinuxMonitor* out_monitor) } 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); @@ -46,11 +81,17 @@ x11_monitor_update_properties(GFX_LinuxMonitor* out_monitor) } 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) ; - /* out_monitor->refresh_rate = mode->dotClock; */ + 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); @@ -90,18 +131,14 @@ x11_window_update_properties(GFX_LinuxWindow* out) S32 down_extent; B32 found = 0; - XRRMonitorInfo* monitor_list = NULL; - S32 monitor_count = 0; - monitor_list = XRRGetMonitors(x11_server, x11_root_window, 1, &monitor_count); - XRRMonitorInfo* one_monitor = XRRGetMonitors(x11_server, out->handle, 1, &monitor_count); - XRRMonitorInfo x_monitor; - for (int i=0; ioffset.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); @@ -110,12 +147,47 @@ x11_window_update_properties(GFX_LinuxWindow* out) 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; imonitor = push_array(gfx_lnx_arena, GFX_LinuxMonitor, 1); - out->monitor->handle = x_monitor.outputs[0]; - x11_monitor_update_properties(out->monitor); + MemoryZeroStruct(&x_monitor); + x11_monitor_init(&x_monitor, monitor_list[i].outputs[0]); + ArrayPushTail(&gfx_lnx_monitors, &x_monitor); } + // Free data + XRRFreeMonitors(monitor_list); return 1; } @@ -135,7 +207,7 @@ x11_graphical_init(GFX_LinuxContext* out) for (int i=0; ifirst == NULL) { out->first = x_node; }; - if (out->last != NULL) - { - out->last->next = x_node; - x_node->prev = out->last; - } - out->last = x_node; - ++(out->count); + x_node = push_array(arena, OS_Event, 1); + window = NULL; + keep_event = 1; // Fill out Event - XEvent event; XNextEvent(x11_server, &event); - GFX_LinuxWindow x_window = {0}; - x_window.handle = event.xany.window; - x_node->window = gfx_handle_from_window(&x_window); + /* search for existing window to match DF API which draws from pre-existing windows (handles) + (hidden dependency) */ + GFX_LinuxWindow* x_window = gfx_lnx_windows.first; + for (; x_window != NULL;) + { + if (x_window->handle == event.xany.window) + { + window = x_window; + x_node->window = gfx_handle_from_window(x_window); + break; + } + x_window = x_window->next; + } + Assert(window != NULL); // 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) { @@ -253,8 +337,7 @@ x11_get_events(Arena *arena, B32 wait, OS_EventList* out) 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); - // TODO(mallchad): Figure out modifiers, this is gonna be weird - // x_node->flags = ; + 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; @@ -265,8 +348,7 @@ x11_get_events(Arena *arena, B32 wait, OS_EventList* out) 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); - // TODO(mallchad): Figure out modifiers, this is gonna be weird - // x_node->flags = ; + 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; @@ -281,13 +363,18 @@ x11_get_events(Arena *arena, B32 wait, OS_EventList* out) 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.xmotion.time / 1000); // ms to us + 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 @@ -368,56 +455,88 @@ x11_get_events(Arena *arena, B32 wait, OS_EventList* out) 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) { - (void)monitor; // NOTE(mallchad): Only really need one instance for now - RROutput x_monitor = {0}; - S32 x_monitor_count = 0; - XRRMonitorInfo* monitor_list = NULL; - MemoryZeroTyped(x11_monitors, gfx_lnx_max_monitors); - - // Get active monitors - monitor_list = XRRGetMonitors(x11_server, x11_root_window, 1, &x_monitor_count); - if (monitor_list == NULL) { return 0; } - x11_monitors_size = x_monitor_count; - if (x_monitor_count > gfx_lnx_max_monitors) - { - gfx_lnx_max_monitors = (2* x_monitor_count); // Greedy allocation to reduce leakage - x11_monitors = push_array_no_zero(gfx_lnx_arena, RROutput, gfx_lnx_max_monitors); - } - GFX_LinuxMonitor* x_monitor2 = NULL; - for (int i=0; ihandle = primary_output; - x11_monitor_update_properties(_monitor); - *monitor = gfx_handle_from_monitor(_monitor); - return 1; + + 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 @@ -471,3 +590,75 @@ x11_window_set_monitor(OS_Handle window, OS_Handle monitor) 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 index bf6f62d57..4cd64fa05 100644 --- a/src/os/gfx/linux/os_gfx_x11.h +++ b/src/os/gfx/linux/os_gfx_x11.h @@ -210,7 +210,20 @@ global U32 x11_mouse[] = /// 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_XdndTypeList, X11_Atom_XdndSelection, X11_Atom_XdndEnter, @@ -226,7 +239,23 @@ enum x11_atoms global char* x11_test_atoms[] = { - "WM_DELETE_WINDOW", + "_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", "XdndTypeList", "XdndSelection", "XdndEnter", @@ -241,3 +270,6 @@ global char* x11_test_atoms[] = }; #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/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) From 3a4b063154ee5a137e8e947ba7e841ccf1c19289 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Thu, 6 Feb 2025 23:45:32 +0000 Subject: [PATCH 29/30] [OS Graphics] Added: icon display of window manager (and various other fixes) Fixed: Array out of bounds issue with sizeof [Build Script] Fixed: Not building with asan because of lld linker?? [Metagen] Fixed: Inconsistency with 'os_make_directory' preventing meta file regeneration Fixed: Hidden bug with missing null terminator preventing '@embed_file' generation [Freetype] Fixed: Bad behaviour with empty font_open argument Added: Function to retreive error code descriptions Fixed: Test bitmap not writing Fixed: Various segfaults with incorrect buffer sizing [OS Core] Merge: [master] Copied maybe-better condition variable Fixed: Condition variable erroring/consuming massive CPU from erroneous timeout parameter --- build.sh | 2 +- src/base/base_core.h | 9 +- src/df/gfx/df_gfx.c | 7 - .../freetype/font_provider_freetype.c | 92 +- .../freetype/font_provider_freetype.h | 17 + src/metagen/metagen_base/metagen_base_types.h | 10 +- .../core/linux/metagen_os_core_linux.c | 8 +- src/metagen/metagen_os/core/metagen_os_core.c | 7 +- src/os/core/linux/os_core_linux.c | 155 ++-- src/os/core/linux/os_core_linux.h | 5 +- src/os/gfx/linux/os_gfx_linux.c | 102 ++- src/os/gfx/linux/os_gfx_linux.h | 4 + src/os/gfx/linux/os_gfx_x11.c | 44 +- src/os/gfx/linux/os_gfx_x11.h | 5 + src/raddbg/raddbg_main.c | 10 + src/render/generated/render.meta.h | 15 + .../opengl/generated/render_opengl.meta.c | 5 + .../opengl/generated/render_opengl.meta.h | 798 ++++++++++++++++++ src/render/opengl/render_opengl.c | 243 +++++- src/render/opengl/render_opengl.h | 145 ++++ src/render/opengl/render_opengl.mdesk | 701 +++++++++++++++ src/render/render_core.mdesk | 22 +- src/render/render_inc.c | 2 + src/render/render_inc.h | 9 +- 24 files changed, 2283 insertions(+), 134 deletions(-) create mode 100644 src/render/opengl/generated/render_opengl.meta.c create mode 100644 src/render/opengl/generated/render_opengl.meta.h create mode 100644 src/render/opengl/render_opengl.h create mode 100644 src/render/opengl/render_opengl.mdesk diff --git a/build.sh b/build.sh index f9c630fc2..16c450a93 100755 --- a/build.sh +++ b/build.sh @@ -94,7 +94,7 @@ set 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=lld" + clang_link="-fuse-ld=ld" cl_out="/out:" clang_out="-o" diff --git a/src/base/base_core.h b/src/base/base_core.h index 31d7e41cf..8717c4008 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -842,7 +842,7 @@ struct GenericArray struct NAME \ { \ ELEMENT_TYPE* data; \ - Arena* arena; \ + struct Arena* arena; \ U64 size; \ U64 head; \ U64 head_size; \ @@ -876,4 +876,11 @@ 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/df/gfx/df_gfx.c b/src/df/gfx/df_gfx.c index c79eb6b4e..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) { diff --git a/src/font_provider/freetype/font_provider_freetype.c b/src/font_provider/freetype/font_provider_freetype.c index de60100d6..891ccea2b 100644 --- a/src/font_provider/freetype/font_provider_freetype.c +++ b/src/font_provider/freetype/font_provider_freetype.c @@ -1,11 +1,9 @@ // Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include "third_party/stb/stb_image_write.h" - -internal FT_Library freetype_instance = NULL; -internal Arena* freetype_arena = NULL; +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) @@ -21,6 +19,19 @@ freetype_handle_from_face(FreeType_FontFace 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) { @@ -69,10 +80,12 @@ fp_font_open_from_static_data_string(String8 *data_ptr) 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; -// Pt Fractional Scaling - sizing_request.width = 300*64; - sizing_request.height = 300*64; // DPI sizing_request.horiResolution = 30; sizing_request.vertResolution = 30; @@ -151,8 +164,10 @@ fp_raster(Arena *arena, if (face == 0) { return result; } Temp scratch = scratch_begin(0, 0); - U32* charmap_indices = push_array(scratch.arena, U32, 4+ string.size); - F32 win32_magic_dimensions = (96.f/72.f); + + 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; @@ -179,18 +194,20 @@ fp_raster(Arena *arena, // start at (300,200) relative to the upper left corner pen.x = 0; pen.y = face->ascender; - U32 glyph_height = 60; - U32 glyph_width = 100; - U32 magic_dpi = 64; + // 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; - U32 total_height = 0; Vec2S16 atlas_dim = {0}; - U32 glyph_count = string.size; - /* NOTE(mallchad): 'size' is the actual internal glyph "scale" not char count - Convert magic fixed point coordinate system to internal floating point representation */ - F32 glyph_advance_width = (face->max_advance_width * (96.f/72.f) * size) / face->units_per_EM; - F32 glyph_advance_height = (face->max_advance_height * (96.f/72.f) * size) / face->units_per_EM; - U8* atlas = push_array_no_zero( scratch.arena, U8, 5* glyph_advance_width * glyph_advance_height ); + + 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; @@ -204,8 +221,8 @@ fp_raster(Arena *arena, // 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 + 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); @@ -214,13 +231,12 @@ fp_raster(Arena *arena, face->glyph->bitmap.buffer, vec_2s32(face->glyph->bitmap.pitch, face->glyph->bitmap.rows), vec_2s32(0, 0), - vec_2s32(total_advance, total_height), + vec_2s32(total_advance, glyph_height), face->glyph->bitmap.pitch, - glyph_advance_width); + glyph_width); // Section compied from dwrite - total_advance += glyph_advance_width; - total_height += glyph_advance_height; + 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); @@ -229,14 +245,14 @@ fp_raster(Arena *arena, 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; + /* 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 = total_height; - result.advance = glyph_advance_width; + 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; @@ -245,7 +261,7 @@ fp_raster(Arena *arena, if (face->glyph->bitmap.buffer != NULL) { // Debug Stuff - String8 debug_name = str8_lit("debug/test.bmp"); + String8 debug_name = str8_lit("glyph_test.bmp"); freetype_write_bmp_monochrome_file(debug_name, face->glyph->bitmap.buffer, face->glyph->bitmap.pitch, @@ -268,10 +284,10 @@ freetype_write_bmp_file(String8 name, U8* data, U32 width, U32 height) { return 0; } Temp scratch = scratch_begin(0, 0); - U32 greedy_filesize = 4* width*height; + 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 = (sizeof(FreeType_BitmapHeader)*2); + U32 pixbuf_offset = (40+ sizeof(FreeType_BitmapHeader)); U8* pixbuf = buffer+ pixbuf_offset; MemoryCopy(&header->signature, "BM\0\0\0\0", 2); @@ -331,10 +347,10 @@ freetype_write_bmp_monochrome_file(String8 name, U8* data, U32 width, U32 height { return 0; } Temp scratch = scratch_begin(0, 0); - U32 greedy_filesize = 4* width*height; + 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 = (sizeof(FreeType_BitmapHeader)*2); + U32 pixbuf_offset = (40+ sizeof(FreeType_BitmapHeader)*2); U8* pixbuf = filebuf+ pixbuf_offset; MemoryZero(filebuf, greedy_filesize); @@ -352,7 +368,7 @@ freetype_write_bmp_monochrome_file(String8 name, U8* data, U32 width, U32 height header->y_pixels_per_meter = 2835; header->compressed_image_size = 4* width*height; - U8* buffer = push_array(scratch.arena, U8, 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); */ diff --git a/src/font_provider/freetype/font_provider_freetype.h b/src/font_provider/freetype/font_provider_freetype.h index 84144c1ca..4e6158437 100644 --- a/src/font_provider/freetype/font_provider_freetype.h +++ b/src/font_provider/freetype/font_provider_freetype.h @@ -9,7 +9,23 @@ /* #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 @@ -44,6 +60,7 @@ 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); diff --git a/src/metagen/metagen_base/metagen_base_types.h b/src/metagen/metagen_base/metagen_base_types.h index d006a541f..83c837ed1 100644 --- a/src/metagen/metagen_base/metagen_base_types.h +++ b/src/metagen/metagen_base/metagen_base_types.h @@ -86,8 +86,14 @@ #if COMPILER_CL # define Trap() __debugbreak() -#elif COMPILER_CLANG || COMPILER_GCC -# define Trap() __builtin_trap() +#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 "undefined trap" #endif diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c index 8d5f84ec7..8fcbf352b 100644 --- a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -1714,9 +1714,11 @@ internal B32 os_make_directory(String8 path) { B32 result = 0; - if (mkdir((char*)path.str, 0777) != -1){ - result = 1; - } + mkdir((char*)path.str, 0770); + DIR* dirfd = opendir((char*)path.str); + result = (ENOENT != errno); + closedir(dirfd); + return(result); } 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/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index bb085c3b1..ef97c84ee 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -6,7 +6,7 @@ global pthread_mutex_t lnx_mutex = {0}; global Arena *lnx_perm_arena = 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; @@ -1478,8 +1478,8 @@ os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) internal void os_file_write(OS_Handle file, Rng1U64 rng, void *data) { - // Zero Valid Argument - if ((*file.u64 == 0) || (data == NULL) || (rng.min - rng.max == 0)) { return; } + // 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); @@ -1790,9 +1790,11 @@ internal B32 os_make_directory(String8 path) { B32 result = 0; - if (mkdir((char*)path.str, 0777) != -1){ - result = 1; - } + mkdir((char*)path.str, 0770); + DIR* dirfd = opendir((char*)path.str); + result = (ENOENT != errno); + closedir(dirfd); + return(result); } @@ -1943,8 +1945,11 @@ os_now_microseconds(void){ } 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); } //////////////////////////////// @@ -2124,12 +2129,10 @@ os_rw_mutex_alloc(void) // 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); - } + Assert(pthread_result == 0); + LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Rwlock); + entity->rwlock = rwlock; + *result.u64 = IntFromPtr(entity); return result; } @@ -2160,7 +2163,7 @@ os_rw_mutex_drop_r_(OS_Handle mutex) internal void os_rw_mutex_take_w_(OS_Handle mutex) { - LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Rwlock); + LNX_Entity* entity = lnx_entity_from_handle(mutex, LNX_EntityKind_Rwlock); pthread_rwlock_wrlock(&(entity->rwlock)); } @@ -2179,17 +2182,21 @@ os_condition_variable_alloc(void){ LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_ConditionVariable); // pthread - pthread_condattr_t attr; - pthread_condattr_init(&attr); + pthread_condattr_t attr_cond; + pthread_mutexattr_t attr_mutex; + // 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; - } + 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)}; @@ -2199,7 +2206,8 @@ os_condition_variable_alloc(void){ internal void os_condition_variable_release(OS_Handle cv){ LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.u64[0]); - pthread_cond_destroy(&entity->cond); + pthread_cond_destroy(&entity->condition_variable.cond); + pthread_mutex_destroy(&entity->condition_variable.mutex); lnx_free_entity(entity); } @@ -2208,54 +2216,105 @@ os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ 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 = lnx_now_precision_timespec(); - timeout_stamp.tv_nsec += endt_us * 1000; + 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 - pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex, &timeout_stamp); + 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) { + // 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; - LNX_Entity *entity_cond = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); - LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex_rw, LNX_EntityKind_Mutex); - 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); + 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) { - B32 result = 0; - LNX_Entity *entity_cond = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); - LNX_Entity *entity_mutex = lnx_entity_from_handle(mutex_rw, LNX_EntityKind_Mutex); - 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); + // 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_from_handle(cv, LNX_EntityKind_ConditionVariable); - pthread_cond_signal(&entity->cond); + pthread_cond_signal(&entity->condition_variable.cond); } internal void os_condition_variable_broadcast_(OS_Handle cv) { LNX_Entity *entity = lnx_entity_from_handle(cv, LNX_EntityKind_ConditionVariable); - pthread_cond_broadcast(&entity->cond); + pthread_cond_broadcast(&entity->condition_variable.cond); } //- rjf: cross-process semaphores diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index 742deee28..8fe7ac002 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -96,9 +96,12 @@ struct LNX_Entity{ U64 size; String8 shm_name; } map; + struct{ + LNX_cond cond; + LNX_mutex mutex; + } condition_variable; LNX_mutex mutex; LNX_rwlock rwlock; - LNX_cond cond; }; }; diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index e3e79c396..0a17b8a2d 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -6,6 +6,10 @@ 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 @@ -16,6 +20,8 @@ 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}; @@ -33,8 +39,8 @@ global S32 gfx_egl_config[] = { EGLConfig gfx_egl_config_available[10]; global S32 gfx_egl_config_available_size = 0; global S32 gfx_egl_context_config[] = { - EGL_CONTEXT_CLIENT_VERSION, - 2, + EGL_CONTEXT_MAJOR_VERSION, gfx_opengl_version_major, + EGL_CONTEXT_MINOR_VERSION, gfx_opengl_version_minor, EGL_NONE }; @@ -72,6 +78,7 @@ 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}; @@ -93,6 +100,85 @@ gfx_handle_from_window(GFX_LinuxWindow* 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) { @@ -109,6 +195,9 @@ os_graphical_init(void) 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); } @@ -190,14 +279,7 @@ os_window_first_paint(OS_Handle window) internal void os_window_equip_repaint(OS_Handle window, OS_WindowRepaintFunctionType *repaint, void *user_data) { - GFX_LinuxWindow* _window = gfx_window_from_handle(window); - Vec4F32 dark_magenta = vec_4f32( 0.2f, 0.f, 0.2f, 1.0f ); - eglMakeCurrent(gfx_egl_display, _window->first_surface, _window->first_surface, gfx_egl_context); - /* glBindFramebuffer(GL_FRAMEBUFFER, 0); */ - 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 ); - S32 swap_result = eglSwapBuffers(gfx_egl_display, _window->first_surface); } internal void @@ -268,7 +350,7 @@ os_window_set_monitor(OS_Handle window, OS_Handle monitor) internal void os_window_clear_custom_border_data(OS_Handle handle) { - // NOTE(mallchad): No practical use yet4 + // NOTE(mallchad): No practical use yet NoOp; } diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index d1ce55e77..f8f5414a2 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -95,6 +95,10 @@ 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; GFX_LinuxMonitor* gfx_monitor_from_handle(OS_Handle monitor); OS_Handle gfx_handle_from_monitor(GFX_LinuxMonitor* monitor); diff --git a/src/os/gfx/linux/os_gfx_x11.c b/src/os/gfx/linux/os_gfx_x11.c index 04f425170..b810ba345 100644 --- a/src/os/gfx/linux/os_gfx_x11.c +++ b/src/os/gfx/linux/os_gfx_x11.c @@ -269,9 +269,51 @@ x11_window_open(GFX_LinuxContext* out, OS_Handle* out_handle, 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; @@ -285,7 +327,7 @@ x11_window_open(GFX_LinuxContext* out, OS_Handle* out_handle, OS_Key x11_oskey_from_keycode(U32 keycode) { - U32 table_size = sizeof(x11_keysym); + U32 table_size = ArrayCount(x11_keysym); U32 x_keycode = 0; for (int i=0; i +// 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 @@ -224,6 +227,7 @@ enum x11_atoms 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, @@ -256,6 +260,7 @@ global char* x11_test_atoms[] = "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_BELOW", "_NET_WM_STATE_DEMANDS_ATTENTION", + "_NET_WM_ICON", "XdndTypeList", "XdndSelection", "XdndEnter", 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..32dc7544e --- /dev/null +++ b/src/render/opengl/generated/render_opengl.meta.h @@ -0,0 +1,798 @@ +// 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); + +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", +}; + +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; +}; +}; +}; + +#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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 index 8f3967d2a..492ee009c 100644 --- a/src/render/opengl/render_opengl.c +++ b/src/render/opengl/render_opengl.c @@ -1,39 +1,246 @@ +#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... -r_hook void -r_init(CmdLine *cmdln) +// -- 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; 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_hook void r_buffer_release(R_Handle buffer); +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); -r_hook void r_window_end_frame(OS_Handle window, R_Handle window_equip); +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) +{ + Vec4F32 dark_magenta = vec_4f32( 0.2f, 0.f, 0.2f, 1.0f ); +#if OS_LINUX + GFX_LinuxWindow* _window = gfx_window_from_handle(window); + + eglMakeCurrent(gfx_egl_display, _window->first_surface, _window->first_surface, gfx_egl_context); + /* glBindFramebuffer(GL_FRAMEBUFFER, 0); */ + 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 ); + + // Enable vsync + S32 vsync_result = eglSwapInterval(gfx_egl_display, 1); + S32 swap_result = eglSwapBuffers(gfx_egl_display, _window->first_surface); + +#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 +} //- rjf: render pass submission -r_hook void r_window_submit(OS_Handle window, R_Handle window_equip, R_PassList *passes); +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..64f65984b --- /dev/null +++ b/src/render/opengl/render_opengl.h @@ -0,0 +1,145 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef RENDER_OPENGL_H +#define RENDER_OPENGL_H + +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; +}; + +typedef struct R_GLProgram R_GLProgram; +struct R_GLProgram +{ + /// 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; + String8 name; +}; + +/// A single stage of a shader +typedef struct R_GLShader R_GLShader;; +struct R_GLShader +{ + OS_Guid id; + U32 index; + U32 handle; + /// The type of shader stage, ie 'GL_FRAGMENT_SHADER' + U32 kind; + U32 usage_pattern; + String8 name; + String8 source; + B32 source_file; +}; +DeclareArray(R_GLBufferArray, R_GLBufferArray); + +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; +}; +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; + /// How 'data' is formatted + R_Tex2DFormat format; + /// How to store the data on the GPU side + R_Tex2DFormat format_internal; + U32 usage_pattern; + String8 name; + String8 source; +}; +DeclareArray(R_GLTextureArray, R_GLTexture); + +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; +}; + +/** 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 +}; + +//- 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..9c25c09d7 --- /dev/null +++ b/src/render/opengl/render_opengl.mdesk @@ -0,0 +1,701 @@ +//////////////////////////////// +//~ 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"} +} + +@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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_finalize_common_src: +""" +#version 330 core +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define float3x3 mat3 +#define float4x4 mat4 +""" + +@embed_string r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_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 r_ogl_g_mesh_common_src: +""" +#version 330 core +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define float3x3 mat3 +#define float4x4 mat4 +""" + +@embed_string r_ogl_g_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 r_ogl_g_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 r_ogl_g_geo3dcomposite_common_src: +""" +#version 330 core +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 +#define float3x3 mat3 +#define float4x4 mat4 +""" + +@embed_string r_ogl_g_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 r_ogl_g_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 37aff7216..828aa7276 100644 --- a/src/render/render_inc.h +++ b/src/render/render_inc.h @@ -9,12 +9,15 @@ #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 +#elif !defined(R_BACKEND) && OS_LINUX + #define R_BACKEND R_BACKEND_OPENGL #else #define R_BACKEND R_BACKEND_STUB #endif @@ -28,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 From 6b6fdf7f7ad9ea4da90eb5e6111f20b8f78ce750 Mon Sep 17 00:00:00 2001 From: Mallchad Date: Wed, 23 Apr 2025 23:30:47 +0100 Subject: [PATCH 30/30] Checkpoint: Working on Linux/OpenGL graphical stuff [OS Linux] Tweak: Made 'os_reserve' return 0 on an error --- src/base/base_core.c | 15 +- src/base/base_core.h | 10 +- src/os/core/linux/os_core_linux.c | 4 + src/os/core/linux/os_core_linux.h | 1 + src/os/gfx/linux/os_gfx_linux.c | 9 + src/os/gfx/linux/os_gfx_linux.h | 1 + src/os/gfx/linux/os_gfx_x11.c | 6 +- .../opengl/generated/render_opengl.meta.h | 37 ++- src/render/opengl/render_opengl.c | 267 +++++++++++++++++- src/render/opengl/render_opengl.h | 97 +++++-- src/render/opengl/render_opengl.mdesk | 33 ++- 11 files changed, 415 insertions(+), 65 deletions(-) diff --git a/src/base/base_core.c b/src/base/base_core.c index d0237b4e8..ac4b099af 100644 --- a/src/base/base_core.c +++ b/src/base/base_core.c @@ -590,29 +590,32 @@ _ArrayGetTailIndex(GenericArray* target) B32 _ArrayGetTail(GenericArray* target, void* out, U32 item_size) { - if (target->head_size <= 0) { return 0; } + 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; } -B32 +void* _ArrayPushTail(GenericArray* target, void* item, U32 item_size) { - if (target->fast_bounded && (target->head + target->head_size >= target->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)); - MemoryCopy(new_tail, item, item_size); + + // Return a new object + if (item != NULL) + { MemoryCopy(new_tail, item, item_size); } ++(target->head_size); - return 1; + return new_tail; } B32 _ArrayPopTail(GenericArray* target, void* out, U32 item_size) { - if (target->fast_bounded && (target->head_size <= 0)) + if (target->fast_unbounded && (target->head_size <= 0)) { return 0; } U8* tail = target->data; tail += (target->head + ((target->head_size -1) * item_size)); diff --git a/src/base/base_core.h b/src/base/base_core.h index 8717c4008..540b18cac 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -833,7 +833,7 @@ struct GenericArray U64 head; U64 head_size; U32 item_size; - B32 fast_bounded; + B32 fast_unbounded; }; /// Declare a typed dynamic array class @@ -847,7 +847,7 @@ struct GenericArray U64 head; \ U64 head_size; \ U32 item_size; \ - B32 fast_bounded; \ + B32 fast_unbounded; \ }; B32 ArrayAllocate(GenericArray* target, struct Arena* arena, U64 size, U32 item_size); @@ -866,8 +866,9 @@ 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 -B32 _ArrayPushTail(GenericArray* target, void* item, U32 item_size); +/// 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])) @@ -876,6 +877,7 @@ 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); diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index ef97c84ee..a97697e2d 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1064,6 +1064,9 @@ os_init(void) internal void* os_reserve(U64 size){ void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + if(result == MAP_FAILED) + { result = 0; } + return(result); } @@ -1447,6 +1450,7 @@ os_file_open(OS_AccessFlags flags, String8 path) internal void os_file_close(OS_Handle file) { + if(os_handle_match(file, os_handle_zero())) { return; } S32 fd = *file.u64; close(fd); } diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index 8fe7ac002..9d8fee125 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include diff --git a/src/os/gfx/linux/os_gfx_linux.c b/src/os/gfx/linux/os_gfx_linux.c index 0a17b8a2d..825a0b904 100644 --- a/src/os/gfx/linux/os_gfx_linux.c +++ b/src/os/gfx/linux/os_gfx_linux.c @@ -41,6 +41,9 @@ 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 }; @@ -261,6 +264,12 @@ os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title) 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 diff --git a/src/os/gfx/linux/os_gfx_linux.h b/src/os/gfx/linux/os_gfx_linux.h index f8f5414a2..b881fe4f4 100644 --- a/src/os/gfx/linux/os_gfx_linux.h +++ b/src/os/gfx/linux/os_gfx_linux.h @@ -99,6 +99,7 @@ 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); diff --git a/src/os/gfx/linux/os_gfx_x11.c b/src/os/gfx/linux/os_gfx_x11.c index b810ba345..79e486770 100644 --- a/src/os/gfx/linux/os_gfx_x11.c +++ b/src/os/gfx/linux/os_gfx_x11.c @@ -222,7 +222,6 @@ x11_graphical_init(GFX_LinuxContext* out) // Try to populate monitors interally for searchability B32 success_repopulate = x11_repopulate_monitors(); Assert(success_repopulate); // Application will behave strangely if can't get monitor properties - return 1; } @@ -257,7 +256,7 @@ x11_window_open(GFX_LinuxContext* out, OS_Handle* out_handle, if (title.size) { XStoreName(x11_server, new_window, (char*)title.str); } else - { XStoreName(x11_server, new_window, (char*)out->default_window_name.str); } + { 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 ] }; @@ -369,7 +368,8 @@ x11_get_events(Arena *arena, B32 wait, OS_EventList* out) } x_window = x_window->next; } - Assert(window != NULL); // Something has gone horribly wrong if no match is found + 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 diff --git a/src/render/opengl/generated/render_opengl.meta.h b/src/render/opengl/generated/render_opengl.meta.h index 32dc7544e..7785fff6f 100644 --- a/src/render/opengl/generated/render_opengl.meta.h +++ b/src/render/opengl/generated/render_opengl.meta.h @@ -60,6 +60,7 @@ 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); @@ -114,6 +115,8 @@ 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[] = { @@ -171,6 +174,8 @@ const char* rgl_function_names[] = "glUniformMatrix4fv", "glGetUniformLocation", "glDepthFunc", +"glGetString", +"glGetStringi", }; typedef struct R_GLProcFunctions R_GLProcFunctions; @@ -235,6 +240,8 @@ PFNGL_Uniform2f Uniform2f; PFNGL_UniformMatrix4fv UniformMatrix4fv; PFNGL_GetUniformLocation GetUniformLocation; PFNGL_DepthFunc DepthFunc; +PFNGL_GetString GetString; +PFNGL_GetStringi GetStringi; }; }; }; @@ -306,7 +313,7 @@ PFNGL_DepthFunc DepthFunc; C_LINKAGE_BEGIN -read_only global String8 r_ogl_g_rect_common_src = +read_only global String8 rgl_rect_common_src = str8_lit_comp( "" "\n" @@ -338,7 +345,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_rect_vs_src = +read_only global String8 rgl_rect_vs_src = str8_lit_comp( "" "\n" @@ -427,7 +434,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_rect_fs_src = +read_only global String8 rgl_rect_fs_src = str8_lit_comp( "" "\n" @@ -501,7 +508,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_finalize_common_src = +read_only global String8 rgl_finalize_common_src = str8_lit_comp( "" "\n" @@ -514,7 +521,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_finalize_vs_src = +read_only global String8 rgl_finalize_vs_src = str8_lit_comp( "" "\n" @@ -534,7 +541,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_finalize_fs_src = +read_only global String8 rgl_finalize_fs_src = str8_lit_comp( "" "\n" @@ -554,7 +561,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_blur_common_src = +read_only global String8 rgl_blur_common_src = str8_lit_comp( "" "\n" @@ -579,7 +586,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_blur_vs_src = +read_only global String8 rgl_blur_vs_src = str8_lit_comp( "" "\n" @@ -629,7 +636,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_blur_fs_src = +read_only global String8 rgl_blur_fs_src = str8_lit_comp( "" "\n" @@ -681,7 +688,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_mesh_common_src = +read_only global String8 rgl_mesh_common_src = str8_lit_comp( "" "\n" @@ -694,7 +701,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_mesh_vs_src = +read_only global String8 rgl_mesh_vs_src = str8_lit_comp( "" "\n" @@ -720,7 +727,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_mesh_fs_src = +read_only global String8 rgl_mesh_fs_src = str8_lit_comp( "" "\n" @@ -739,7 +746,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_geo3dcomposite_common_src = +read_only global String8 rgl_geo3dcomposite_common_src = str8_lit_comp( "" "\n" @@ -752,7 +759,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_geo3dcomposite_vs_src = +read_only global String8 rgl_geo3dcomposite_vs_src = str8_lit_comp( "" "\n" @@ -772,7 +779,7 @@ str8_lit_comp( "" ); -read_only global String8 r_ogl_g_geo3dcomposite_fs_src = +read_only global String8 rgl_geo3dcomposite_fs_src = str8_lit_comp( "" "\n" diff --git a/src/render/opengl/render_opengl.c b/src/render/opengl/render_opengl.c index 492ee009c..9436a26a3 100644 --- a/src/render/opengl/render_opengl.c +++ b/src/render/opengl/render_opengl.c @@ -61,6 +61,162 @@ rgl_read_format_from_texture_format(U32* out_format, U32* out_type, R_Tex2DForma return 1; } +U32 +rgl_clear_errors() +{ + U32 count = 0; + while (glGetError() != GL_NO_ERROR) { count++; } + glGetError(); + glGetError(); + glGetError(); + return count; +} + +B32 +rgl_check_error( String8 source_file, U32 source_line ) +{ + char* error_message; + GLenum error = glGetError(); + switch (error) + { + case GL_NO_ERROR: + error_message = "GL_NO_ERROR"; break; + case GL_INVALID_ENUM: + error_message = "GL_INVALID_ENUM"; break; + case GL_INVALID_VALUE: + error_message = "GL_INVALID_VALUE"; break; + case GL_INVALID_OPERATION: + error_message = "GL_INVALID_OPERATION"; break; + case GL_STACK_OVERFLOW: + error_message = "GL_STACK_OVERFLOW"; break; + case GL_STACK_UNDERFLOW: + error_message = "GL_STACK_UNDERFLOW"; break; + case GL_OUT_OF_MEMORY: + error_message = "GL_OUT_OF_MEMORY"; break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + error_message = "GL_INVALID_FRAMEBUFFER_OPERATION"; break; + case GL_CONTEXT_LOST: + error_message = "GL_CONTEXT_LOST"; break; + case GL_TABLE_TOO_LARGE: + error_message = "GL_TABLE_TOO_LARGE"; break; + default: + error_message = "Unknown Error"; break; + } + if (error != GL_NO_ERROR) + { + printf("%s:%d: OpenGL Error Status: %s\n", source_file.str, source_line, error_message); + } + return error == GL_NO_ERROR; +} + +B32 +rgl_shader_init(R_GLShader* shader) +{ + return 0; +} + +B32 +rgl_pipeline_init(R_GLPipeline* pipeline) +{ + return 0; +} + +R_GLPipeline* +rgl_pipeline_simple_create(String8 name, + String8 source_include, + String8 source_vertex, + String8 source_fragment) +{ + R_GLPipeline* pipeline = ArrayPushTail(&rgl.pipelines, NULL); + pipeline->index = 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 @@ -68,6 +224,8 @@ 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); @@ -76,8 +234,11 @@ r_init(CmdLine *cmdln) 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 + // -- Load dynamic function pointers -- void* func_ptr = NULL; for (int i=0; ifirst_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); - eglMakeCurrent(gfx_egl_display, _window->first_surface, _window->first_surface, gfx_egl_context); - /* glBindFramebuffer(GL_FRAMEBUFFER, 0); */ + 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 @@ -234,8 +485,12 @@ r_window_end_frame(OS_Handle window, R_Handle window_equip) 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 ); + 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 diff --git a/src/render/opengl/render_opengl.h b/src/render/opengl/render_opengl.h index 64f65984b..1010b8ca2 100644 --- a/src/render/opengl/render_opengl.h +++ b/src/render/opengl/render_opengl.h @@ -4,6 +4,15 @@ #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 { @@ -19,34 +28,47 @@ struct R_GLBuffer B32 bound; String8 name; }; +DeclareArray(R_GLBufferArray, R_GLBufferArray); -typedef struct R_GLProgram R_GLProgram; -struct R_GLProgram +/// A single stage of a shader +typedef struct R_GLShader R_GLShader; +struct R_GLShader { - /// 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; 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 single stage of a shader -typedef struct R_GLShader R_GLShader;; -struct 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; - /// The type of shader stage, ie 'GL_FRAGMENT_SHADER' - U32 kind; - U32 usage_pattern; String8 name; - String8 source; - B32 source_file; + 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_GLBufferArray, R_GLBufferArray); +DeclareArray(R_GLPipelineArray, R_GLPipeline); typedef struct R_GLVertexArray R_GLVertexArray; struct R_GLVertexArray @@ -57,6 +79,10 @@ struct R_GLVertexArray U32 index; // Internal OpenGL handle U32 handle; + U8* data; + U32 vertex_components; + U32 format; + B32 normalized; }; DeclareArray(R_GLVertexArrayArray, R_GLVertexArray); @@ -68,16 +94,30 @@ struct R_GLTexture U32 handle; void* data; Vec2S32 size; - /// How 'data' is formatted + /// 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; - String8 source; + // 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 { @@ -94,6 +134,16 @@ struct R_GLContext 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 @@ -113,6 +163,21 @@ U32 rgl_texture_formats[] = 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); diff --git a/src/render/opengl/render_opengl.mdesk b/src/render/opengl/render_opengl.mdesk index 9c25c09d7..a1a5af733 100644 --- a/src/render/opengl/render_opengl.mdesk +++ b/src/render/opengl/render_opengl.mdesk @@ -56,6 +56,8 @@ {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: @@ -182,6 +184,7 @@ `typedef khronos_int64_t GLint64;` `typedef khronos_uint64_t GLuint64EXT;` `typedef khronos_int64_t GLint64EXT;` +` } @gen{ `` } @@ -250,7 +253,7 @@ //////////////////////////////// //~ dmylo: UI Rectangle Shaders -@embed_string r_ogl_g_rect_common_src: +@embed_string rgl_rect_common_src: """ #version 330 core #define float2 vec2 @@ -279,7 +282,7 @@ layout (std140) uniform Globals }; """ -@embed_string r_ogl_g_rect_vs_src: +@embed_string rgl_rect_vs_src: """ layout (location=0) in float4 a_dst_rect_px; layout (location=1) in float4 a_src_rect_px; @@ -365,7 +368,7 @@ void main() } """ -@embed_string r_ogl_g_rect_fs_src: +@embed_string rgl_rect_fs_src: """ in Vertex2Pixel { @@ -438,7 +441,7 @@ void main() //////////////////////////////// //~ dmylo: UI Finalize Shaders -@embed_string r_ogl_g_finalize_common_src: +@embed_string rgl_finalize_common_src: """ #version 330 core #define float2 vec2 @@ -448,7 +451,7 @@ void main() #define float4x4 mat4 """ -@embed_string r_ogl_g_finalize_vs_src: +@embed_string rgl_finalize_vs_src: """ out Vertex2Pixel { @@ -465,7 +468,7 @@ void main() } """ -@embed_string r_ogl_g_finalize_fs_src: +@embed_string rgl_finalize_fs_src: """ in Vertex2Pixel { @@ -484,7 +487,7 @@ void main() //////////////////////////////// //~ dmylo: Blur Shaders -@embed_string r_ogl_g_blur_common_src: +@embed_string rgl_blur_common_src: """ #version 330 core #define float2 vec2 @@ -506,7 +509,7 @@ layout (std140) uniform Globals }; """ -@embed_string r_ogl_g_blur_vs_src: +@embed_string rgl_blur_vs_src: """ out Vertex2Pixel { @@ -553,7 +556,7 @@ void main() } """ -@embed_string r_ogl_g_blur_fs_src: +@embed_string rgl_blur_fs_src: """ in Vertex2Pixel { @@ -605,7 +608,7 @@ void main() //////////////////////////////// //~ dmylo: Mesh Shaders -@embed_string r_ogl_g_mesh_common_src: +@embed_string rgl_mesh_common_src: """ #version 330 core #define float2 vec2 @@ -615,7 +618,7 @@ void main() #define float4x4 mat4 """ -@embed_string r_ogl_g_mesh_vs_src: +@embed_string rgl_mesh_vs_src: """ uniform mat4 xform; @@ -638,7 +641,7 @@ void main() } """ -@embed_string r_ogl_g_mesh_fs_src: +@embed_string rgl_mesh_fs_src: """ in Vertex2Pixel { @@ -656,7 +659,7 @@ void main() //////////////////////////////// //~ dmylo: Geo3D Composition Shaders -@embed_string r_ogl_g_geo3dcomposite_common_src: +@embed_string rgl_geo3dcomposite_common_src: """ #version 330 core #define float2 vec2 @@ -666,7 +669,7 @@ void main() #define float4x4 mat4 """ -@embed_string r_ogl_g_geo3dcomposite_vs_src: +@embed_string rgl_geo3dcomposite_vs_src: """ out Vertex2Pixel { @@ -683,7 +686,7 @@ void main() } """ -@embed_string r_ogl_g_geo3dcomposite_fs_src: +@embed_string rgl_geo3dcomposite_fs_src: """ in Vertex2Pixel {