ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | LICENSE

Commit: bb06bbeb3f69a70b1d7d59da62a9dbefd31f0ece
Parent: 7fc08ca46a917eaf6fb3cb77b97ed36b0514bcf3
Author: Randy Palamar
Date:   Tue,  2 Jul 2024 13:34:46 -0600

read rf data from a pipe; only compute on new data

Diffstat:
Mbeamformer.c | 63+++++++++++++++++++++++++++++++++------------------------------
Mbuild.sh | 2+-
Mmain.c | 24+++++++++++++++---------
Mos_unix.c | 42++++++++++++++++++++++++++++++++++++++++++
Mutil.h | 46++++++++++++++++++++--------------------------
5 files changed, 111 insertions(+), 66 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -7,7 +7,7 @@ #include "util.h" static void -do_compute_shader(BeamformerCtx *ctx, u32 rf_ssbo_idx, enum compute_shaders shader) +do_compute_shader(BeamformerCtx *ctx, enum compute_shaders shader) { ComputeShaderCtx *csctx = &ctx->csctx; glUseProgram(csctx->programs[shader]); @@ -17,7 +17,8 @@ do_compute_shader(BeamformerCtx *ctx, u32 rf_ssbo_idx, enum compute_shaders shad GL_WRITE_ONLY, GL_RG32F); glUniform1i(csctx->out_data_tex_id, ctx->out_texture_unit); - u32 decoded_ssbo_idx = 2; + u32 rf_ssbo_idx = 0; + u32 decoded_ssbo_idx = 1; switch (shader) { case CS_HADAMARD: glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, csctx->rf_data_ssbos[rf_ssbo_idx]); @@ -61,29 +62,29 @@ draw_debug_overlay(BeamformerCtx *ctx, Arena arena, f32 dt) u32 fontsize = 32; u32 fontspace = 1; + s8 partial_txt = s8alloc(&arena, 64); s8 db_txt = s8alloc(&arena, 64); - s8 compute_txt = s8alloc(&arena, 64); + snprintf((char *)partial_txt.data, partial_txt.len, "Partial Transfers: %u", ctx->partial_transfer_count); snprintf((char *)db_txt.data, db_txt.len, "Dynamic Range: %0.01f [db]", ctx->fsctx.db); - snprintf((char *)compute_txt.data, compute_txt.len, "Compute: %d", !!(ctx->flags & DO_COMPUTE)); + v2 partial_fs = {.rl = MeasureTextEx(ctx->font, (char *)partial_txt.data, fontsize, fontspace)}; v2 db_fs = {.rl = MeasureTextEx(ctx->font, (char *)db_txt.data, fontsize, fontspace)}; - v2 compute_fs = {.rl = MeasureTextEx(ctx->font, (char *)compute_txt.data, fontsize, fontspace)}; - v2 scale = {.x = 90, .y = 20 }; + v2 scale = {.x = 90, .y = 20}; + v2 pos = {.x = 20, .y = ws.h - db_fs.y - partial_fs.y - 20}; /* NOTE: Dynamic Range */ { - v2 dpos = {.x = 20, .y = ws.h - db_fs.y - compute_fs.y - 20}; - v2 dposa = {.x = dpos.x + db_fs.x / scale.x, .y = dpos.y + db_fs.y / scale.y }; + v2 dposa = {.x = pos.x + db_fs.x / scale.x, .y = pos.y + db_fs.y / scale.y }; DrawTextEx(ctx->font, (char *)db_txt.data, dposa.rl, fontsize, fontspace, Fade(BLACK, 0.8)); - DrawTextEx(ctx->font, (char *)db_txt.data, dpos.rl, fontsize, fontspace, RED); + DrawTextEx(ctx->font, (char *)db_txt.data, pos.rl, fontsize, fontspace, RED); + pos.y += db_fs.y; } - - /* NOTE: Compute Status */ + /* NOTE: Partial Tranfers */ { - v2 dpos = {.x = 20, .y = ws.h - compute_fs.y - 20}; - v2 dposa = {.x = dpos.x + compute_fs.x / scale.x, .y = dpos.y + compute_fs.y / scale.y }; - DrawTextEx(ctx->font, (char *)compute_txt.data, dposa.rl, fontsize, fontspace, Fade(BLACK, 0.8)); - DrawTextEx(ctx->font, (char *)compute_txt.data, dpos.rl, fontsize, fontspace, RED); + v2 dposa = {.x = pos.x + partial_fs.x / scale.x, .y = pos.y + partial_fs.y / scale.y }; + DrawTextEx(ctx->font, (char *)partial_txt.data, dposa.rl, fontsize, fontspace, Fade(BLACK, 0.8)); + DrawTextEx(ctx->font, (char *)partial_txt.data, pos.rl, fontsize, fontspace, RED); + pos.y += partial_fs.y; } { @@ -122,19 +123,23 @@ do_beamformer(BeamformerCtx *ctx, Arena arena, s8 rf_data) { f32 dt = GetFrameTime(); - /* NOTE: grab operating idx and swap it; other buffer can now be used for storage */ - u32 rf_ssbo_idx = atomic_fetch_xor_explicit(&ctx->csctx.rf_data_idx, 1, memory_order_relaxed); - ASSERT(rf_ssbo_idx == 0 || rf_ssbo_idx == 1); - - /* NOTE: Load RF Data into GPU */ - /* TODO: This should be done in a separate thread */ - glBindBuffer(GL_SHADER_STORAGE_BUFFER, ctx->csctx.rf_data_ssbos[!rf_ssbo_idx]); - glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, rf_data.len, rf_data.data); - - if (ctx->flags & DO_COMPUTE) { - do_compute_shader(ctx, rf_ssbo_idx, CS_HADAMARD); - do_compute_shader(ctx, rf_ssbo_idx, CS_UFORCES); - do_compute_shader(ctx, rf_ssbo_idx, CS_MIN_MAX); + /* NOTE: Check for and Load RF Data into GPU */ + if (os_poll_pipe(ctx->data_pipe)) { + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ctx->csctx.rf_data_ssbos[0]); + void *rf_data_buf = glMapBuffer(GL_SHADER_STORAGE_BUFFER, GL_WRITE_ONLY); + ASSERT(rf_data_buf); + uv3 rf_data_dim = ctx->csctx.rf_data_dim; + size rf_raw_size = rf_data_dim.w * rf_data_dim.h * rf_data_dim.d * sizeof(i32); + size rlen = os_read_pipe_data(ctx->data_pipe, rf_data_buf, rf_raw_size); + glUnmapBuffer(GL_SHADER_STORAGE_BUFFER); + if (rlen == rf_raw_size) { + /* NOTE: this will skip partially read data */ + do_compute_shader(ctx, CS_HADAMARD); + do_compute_shader(ctx, CS_UFORCES); + do_compute_shader(ctx, CS_MIN_MAX); + } else { + ctx->partial_transfer_count++; + } } /* NOTE: check mouse wheel for adjusting dynamic range of image */ @@ -167,6 +172,4 @@ do_beamformer(BeamformerCtx *ctx, Arena arena, s8 rf_data) if (IsKeyPressed(KEY_R)) ctx->flags |= RELOAD_SHADERS; - if (IsKeyPressed(KEY_SPACE)) - ctx->flags ^= DO_COMPUTE; } diff --git a/build.sh b/build.sh @@ -11,7 +11,7 @@ case "$1" in ldflags="$ldflags -lGL" # Hot Reloading/Debugging - cflags="$cflags -D_DEBUG" + cflags="$cflags -D_DEBUG -Wno-unused-function" libcflags="$cflags -fPIC -flto -Wno-unused-function" libldflags="$ldflags -shared" diff --git a/main.c b/main.c @@ -125,17 +125,16 @@ static void init_compute_shader_ctx(ComputeShaderCtx *ctx, Arena a, uv3 rf_data_dim) { ctx->rf_data_dim = rf_data_dim; - size rf_data_size = rf_data_dim.w * rf_data_dim.h * rf_data_dim.d * sizeof(i32); + /* TODO: send i16 data and convert to i32 on GPU */ + size rf_raw_size = rf_data_dim.w * rf_data_dim.h * rf_data_dim.d * sizeof(i32); + size rf_decoded_size = rf_data_dim.w * rf_data_dim.h * rf_data_dim.d * sizeof(i32); glGenBuffers(ARRAY_COUNT(ctx->rf_data_ssbos), ctx->rf_data_ssbos); - for (u32 i = 0; i < ARRAY_COUNT(ctx->rf_data_ssbos); i++) { - glBindBuffer(GL_SHADER_STORAGE_BUFFER, ctx->rf_data_ssbos[i]); - glBufferData(GL_SHADER_STORAGE_BUFFER, rf_data_size, 0, GL_DYNAMIC_COPY); - /* TODO: This doesn't actually work; need to use - * a texture to store i16 data and load i32 data */ - glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R32I, GL_R16I, GL_SHORT, 0); - } - ctx->rf_data_idx = 0; + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ctx->rf_data_ssbos[0]); + glBufferStorage(GL_SHADER_STORAGE_BUFFER, rf_raw_size, 0, + GL_DYNAMIC_STORAGE_BIT|GL_MAP_WRITE_BIT); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ctx->rf_data_ssbos[1]); + glBufferStorage(GL_SHADER_STORAGE_BUFFER, rf_decoded_size, 0, GL_DYNAMIC_STORAGE_BIT); /* NOTE: store hadamard in GPU once; it won't change for a particular imaging session */ ctx->hadamard_dim = (uv2){ .x = rf_data_dim.d, .y = rf_data_dim.d }; @@ -223,6 +222,10 @@ main(void) init_compute_shader_ctx(&ctx.csctx, temp_memory, (uv3){.w = 3456, .h = 128, .d = 8}); init_fragment_shader_ctx(&ctx.fsctx, ctx.out_data_dim); + ctx.data_pipe = os_open_named_pipe("/tmp/beamformer_data_fifo"); + /* TODO: properly handle this? */ + ASSERT(ctx.data_pipe.file != OS_INVALID_FILE); + ctx.flags |= RELOAD_SHADERS; while(!WindowShouldClose()) { @@ -235,4 +238,7 @@ main(void) do_beamformer(&ctx, temp_memory, raw_rf_data); } + + /* NOTE: garbage code needed for Linux */ + os_close_named_pipe(ctx.data_pipe); } diff --git a/os_unix.c b/os_unix.c @@ -1,8 +1,16 @@ #include <fcntl.h> +#include <poll.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> +#define OS_INVALID_FILE (-1) +typedef i32 os_file; +typedef struct { + os_file file; + char *name; +} os_pipe; + typedef struct timespec os_filetime; typedef struct { @@ -59,3 +67,37 @@ os_get_file_stats(char *fname) .timestamp = st.st_mtim, }; } + +static os_pipe +os_open_named_pipe(char *name) +{ + mkfifo(name, 0660); + return (os_pipe){.file = open(name, O_RDONLY|O_NONBLOCK), .name = name}; +} + +static void +os_close_named_pipe(os_pipe p) +{ + close(p.file); + unlink(p.name); +} + +static b32 +os_poll_pipe(os_pipe p) +{ + struct pollfd pfd = {.fd = p.file, .events = POLLIN}; + poll(&pfd, 1, 0); + return !!(pfd.revents & POLLIN); +} + +static size +os_read_pipe_data(os_pipe p, void *buf, size len) +{ + size r = 0, total_read = 0; + do { + if (r != -1) + total_read += r; + r = read(p.file, buf + total_read, len - total_read); + } while (r); + return total_read; +} diff --git a/util.h b/util.h @@ -2,7 +2,6 @@ #ifndef _UTIL_H_ #define _UTIL_H_ -#include <stdatomic.h> #include <stddef.h> #include <stdint.h> @@ -63,22 +62,28 @@ enum compute_shaders { enum program_flags { RELOAD_SHADERS = 1 << 0, - DO_COMPUTE = 1 << 1, }; +#include "util.c" +#if defined(__unix__) +#define GL_GLEXT_PROTOTYPES 1 +#include <GL/glcorearb.h> +#include <GL/glext.h> +#include "os_unix.c" +#elif defined(_WIN32) +#include <glad.h> +#include "os_win32.c" +#else +#error Unsupported Platform! +#endif + typedef struct { u32 programs[CS_LAST]; - /* NOTE: need 3 storage buffers: incoming rf, currently decoding rf, decoded. - * last buffer will always be for decoded data. other two will swap everytime there - * is new data. current operating idx is stored in rf_data_idx (0 or 1) which needs - * to be accessed atomically - */ - u32 rf_data_ssbos[3]; - _Atomic u32 rf_data_idx; - - u32 hadamard_ssbo; - uv2 hadamard_dim; + /* NOTE: One SSBO for raw data and one for decoded data */ + u32 rf_data_ssbos[2]; + u32 hadamard_ssbo; + uv2 hadamard_dim; uv3 rf_data_dim; i32 rf_data_dim_id; @@ -110,6 +115,9 @@ typedef struct { ComputeShaderCtx csctx; FragmentShaderCtx fsctx; + + os_pipe data_pipe; + u32 partial_transfer_count; } BeamformerCtx; #define MEGABYTE (1024ULL * 1024ULL) @@ -123,18 +131,4 @@ typedef struct { #define CLAMP(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)) #define ISPOWEROF2(a) (((a) & ((a) - 1)) == 0) -#include "util.c" - -#if defined(__unix__) -#define GL_GLEXT_PROTOTYPES 1 -#include <GL/glcorearb.h> -#include <GL/glext.h> -#include "os_unix.c" -#elif defined(_WIN32) -#include <glad.h> -#include "os_win32.c" -#else -#error Unsupported Platform! -#endif - #endif /*_UTIL_H_ */