Commit: a3edbfbb675ec3f06797d6bb68612ca7dde63c66
Parent: f108b6dcfc00fe769911babdd6b206f0319615d1
Author: Randy Palamar
Date: Tue, 21 Jan 2025 11:10:38 -0700
core: automatic hot reloading
* shaders reload on file save
* w32 debug reloading now works without jank
Diffstat:
M | beamformer.c | | | 17 | +++++++++-------- |
M | beamformer.h | | | 4 | ++-- |
M | build.sh | | | 6 | +++--- |
D | main_generic.c | | | 80 | ------------------------------------------------------------------------------- |
A | main_linux.c | | | 110 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main_w32.c | | | 150 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | os_unix.c | | | 51 | +++++++++++++++++++++++++++++++-------------------- |
M | os_win32.c | | | 87 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
M | static.c | | | 233 | +++++++++++++++++++++++++++++++++++++++++++++---------------------------------- |
M | ui.c | | | 2 | +- |
M | util.c | | | 72 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- |
M | util.h | | | 40 | +++++++++++++++++++++++++++++++++++++--- |
12 files changed, 610 insertions(+), 242 deletions(-)
diff --git a/beamformer.c b/beamformer.c
@@ -599,7 +599,7 @@ do_beamform_work(BeamformerCtx *ctx, Arena *a)
work->compute_ctx.export_handle = INVALID_FILE;
}
- ctx->flags |= GEN_MIPMAPS;
+ ctx->fsctx.gen_mipmaps = 1;
} break;
}
@@ -671,6 +671,12 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step)
ui_init(ctx, ctx->ui_backing_store);
}
+ if (ctx->flags & START_COMPUTE) {
+ if (ui_can_start_compute(ctx))
+ ui_start_compute(ctx);
+ ctx->flags &= ~START_COMPUTE;
+ }
+
/* NOTE: Store the compute time for the last frame. */
check_compute_timers(&ctx->csctx, &ctx->partial_compute_ctx, ctx->params);
@@ -767,21 +773,16 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step)
EndTextureMode();
/* NOTE: regenerate mipmaps only when the output has actually changed */
- if (ctx->flags & GEN_MIPMAPS) {
+ if (ctx->fsctx.gen_mipmaps) {
/* NOTE: shut up raylib's reporting on mipmap gen */
SetTraceLogLevel(LOG_NONE);
GenTextureMipmaps(&ctx->fsctx.output.texture);
SetTraceLogLevel(LOG_INFO);
- ctx->flags &= ~GEN_MIPMAPS;
+ ctx->fsctx.gen_mipmaps = 0;
}
draw_ui(ctx, input, frame_to_draw);
- if (IsKeyPressed(KEY_R)) {
- ctx->flags |= RELOAD_SHADERS;
- if (ui_can_start_compute(ctx))
- ui_start_compute(ctx);
- }
if (WindowShouldClose())
ctx->flags |= SHOULD_EXIT;
}
diff --git a/beamformer.h b/beamformer.h
@@ -35,8 +35,7 @@
enum program_flags {
SHOULD_EXIT = 1 << 0,
- RELOAD_SHADERS = 1 << 1,
- GEN_MIPMAPS = 1 << 30,
+ START_COMPUTE = 1 << 1,
};
enum gl_vendor_ids {
@@ -221,6 +220,7 @@ typedef struct {
i32 threshold_id;
f32 db;
f32 threshold;
+ b32 gen_mipmaps;
} FragmentShaderCtx;
typedef struct {
diff --git a/build.sh b/build.sh
@@ -1,5 +1,4 @@
#!/bin/sh
-set -e
cflags="-march=native -std=c11 -O3 -Wall -I./external/include"
#cflags="${cflags} -fproc-stat-report"
@@ -10,7 +9,6 @@ ldflags="-lm"
debug=${DEBUG}
cc=${CC:-cc}
-main=main_generic.c
case $(uname -sm) in
MINGW64*)
@@ -20,6 +18,7 @@ MINGW64*)
extra_ldflags="-llibmat -llibmex"
fi
libname="beamformer.dll"
+ main="main_w32.c"
${cc} $libcflags helpers/ogl_beamformer_lib.c -o helpers/ogl_beamformer_lib.dll \
-L'C:/Program Files/MATLAB/R2022a/extern/lib/win64/microsoft' \
$extra_ldflags
@@ -27,6 +26,7 @@ MINGW64*)
Linux*)
cflags="$cflags -D_DEFAULT_SOURCE"
libname="beamformer.so"
+ main="main_linux.c"
${cc} $libcflags helpers/ogl_beamformer_lib.c -o helpers/ogl_beamformer_lib.so
;;
esac
@@ -64,4 +64,4 @@ else
ldflags="./external/lib/libraylib.a ${ldflags}"
fi
-${cc} $cflags -o ogl $main $ldflags
+${cc} ${cflags} -o ogl ${main} ${ldflags}
diff --git a/main_generic.c b/main_generic.c
@@ -1,80 +0,0 @@
-/* See LICENSE for license details. */
-#include "beamformer.h"
-
-#if defined(__unix__)
- #include "os_unix.c"
-
- #define OS_DEBUG_LIB_NAME "./beamformer.so"
- #define OS_DEBUG_LIB_TEMP_NAME "./beamformer_temp.so"
-
- #define OS_CUDA_LIB_NAME "./external/cuda_toolkit.so"
- #define OS_CUDA_LIB_TEMP_NAME "./external/cuda_toolkit_temp.so"
-
- #define OS_PIPE_NAME "/tmp/beamformer_data_fifo"
- #define OS_SMEM_NAME "/ogl_beamformer_parameters"
-
-#elif defined(_WIN32)
- #include "os_win32.c"
-
- #define OS_DEBUG_LIB_NAME "beamformer.dll"
- #define OS_DEBUG_LIB_TEMP_NAME "beamformer_temp.dll"
-
- #define OS_CUDA_LIB_NAME "external\\cuda_toolkit.dll"
- #define OS_CUDA_LIB_TEMP_NAME "external\\cuda_toolkit_temp.dll"
-
- #define OS_PIPE_NAME "\\\\.\\pipe\\beamformer_data_fifo"
- #define OS_SMEM_NAME "Local\\ogl_beamformer_parameters"
-#else
- #error Unsupported Platform!
-#endif
-
-#include "static.c"
-
-int
-main(void)
-{
- BeamformerCtx ctx = {0};
- BeamformerInput input = {0};
- Arena temp_memory = os_alloc_arena((Arena){0}, 16 * MEGABYTE);
- ctx.error_stream = stream_alloc(&temp_memory, 1 * MEGABYTE);
-
- ctx.ui_backing_store = sub_arena(&temp_memory, 2 * MEGABYTE);
-
- Pipe data_pipe = os_open_named_pipe(OS_PIPE_NAME);
- input.pipe_handle = data_pipe.file;
- ASSERT(data_pipe.file != INVALID_FILE);
-
- input.executable_reloaded = 1;
-
- #define X(name) ctx.platform.name = os_ ## name;
- PLATFORM_FNS
- #undef X
-
- setup_beamformer(&ctx, temp_memory);
-
- while(!(ctx.flags & SHOULD_EXIT)) {
- do_debug(&input, &ctx.error_stream);
- if (ctx.gl.vendor_id == GL_VENDOR_NVIDIA)
- check_and_load_cuda_lib(&ctx.cuda_lib, &ctx.error_stream);
-
- if (ctx.flags & RELOAD_SHADERS) {
- ctx.flags &= ~RELOAD_SHADERS;
- reload_shaders(&ctx, temp_memory);
- }
-
- input.last_mouse = input.mouse;
- input.mouse.rl = GetMousePosition();
- input.pipe_data_available = os_poll_pipe(data_pipe);
-
- beamformer_frame_step(&ctx, &temp_memory, &input);
-
- input.executable_reloaded = 0;
- }
-
- /* NOTE: make sure this will get cleaned up after external
- * programs release their references */
- os_remove_shared_memory(OS_SMEM_NAME);
-
- /* NOTE: garbage code needed for Linux */
- os_close_named_pipe(data_pipe);
-}
diff --git a/main_linux.c b/main_linux.c
@@ -0,0 +1,110 @@
+/* See LICENSE for license details. */
+#ifndef __linux__
+#error This file is only meant to be compiled for Linux
+#endif
+
+#include "beamformer.h"
+
+#include "os_unix.c"
+
+#define OS_DEBUG_LIB_NAME "./beamformer.so"
+#define OS_DEBUG_LIB_TEMP_NAME "./beamformer_temp.so"
+
+#define OS_CUDA_LIB_NAME "./external/cuda_toolkit.so"
+#define OS_CUDA_LIB_TEMP_NAME "./external/cuda_toolkit_temp.so"
+
+#define OS_PIPE_NAME "/tmp/beamformer_data_fifo"
+#define OS_SMEM_NAME "/ogl_beamformer_parameters"
+
+#define OS_PATH_SEPERATOR "/"
+
+#include "static.c"
+
+static void
+dispatch_file_watch_events(FileWatchContext *fwctx, Arena arena)
+{
+ u8 *mem = alloc_(&arena, 4096, 64, 1);
+ Stream path = stream_alloc(&arena, 256);
+ struct inotify_event *event;
+
+ size rlen;
+ while ((rlen = read(fwctx->handle, mem, 4096)) > 0) {
+ for (u8 *data = mem; data < mem + rlen; data += sizeof(*event) + event->len) {
+ event = (struct inotify_event *)data;
+ for (u32 i = 0; i < fwctx->directory_watch_count; i++) {
+ FileWatchDirectory *dir = fwctx->directory_watches + i;
+ if (event->wd != dir->handle)
+ continue;
+
+ s8 file = cstr_to_s8(event->name);
+ u64 hash = s8_hash(file);
+ for (u32 i = 0; i < dir->file_watch_count; i++) {
+ FileWatch *fw = dir->file_watches + i;
+ if (fw->hash == hash) {
+ stream_append_s8(&path, dir->name);
+ stream_append_byte(&path, '/');
+ stream_append_s8(&path, file);
+ stream_append_byte(&path, 0);
+ path.widx--;
+ fw->callback(stream_to_s8(&path),
+ fw->user_data, arena);
+ path.widx = 0;
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+int
+main(void)
+{
+ BeamformerCtx ctx = {0};
+ BeamformerInput input = {.executable_reloaded = 1};
+ Arena temp_memory = os_alloc_arena((Arena){0}, 16 * MEGABYTE);
+ ctx.error_stream = stream_alloc(&temp_memory, 1 * MEGABYTE);
+
+ ctx.ui_backing_store = sub_arena(&temp_memory, 2 * MEGABYTE, 4096);
+
+ Pipe data_pipe = os_open_named_pipe(OS_PIPE_NAME);
+ input.pipe_handle = data_pipe.file;
+ ASSERT(data_pipe.file != INVALID_FILE);
+
+ #define X(name) ctx.platform.name = os_ ## name;
+ PLATFORM_FNS
+ #undef X
+
+ ctx.platform.file_watch_context.handle = inotify_init1(O_NONBLOCK|O_CLOEXEC);
+
+ setup_beamformer(&ctx, &temp_memory);
+ debug_init(&ctx.platform, (iptr)&input, &temp_memory);
+
+ struct pollfd fds[2] = {{0}, {0}};
+ fds[0].fd = ctx.platform.file_watch_context.handle;
+ fds[0].events = POLLIN;
+ fds[1].fd = data_pipe.file;
+ fds[1].events = POLLIN;
+
+ while (!(ctx.flags & SHOULD_EXIT)) {
+ poll(fds, 2, 0);
+ if (fds[0].revents & POLLIN)
+ dispatch_file_watch_events(&ctx.platform.file_watch_context, temp_memory);
+
+ input.pipe_data_available = !!(fds[1].revents & POLLIN);
+ input.last_mouse = input.mouse;
+ input.mouse.rl = GetMousePosition();
+
+ beamformer_frame_step(&ctx, &temp_memory, &input);
+
+ input.executable_reloaded = 0;
+ }
+
+ /* NOTE: make sure this will get cleaned up after external
+ * programs release their references */
+ shm_unlink(OS_SMEM_NAME);
+
+ /* NOTE: garbage code needed for Linux */
+ close(data_pipe.file);
+ unlink(data_pipe.name);
+}
diff --git a/main_w32.c b/main_w32.c
@@ -0,0 +1,150 @@
+/* See LICENSE for license details. */
+#ifndef _WIN32
+#error This file is only meant to be compiled for Win32
+#endif
+
+#include "beamformer.h"
+
+typedef struct {
+ iptr io_completion_handle;
+} w32_context;
+
+enum w32_io_events {
+ W32_IO_FILE_WATCH,
+ W32_IO_PIPE,
+};
+
+typedef struct {
+ u64 tag;
+ iptr context;
+} w32_io_completion_event;
+
+#include "os_win32.c"
+
+#define OS_DEBUG_LIB_NAME ".\\beamformer.dll"
+#define OS_DEBUG_LIB_TEMP_NAME ".\\beamformer_temp.dll"
+
+#define OS_CUDA_LIB_NAME "external\\cuda_toolkit.dll"
+#define OS_CUDA_LIB_TEMP_NAME "external\\cuda_toolkit_temp.dll"
+
+#define OS_PIPE_NAME "\\\\.\\pipe\\beamformer_data_fifo"
+#define OS_SMEM_NAME "Local\\ogl_beamformer_parameters"
+
+#define OS_PATH_SEPERATOR "\\"
+
+#include "static.c"
+
+static void
+w32_wide_char_to_mb(Stream *s, u16 *wstr, u32 wide_char_length)
+{
+ /* NOTE(rnp): this assumes the wstr is strictly ASCII */
+ s->errors |= (s->cap - s->widx) < wide_char_length;
+ if (!s->errors) {
+ for (u32 i = 0; i < wide_char_length; i++)
+ s->data[s->widx++] = wstr[i] & 0xFF;
+ }
+}
+
+static void
+dispatch_file_watch(FileWatchDirectory *fw_dir, u8 *buf, Arena arena)
+{
+ i64 offset = 0;
+ Stream path = stream_alloc(&arena, 256);
+ w32_file_notify_info *fni = (w32_file_notify_info *)buf;
+ do {
+ if (fni->action != FILE_ACTION_MODIFIED) {
+ path.widx = 0;
+ stream_append_s8(&path, s8("unknown file watch event: "));
+ stream_append_u64(&path, fni->action);
+ stream_append_byte(&path, '\n');
+ os_write_err_msg(stream_to_s8(&path));
+ }
+
+ path.widx = 0;
+ stream_append_s8(&path, fw_dir->name);
+ stream_append_byte(&path, '\\');
+
+ s8 file_name = {.data = path.data + path.widx, .len = fni->filename_size / 2};
+ w32_wide_char_to_mb(&path, fni->filename, fni->filename_size / 2);
+ stream_append_byte(&path, 0);
+ path.widx--;
+
+ u64 hash = s8_hash(file_name);
+ for (u32 i = 0; i < fw_dir->file_watch_count; i++) {
+ FileWatch *fw = fw_dir->file_watches + i;
+ if (fw->hash == hash) {
+ fw->callback(stream_to_s8(&path),
+ fw->user_data, arena);
+ break;
+ }
+ }
+
+ offset = fni->next_entry_offset;
+ fni = (w32_file_notify_info *)((u8 *)fni + offset);
+ } while (offset);
+}
+
+static void
+clear_io_queue(Platform *platform, BeamformerInput *input, Arena arena)
+{
+ w32_context *ctx = (w32_context *)platform->os_context;
+
+ iptr handle = ctx->io_completion_handle;
+ w32_overlapped *overlapped;
+ u32 bytes_read;
+ uptr user_data;
+ while (GetQueuedCompletionStatus(handle, &bytes_read, &user_data, &overlapped, 0)) {
+ w32_io_completion_event *event = (w32_io_completion_event *)user_data;
+ switch (event->tag) {
+ case W32_IO_FILE_WATCH: {
+ FileWatchDirectory *dir = (FileWatchDirectory *)event->context;
+ dispatch_file_watch(dir, dir->buffer.beg, arena);
+ zero_struct(overlapped);
+ ReadDirectoryChangesW(dir->handle, dir->buffer.beg, 4096, 0,
+ FILE_NOTIFY_CHANGE_LAST_WRITE, 0, overlapped, 0);
+ } break;
+ case W32_IO_PIPE: break;
+ }
+ }
+}
+
+int
+main(void)
+{
+ BeamformerCtx ctx = {0};
+ BeamformerInput input = {.executable_reloaded = 1};
+ Arena temp_memory = os_alloc_arena((Arena){0}, 16 * MEGABYTE);
+ ctx.error_stream = stream_alloc(&temp_memory, 1 * MEGABYTE);
+
+ ctx.ui_backing_store = sub_arena(&temp_memory, 2 * MEGABYTE, 4096);
+
+ Pipe data_pipe = os_open_named_pipe(OS_PIPE_NAME);
+ input.pipe_handle = data_pipe.file;
+ ASSERT(data_pipe.file != INVALID_FILE);
+
+ #define X(name) ctx.platform.name = os_ ## name;
+ PLATFORM_FNS
+ #undef X
+
+ w32_context w32_ctx = {0};
+ w32_ctx.io_completion_handle = CreateIoCompletionPort(INVALID_FILE, 0, 0, 0);
+ ctx.platform.os_context = (iptr)&w32_ctx;
+
+ setup_beamformer(&ctx, &temp_memory);
+ debug_init(&ctx.platform, (iptr)&input, &temp_memory);
+
+ while (!(ctx.flags & SHOULD_EXIT)) {
+ clear_io_queue(&ctx.platform, &input, temp_memory);
+
+ input.last_mouse = input.mouse;
+ input.mouse.rl = GetMousePosition();
+
+ i32 bytes_available = 0;
+ input.pipe_data_available = PeekNamedPipe(data_pipe.file, 0, 1 * MEGABYTE, 0,
+ &bytes_available, 0) && bytes_available;
+
+ beamformer_frame_step(&ctx, &temp_memory, &input);
+
+ input.executable_reloaded = 0;
+ }
+}
diff --git a/os_unix.c b/os_unix.c
@@ -1,9 +1,14 @@
/* See LICENSE for license details. */
+
+/* NOTE(rnp): provides the platform layer for the beamformer. This code must
+ * be provided by any platform the beamformer is ported to. */
+
#include "util.h"
#include <dlfcn.h>
#include <fcntl.h>
#include <poll.h>
+#include <sys/inotify.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -118,20 +123,6 @@ os_open_named_pipe(char *name)
return (Pipe){.file = open(name, O_RDONLY|O_NONBLOCK), .name = name};
}
-static void
-os_close_named_pipe(Pipe p)
-{
- close(p.file);
- unlink(p.name);
-}
-
-static PLATFORM_POLL_PIPE_FN(os_poll_pipe)
-{
- struct pollfd pfd = {.fd = p.file, .events = POLLIN};
- poll(&pfd, 1, 0);
- return !!(pfd.revents & POLLIN);
-}
-
static PLATFORM_READ_PIPE_FN(os_read_pipe)
{
size r = 0, total_read = 0;
@@ -164,12 +155,6 @@ os_open_shared_memory_area(char *name, size cap)
return new;
}
-static void
-os_remove_shared_memory(char *name)
-{
- shm_unlink(name);
-}
-
/* NOTE: complete garbage because there is no standarized copyfile() in POSix */
static b32
os_copy_file(char *name, char *new)
@@ -246,3 +231,29 @@ os_unload_library(void *h)
if (h)
dlclose(h);
}
+
+static PLATFORM_ADD_FILE_WATCH_FN(os_add_file_watch)
+{
+ s8 directory = path;
+ directory.len = s8_scan_backwards(path, '/');
+ ASSERT(directory.len > 0);
+
+ u64 hash = s8_hash(directory);
+ FileWatchContext *fwctx = &platform->file_watch_context;
+ FileWatchDirectory *dir = lookup_file_watch_directory(fwctx, hash);
+ if (!dir) {
+ ASSERT(path.data[directory.len] == '/');
+
+ dir = fwctx->directory_watches + fwctx->directory_watch_count++;
+ dir->hash = hash;
+
+ dir->name = push_s8(a, directory);
+ arena_commit(a, 1);
+ dir->name.data[dir->name.len] = 0;
+
+ i32 mask = IN_MOVED_TO|IN_CLOSE_WRITE;
+ dir->handle = inotify_add_watch(fwctx->handle, (c8 *)dir->name.data, mask);
+ }
+
+ insert_file_watch(dir, s8_cut_head(path, dir->name.len + 1), user_data, callback);
+}
diff --git a/os_win32.c b/os_win32.c
@@ -8,13 +8,21 @@
#define MEM_COMMIT 0x1000
#define MEM_RESERVE 0x2000
#define MEM_RELEASE 0x8000
+
#define GENERIC_WRITE 0x40000000
#define GENERIC_READ 0x80000000
#define PIPE_TYPE_BYTE 0x00
#define PIPE_ACCESS_INBOUND 0x01
-#define FILE_MAP_ALL_ACCESS 0x000F001F
+#define FILE_SHARE_READ 0x00000001
+#define FILE_MAP_ALL_ACCESS 0x000F001F
+#define FILE_FLAG_BACKUP_SEMANTICS 0x02000000
+#define FILE_FLAG_OVERLAPPED 0x40000000
+
+#define FILE_NOTIFY_CHANGE_LAST_WRITE 0x00000010
+
+#define FILE_ACTION_MODIFIED 0x00000003
#define CREATE_ALWAYS 2
#define OPEN_EXISTING 3
@@ -50,11 +58,28 @@ typedef struct __attribute__((packed)) {
u32 nFileIndexLow;
} w32_file_info;
+typedef struct {
+ u32 next_entry_offset;
+ u32 action;
+ u32 filename_size;
+ u16 filename[];
+} w32_file_notify_info;
+
+typedef struct {
+ uptr internal, internal_high;
+ union {
+ struct {u32 off, off_high;};
+ iptr pointer;
+ };
+ iptr event_handle;
+} w32_overlapped;
+
#define W32(r) __declspec(dllimport) r __stdcall
W32(b32) CloseHandle(iptr);
W32(b32) CopyFileA(c8 *, c8 *, b32);
W32(iptr) CreateFileA(c8 *, u32, u32, void *, u32, u32, void *);
W32(iptr) CreateFileMappingA(iptr, void *, u32, u32, u32, c8 *);
+W32(iptr) CreateIoCompletionPort(iptr, iptr, uptr, u32);
W32(iptr) CreateNamedPipeA(c8 *, u32, u32, u32, u32, u32, u32, void *);
W32(b32) DeleteFileA(c8 *);
W32(void) ExitProcess(i32);
@@ -62,11 +87,13 @@ W32(b32) FreeLibrary(void *);
W32(b32) GetFileInformationByHandle(iptr, w32_file_info *);
W32(i32) GetLastError(void);
W32(void *) GetProcAddress(void *, c8 *);
+W32(b32) GetQueuedCompletionStatus(iptr, u32 *, uptr *, w32_overlapped **, u32);
W32(iptr) GetStdHandle(i32);
W32(void) GetSystemInfo(void *);
W32(void *) LoadLibraryA(c8 *);
W32(void *) MapViewOfFile(iptr, u32, u32, u32, u64);
W32(b32) PeekNamedPipe(iptr, u8 *, i32, i32 *, i32 *, i32 *);
+W32(b32) ReadDirectoryChangesW(iptr, u8 *, u32, b32, u32, u32 *, void *, void *);
W32(b32) ReadFile(iptr, u8 *, i32, i32 *, void *);
W32(b32) WriteFile(iptr, u8 *, i32, i32 *, void *);
W32(void *) VirtualAlloc(u8 *, size, u32, u32);
@@ -203,18 +230,6 @@ os_open_named_pipe(char *name)
return (Pipe){.file = h, .name = name};
}
-/* NOTE: win32 doesn't pollute the filesystem so no need to waste the user's time */
-static void
-os_close_named_pipe(Pipe p)
-{
-}
-
-static PLATFORM_POLL_PIPE_FN(os_poll_pipe)
-{
- i32 bytes_available = 0;
- return PeekNamedPipe(p.file, 0, 1 * MEGABYTE, 0, &bytes_available, 0) && bytes_available;
-}
-
static PLATFORM_READ_PIPE_FN(os_read_pipe)
{
i32 total_read = 0;
@@ -232,12 +247,6 @@ os_open_shared_memory_area(char *name, size cap)
return MapViewOfFile(h, FILE_MAP_ALL_ACCESS, 0, 0, cap);
}
-/* NOTE: closing the handle releases the memory and this happens when program terminates */
-static void
-os_remove_shared_memory(char *name)
-{
-}
-
static void *
os_load_library(char *name, char *temp_name, Stream *e)
{
@@ -284,3 +293,43 @@ os_unload_library(void *h)
{
FreeLibrary(h);
}
+
+static PLATFORM_ADD_FILE_WATCH_FN(os_add_file_watch)
+{
+ s8 directory = path;
+ directory.len = s8_scan_backwards(path, '\\');
+ ASSERT(directory.len > 0);
+
+ u64 hash = s8_hash(directory);
+ FileWatchContext *fwctx = &platform->file_watch_context;
+ FileWatchDirectory *dir = lookup_file_watch_directory(fwctx, hash);
+ if (!dir) {
+ ASSERT(path.data[directory.len] == '\\');
+
+ dir = fwctx->directory_watches + fwctx->directory_watch_count++;
+ dir->hash = hash;
+
+ dir->name = push_s8(a, directory);
+ arena_commit(a, 1);
+ dir->name.data[dir->name.len] = 0;
+
+ dir->handle = CreateFileA((c8 *)dir->name.data, GENERIC_READ, FILE_SHARE_READ, 0,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED, 0);
+
+ w32_context *ctx = (w32_context *)platform->os_context;
+ w32_io_completion_event *event = push_struct(a, typeof(*event));
+ event->tag = W32_IO_FILE_WATCH;
+ event->context = (iptr)dir;
+ CreateIoCompletionPort(dir->handle, ctx->io_completion_handle, (uptr)event, 0);
+
+ dir->buffer = sub_arena(a, 4096 + sizeof(w32_overlapped), 64);
+ w32_overlapped *overlapped = (w32_overlapped *)(dir->buffer.beg + 4096);
+ zero_struct(overlapped);
+
+ ReadDirectoryChangesW(dir->handle, dir->buffer.beg, 4096, 0,
+ FILE_NOTIFY_CHANGE_LAST_WRITE, 0, overlapped, 0);
+ }
+
+ insert_file_watch(dir, s8_cut_head(path, dir->name.len + 1), user_data, callback);
+}
diff --git a/static.c b/static.c
@@ -1,42 +1,39 @@
/* See LICENSE for license details. */
-static struct {
- s8 label;
- s8 path;
- b32 needs_header;
-} compute_shaders[CS_LAST] = {
- #define X(e, n, s, h, pn) [CS_##e] = {s8(#e), s8("shaders/" s ".glsl"), h},
- COMPUTE_SHADERS
- #undef X
-};
-
#ifndef _DEBUG
#include "beamformer.c"
-#define do_debug(...)
+#define debug_init(...)
#else
static void *debug_lib;
static beamformer_frame_step_fn *beamformer_frame_step;
+static FILE_WATCH_CALLBACK_FN(debug_reload)
+{
+ BeamformerInput *input = (BeamformerInput *)user_data;
+ Stream err = arena_stream(&tmp);
+
+ os_unload_library(debug_lib);
+ debug_lib = os_load_library(OS_DEBUG_LIB_NAME, OS_DEBUG_LIB_TEMP_NAME, &err);
+ beamformer_frame_step = os_lookup_dynamic_symbol(debug_lib, "beamformer_frame_step", &err);
+ os_write_err_msg(s8("Reloaded Main Executable\n"));
+ input->executable_reloaded = 1;
+
+ return 1;
+}
+
static void
-do_debug(BeamformerInput *input, Stream *error_stream)
+debug_init(Platform *p, iptr input, Arena *arena)
{
- static f64 updated_time;
- FileStats test_stats = os_get_file_stats(OS_DEBUG_LIB_NAME);
- if (test_stats.filesize > 32 && test_stats.timestamp > updated_time) {
- os_unload_library(debug_lib);
- debug_lib = os_load_library(OS_DEBUG_LIB_NAME, OS_DEBUG_LIB_TEMP_NAME, error_stream);
- beamformer_frame_step = os_lookup_dynamic_symbol(debug_lib, "beamformer_frame_step", error_stream);
- updated_time = test_stats.timestamp;
-
- input->executable_reloaded = 1;
- os_write_err_msg(s8("Reloaded Main Executable\n"));
- }
+ p->add_file_watch(p, arena, s8(OS_DEBUG_LIB_NAME), debug_reload, input);
+ debug_reload((s8){0}, input, *arena);
}
#endif /* _DEBUG */
+#define static_path_join(a, b) (a OS_PATH_SEPERATOR b)
+
static void
gl_debug_logger(u32 src, u32 type, u32 id, u32 lvl, i32 len, const char *msg, const void *userctx)
{
@@ -154,13 +151,13 @@ compile_shader(Arena a, u32 type, s8 shader)
i32 res = 0;
glGetShaderiv(sid, GL_COMPILE_STATUS, &res);
- char *stype;
- switch (type) {
- case GL_COMPUTE_SHADER: stype = "Compute"; break;
- case GL_FRAGMENT_SHADER: stype = "Fragment"; break;
- }
-
if (res == GL_FALSE) {
+ char *stype;
+ switch (type) {
+ case GL_COMPUTE_SHADER: stype = "Compute"; break;
+ case GL_FRAGMENT_SHADER: stype = "Fragment"; break;
+ }
+
TraceLog(LOG_WARNING, "SHADER: [ID %u] %s shader failed to compile", sid, stype);
i32 len = 0;
glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len);
@@ -168,94 +165,103 @@ compile_shader(Arena a, u32 type, s8 shader)
glGetShaderInfoLog(sid, len, (int *)&err.len, (char *)err.data);
TraceLog(LOG_WARNING, "SHADER: [ID %u] Compile error: %s", sid, (char *)err.data);
glDeleteShader(sid);
- } else {
- TraceLog(LOG_INFO, "SHADER: [ID %u] %s shader compiled successfully", sid, stype);
+
+ sid = 0;
}
return sid;
}
-static void
-reload_shaders(BeamformerCtx *ctx, Arena a)
+static FILE_WATCH_CALLBACK_FN(reload_render_shader)
{
- ComputeShaderCtx *csctx = &ctx->csctx;
- s8 header_in_arena = push_s8(&a, s8(COMPUTE_SHADER_HEADER));
- for (u32 i = 0; i < ARRAY_COUNT(csctx->programs); i++) {
- FileStats fs = os_get_file_stats((char *)compute_shaders[i].path.data);
- if (fs.filesize <= 0)
- continue;
-
- Arena tmp = a;
- s8 shader_text = os_read_file(&tmp, (char *)compute_shaders[i].path.data, fs.filesize);
- if (shader_text.len == -1) {
- stream_append_s8(&ctx->error_stream, s8("failed to read shader: "));
- stream_append_s8(&ctx->error_stream, compute_shaders[i].path);
- stream_append_byte(&ctx->error_stream, '\n');
- /* TODO: maybe we don't need to fail here */
- os_fatal(stream_to_s8(&ctx->error_stream));
- }
- /* NOTE: arena works as stack (since everything here is 1 byte aligned) */
- if (compute_shaders[i].needs_header) {
- shader_text.data -= header_in_arena.len;
- shader_text.len += header_in_arena.len;
- ASSERT(shader_text.data == header_in_arena.data);
- }
+ FragmentShaderCtx *ctx = (FragmentShaderCtx *)user_data;
+ Shader updated_fs = LoadShader(0, (c8 *)path.data);
+
+ if (updated_fs.id) {
+ UnloadShader(ctx->shader);
+ LABEL_GL_OBJECT(GL_PROGRAM, updated_fs.id, s8("Render Shader"));
+ ctx->shader = updated_fs;
+ ctx->db_cutoff_id = GetShaderLocation(updated_fs, "u_db_cutoff");
+ ctx->threshold_id = GetShaderLocation(updated_fs, "u_threshold");
+ ctx->gen_mipmaps = 1;
+ }
- u32 shader_id = compile_shader(tmp, GL_COMPUTE_SHADER, shader_text);
+ return 1;
+}
- if (shader_id) {
- glDeleteProgram(csctx->programs[i]);
- csctx->programs[i] = rlLoadComputeShaderProgram(shader_id);
- glUseProgram(csctx->programs[i]);
- glBindBufferBase(GL_UNIFORM_BUFFER, 0, csctx->shared_ubo);
- LABEL_GL_OBJECT(GL_PROGRAM, csctx->programs[i], compute_shaders[i].label);
- }
+struct compute_shader_reload_ctx {
+ BeamformerCtx *ctx;
+ s8 label;
+ u32 shader;
+ b32 needs_header;
+};
- glDeleteShader(shader_id);
- }
+static FILE_WATCH_CALLBACK_FN(reload_compute_shader)
+{
+ struct compute_shader_reload_ctx *ctx = (struct compute_shader_reload_ctx *)user_data;
+ ComputeShaderCtx *cs = &ctx->ctx->csctx;
- #define X(idx, name) csctx->name##_id = glGetUniformLocation(csctx->programs[idx], "u_" #name);
- CS_UNIFORMS
- #undef X
+ b32 result = 1;
+
+ /* NOTE: arena works as stack (since everything here is 1 byte aligned) */
+ s8 header_in_arena = {.data = tmp.beg};
+ if (ctx->needs_header)
+ header_in_arena = push_s8(&tmp, s8(COMPUTE_SHADER_HEADER));
+
+ size fs = os_get_file_stats((c8 *)path.data).filesize;
+
+ s8 shader_text = os_read_file(&tmp, (c8 *)path.data, fs);
+ shader_text.data -= header_in_arena.len;
+ shader_text.len += header_in_arena.len;
+ ASSERT(shader_text.data == header_in_arena.data);
- Shader updated_fs = LoadShader(NULL, "shaders/render.glsl");
- if (updated_fs.id != rlGetShaderIdDefault()) {
- UnloadShader(ctx->fsctx.shader);
- LABEL_GL_OBJECT(GL_PROGRAM, updated_fs.id, s8("Render"));
- ctx->fsctx.shader = updated_fs;
- ctx->fsctx.db_cutoff_id = GetShaderLocation(updated_fs, "u_db_cutoff");
- ctx->fsctx.threshold_id = GetShaderLocation(updated_fs, "u_threshold");
+ u32 shader_id = compile_shader(tmp, GL_COMPUTE_SHADER, shader_text);
+ if (shader_id) {
+ glDeleteProgram(cs->programs[ctx->shader]);
+ cs->programs[ctx->shader] = rlLoadComputeShaderProgram(shader_id);
+ glUseProgram(cs->programs[ctx->shader]);
+ glBindBufferBase(GL_UNIFORM_BUFFER, 0, cs->shared_ubo);
+ LABEL_GL_OBJECT(GL_PROGRAM, cs->programs[ctx->shader], ctx->label);
+
+ TraceLog(LOG_INFO, "%s loaded", path.data);
+
+ ctx->ctx->flags |= START_COMPUTE;
+ } else {
+ result = 0;
}
-}
-static void
-validate_cuda_lib(CudaLib *cl)
-{
- #define X(name) if (!cl->name) cl->name = name ## _stub;
- CUDA_LIB_FNS
- #undef X
+ glDeleteShader(shader_id);
+
+ return result;
}
-static void
-check_and_load_cuda_lib(CudaLib *cl, Stream *error_stream)
+static FILE_WATCH_CALLBACK_FN(load_cuda_lib)
{
- FileStats current = os_get_file_stats(OS_CUDA_LIB_NAME);
- if (cl->timestamp == current.timestamp || current.filesize < 32)
- return;
-
- TraceLog(LOG_INFO, "Loading CUDA lib: %s", OS_CUDA_LIB_NAME);
+ CudaLib *cl = (CudaLib *)user_data;
+ b32 result = 0;
+ size fs = os_get_file_stats((c8 *)path.data).filesize;
+ if (fs > 0) {
+ TraceLog(LOG_INFO, "Loading CUDA lib: %s", OS_CUDA_LIB_NAME);
+
+ Stream err = arena_stream(&tmp);
+ os_unload_library(cl->lib);
+ cl->lib = os_load_library((c8 *)path.data, OS_CUDA_LIB_TEMP_NAME, &err);
+ #define X(name) cl->name = os_lookup_dynamic_symbol(cl->lib, #name, &err);
+ CUDA_LIB_FNS
+ #undef X
+
+ result = 1;
+ }
- cl->timestamp = current.timestamp;
- os_unload_library(cl->lib);
- cl->lib = os_load_library(OS_CUDA_LIB_NAME, OS_CUDA_LIB_TEMP_NAME, error_stream);
- #define X(name) cl->name = os_lookup_dynamic_symbol(cl->lib, #name, error_stream);
+ #define X(name) if (!cl->name) cl->name = name ## _stub;
CUDA_LIB_FNS
#undef X
- validate_cuda_lib(cl);
+
+ return result;
}
static void
-setup_beamformer(BeamformerCtx *ctx, Arena temp_memory)
+setup_beamformer(BeamformerCtx *ctx, Arena *memory)
{
ctx->window_size = (uv2){.w = 1280, .h = 840};
@@ -267,7 +273,7 @@ setup_beamformer(BeamformerCtx *ctx, Arena temp_memory)
/* NOTE: Gather information about the GPU */
get_gl_params(&ctx->gl, &ctx->error_stream);
- dump_gl_params(&ctx->gl, temp_memory);
+ dump_gl_params(&ctx->gl, *memory);
validate_gl_requirements(&ctx->gl);
ctx->fsctx.db = -50.0f;
@@ -283,8 +289,16 @@ setup_beamformer(BeamformerCtx *ctx, Arena temp_memory)
ctx->params->compute_stages[2] = CS_MIN_MAX;
ctx->params->compute_stages_count = 3;
- /* NOTE: make sure function pointers are valid even if we are not using the cuda lib */
- validate_cuda_lib(&ctx->cuda_lib);
+ if (ctx->gl.vendor_id == GL_VENDOR_NVIDIA
+ && load_cuda_lib(s8(OS_CUDA_LIB_NAME), (iptr)&ctx->cuda_lib, *memory))
+ {
+ os_add_file_watch(&ctx->platform, memory, s8(OS_CUDA_LIB_NAME), load_cuda_lib,
+ (iptr)&ctx->cuda_lib);
+ } else {
+ #define X(name) if (!ctx->cuda_lib.name) ctx->cuda_lib.name = name ## _stub;
+ CUDA_LIB_FNS
+ #undef X
+ }
/* NOTE: set up OpenGL debug logging */
glDebugMessageCallback(gl_debug_logger, &ctx->error_stream);
@@ -300,5 +314,26 @@ setup_beamformer(BeamformerCtx *ctx, Arena temp_memory)
glGenQueries(ARRAY_COUNT(ctx->csctx.timer_fences) * CS_LAST, (u32 *)ctx->csctx.timer_ids);
glGenQueries(ARRAY_COUNT(ctx->partial_compute_ctx.timer_ids), ctx->partial_compute_ctx.timer_ids);
- reload_shaders(ctx, temp_memory);
+ #define X(e, sn, f, nh, pretty_name) do if (s8(f).len > 0) { \
+ struct compute_shader_reload_ctx *csr = push_struct(memory, typeof(*csr)); \
+ csr->ctx = ctx; \
+ csr->label = s8("CS_" #e); \
+ csr->shader = sn; \
+ csr->needs_header = nh; \
+ s8 shader = s8(static_path_join("shaders", f ".glsl")); \
+ reload_compute_shader(shader, (iptr)csr, *memory); \
+ os_add_file_watch(&ctx->platform, memory, shader, reload_compute_shader, (iptr)csr); \
+ } while (0);
+ COMPUTE_SHADERS
+ #undef X
+
+ s8 render = s8(static_path_join("shaders", "render.glsl"));
+ reload_render_shader(render, (iptr)&ctx->fsctx, *memory);
+ os_add_file_watch(&ctx->platform, memory, render, reload_render_shader, (iptr)&ctx->fsctx);
+
+ /* TODO(rnp): remove this */
+ ComputeShaderCtx *csctx = &ctx->csctx;
+ #define X(idx, name) csctx->name##_id = glGetUniformLocation(csctx->programs[idx], "u_" #name);
+ CS_UNIFORMS
+ #undef X
}
diff --git a/ui.c b/ui.c
@@ -880,7 +880,7 @@ static void
ui_gen_mipmaps(BeamformerCtx *ctx)
{
if (ctx->fsctx.output.texture.id)
- ctx->flags |= GEN_MIPMAPS;
+ ctx->fsctx.gen_mipmaps = 1;
}
static void
diff --git a/util.c b/util.c
@@ -14,6 +14,7 @@ static i32 hadamard_12_12_transpose[] = {
1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1,
};
+#define zero_struct(s) mem_clear(s, 0, sizeof(*s));
static void *
mem_clear(void *p_, u8 c, size len)
{
@@ -37,6 +38,14 @@ mem_move(u8 *src, u8 *dest, size n)
else while (n) { n--; dest[n] = src[n]; }
}
+
+static void
+arena_commit(Arena *a, size size)
+{
+ ASSERT(a->end - a->beg >= size);
+ a->beg += size;
+}
+
#define alloc(a, t, n) (t *)alloc_(a, sizeof(t), _Alignof(t), n)
#define push_struct(a, t) (t *)alloc_(a, sizeof(t), _Alignof(t), 1)
static void *
@@ -57,14 +66,15 @@ alloc_(Arena *a, size len, size align, size count)
}
static Arena
-sub_arena(Arena *a, size size)
+sub_arena(Arena *a, size len, size align)
{
Arena result = {0};
- if ((a->end - a->beg) >= size) {
- result.beg = a->beg;
- result.end = a->beg + size;
- a->beg += size;
- }
+
+ size padding = -(uintptr_t)a->beg & (align - 1);
+ result.beg = a->beg + padding;
+ result.end = result.beg + len;
+ arena_commit(a, len + padding);
+
return result;
}
@@ -246,6 +256,18 @@ stream_append_variable(Stream *s, Variable *var)
}
}
+/* NOTE(rnp): FNV-1a hash */
+static u64
+s8_hash(s8 v)
+{
+ u64 h = 0x3243f6a8885a308d; /* digits of pi */
+ for (; v.len; v.len--) {
+ h ^= v.data[v.len - 1] & 0xFF;
+ h *= 1111111111111111111; /* random prime */
+ }
+ return h;
+}
+
static s8
cstr_to_s8(char *cstr)
{
@@ -254,13 +276,23 @@ cstr_to_s8(char *cstr)
return result;
}
+/* NOTE(rnp): returns < 0 if byte is not found */
+static size
+s8_scan_backwards(s8 s, u8 byte)
+{
+ size result = s.len;
+ while (result && s.data[result - 1] != byte) result--;
+ result--;
+ return result;
+}
+
static s8
s8_cut_head(s8 s, size cut)
{
s8 result = s;
if (cut > 0) {
result.data += cut;
- result.len -= cut;
+ result.len -= cut;
}
return result;
}
@@ -406,6 +438,32 @@ parse_f64(s8 s)
return result;
}
+static FileWatchDirectory *
+lookup_file_watch_directory(FileWatchContext *ctx, u64 hash)
+{
+ FileWatchDirectory *result = 0;
+
+ for (u32 i = 0; i < ctx->directory_watch_count; i++) {
+ FileWatchDirectory *test = ctx->directory_watches + i;
+ if (test->hash == hash) {
+ result = test;
+ break;
+ }
+ }
+
+ return result;
+}
+
+static void
+insert_file_watch(FileWatchDirectory *dir, s8 name, iptr user_data, file_watch_callback *callback)
+{
+ ASSERT(dir->file_watch_count < ARRAY_COUNT(dir->file_watches));
+ FileWatch *fw = dir->file_watches + dir->file_watch_count++;
+ fw->hash = s8_hash(name);
+ fw->user_data = user_data;
+ fw->callback = callback;
+}
+
static void
fill_kronecker_sub_matrix(i32 *out, i32 out_stride, i32 scale, i32 *b, uv2 b_dim)
{
diff --git a/util.h b/util.h
@@ -68,6 +68,7 @@ typedef float f32;
typedef double f64;
typedef ptrdiff_t size;
typedef ptrdiff_t iptr;
+typedef size_t uptr;
typedef struct { u8 *beg, *end; } Arena;
typedef struct { Arena *arena; u8 *old_beg; } TempArena;
@@ -160,6 +161,30 @@ typedef struct {
} FileStats;
#define ERROR_FILE_STATS (FileStats){.filesize = -1}
+#define FILE_WATCH_CALLBACK_FN(name) b32 name(s8 path, iptr user_data, Arena tmp)
+typedef FILE_WATCH_CALLBACK_FN(file_watch_callback);
+
+typedef struct {
+ iptr user_data;
+ u64 hash;
+ file_watch_callback *callback;
+} FileWatch;
+
+typedef struct {
+ u64 hash;
+ iptr handle;
+ s8 name;
+ FileWatch file_watches[16];
+ u32 file_watch_count;
+ Arena buffer;
+} FileWatchDirectory;
+
+typedef struct {
+ FileWatchDirectory directory_watches[4];
+ iptr handle;
+ u32 directory_watch_count;
+} FileWatchContext;
+
typedef struct {
u8 *data;
u32 widx;
@@ -202,17 +227,21 @@ typedef struct {
#define NULL_VARIABLE (Variable){.store = 0, .type = VT_NULL}
+typedef struct Platform Platform;
+
#define PLATFORM_ALLOC_ARENA_FN(name) Arena name(Arena old, size capacity)
typedef PLATFORM_ALLOC_ARENA_FN(platform_alloc_arena_fn);
+#define PLATFORM_ADD_FILE_WATCH_FN(name) void name(Platform *platform, Arena *a, s8 path, \
+ file_watch_callback *callback, iptr user_data)
+typedef PLATFORM_ADD_FILE_WATCH_FN(platform_add_file_watch_fn);
+
#define PLATFORM_CLOSE_FN(name) void name(iptr file)
typedef PLATFORM_CLOSE_FN(platform_close_fn);
#define PLATFORM_OPEN_FOR_WRITE_FN(name) iptr name(c8 *fname)
typedef PLATFORM_OPEN_FOR_WRITE_FN(platform_open_for_write_fn);
-#define PLATFORM_POLL_PIPE_FN(name) b32 name(Pipe p)
-
#define PLATFORM_READ_PIPE_FN(name) size name(iptr pipe, void *buf, size len)
typedef PLATFORM_READ_PIPE_FN(platform_read_pipe_fn);
@@ -223,6 +252,7 @@ typedef PLATFORM_WRITE_NEW_FILE_FN(platform_write_new_file_fn);
typedef PLATFORM_WRITE_FILE_FN(platform_write_file_fn);
#define PLATFORM_FNS \
+ X(add_file_watch) \
X(alloc_arena) \
X(close) \
X(open_for_write) \
@@ -231,7 +261,11 @@ typedef PLATFORM_WRITE_FILE_FN(platform_write_file_fn);
X(write_file)
#define X(name) platform_ ## name ## _fn *name;
-typedef struct { PLATFORM_FNS } Platform;
+struct Platform {
+ PLATFORM_FNS
+ FileWatchContext file_watch_context;
+ iptr os_context;
+};
#undef X
typedef struct {