ogl_beamforming

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

Commit: aa57cf8ebd7c9fae917350ccc850dc57030ddf34
Parent: 004752ed6527fcbbec1a09443e69c35aa71c9f23
Author: Randy Palamar
Date:   Tue,  9 Jul 2024 15:59:06 -0600

allow rf_data_dim to be changed at runtime

Diffstat:
Mbeamformer.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mbeamformer.h | 20+++++---------------
Mhelpers/ogl_beamformer_lib.c | 32+++++++++++++++++++-------------
Mhelpers/ogl_beamformer_lib.h | 7++++---
Mmain.c | 64+++++-----------------------------------------------------------
Mutil.c | 26++++++++++++++++++++++++++
Mutil.h | 12++++++++++++
7 files changed, 125 insertions(+), 94 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -2,6 +2,49 @@ #include "beamformer.h" static void +alloc_shader_storage(BeamformerCtx *ctx, Arena a) +{ + uv3 rf_data_dim = ctx->params->rf_data_dim; + ctx->csctx.rf_data_dim = rf_data_dim; + size rf_raw_size = rf_data_dim.w * rf_data_dim.h * rf_data_dim.d * sizeof(i16); + size rf_decoded_size = rf_data_dim.w * rf_data_dim.h * rf_data_dim.d * sizeof(f32); + + glDeleteBuffers(ARRAY_COUNT(ctx->csctx.rf_data_ssbos), ctx->csctx.rf_data_ssbos); + glGenBuffers(ARRAY_COUNT(ctx->csctx.rf_data_ssbos), ctx->csctx.rf_data_ssbos); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ctx->csctx.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->csctx.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->csctx.hadamard_dim = (uv2){ .x = rf_data_dim.d, .y = rf_data_dim.d }; + size hadamard_elements = ctx->csctx.hadamard_dim.x * ctx->csctx.hadamard_dim.y; + i32 *hadamard = alloc(&a, i32, hadamard_elements); + fill_hadamard(hadamard, ctx->csctx.hadamard_dim.x); + + rlUnloadShaderBuffer(ctx->csctx.hadamard_ssbo); + ctx->csctx.hadamard_ssbo = rlLoadShaderBuffer(hadamard_elements * sizeof(i32), hadamard, + GL_STATIC_DRAW); + + /* NOTE: allocate storage for beamformed output data; + * this is shared between compute and fragment shaders */ + uv3 odim = ctx->out_data_dim; + u32 max_dim = MAX(odim.x, MAX(odim.y, odim.z)); + /* TODO: does this actually matter or is 0 fine? */ + ctx->out_texture_unit = 0; + ctx->out_texture_mips = _tzcnt_u32(max_dim) + 1; + glActiveTexture(GL_TEXTURE0 + ctx->out_texture_unit); + glDeleteTextures(1, &ctx->out_texture); + glGenTextures(1, &ctx->out_texture); + glBindTexture(GL_TEXTURE_3D, ctx->out_texture); + glTexStorage3D(GL_TEXTURE_3D, ctx->out_texture_mips, GL_RG32F, odim.x, odim.y, odim.z); + + UnloadRenderTexture(ctx->fsctx.output); + ctx->fsctx.output = LoadRenderTexture(odim.w, odim.h); +} + +static void do_compute_shader(BeamformerCtx *ctx, enum compute_shaders shader) { ComputeShaderCtx *csctx = &ctx->csctx; @@ -120,6 +163,9 @@ do_beamformer(BeamformerCtx *ctx, Arena arena) /* NOTE: Check for and Load RF Data into GPU */ if (os_poll_pipe(ctx->data_pipe)) { + if (!uv3_equal(ctx->csctx.rf_data_dim, ctx->params->rf_data_dim)) + alloc_shader_storage(ctx, arena); + 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); @@ -128,15 +174,19 @@ do_beamformer(BeamformerCtx *ctx, Arena arena) 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); + ctx->flags |= DO_COMPUTE; } else { ctx->partial_transfer_count++; } } + if (ctx->flags & DO_COMPUTE) { + do_compute_shader(ctx, CS_HADAMARD); + do_compute_shader(ctx, CS_UFORCES); + do_compute_shader(ctx, CS_MIN_MAX); + ctx->flags &= ~DO_COMPUTE; + } + /* NOTE: check mouse wheel for adjusting dynamic range of image */ ctx->fsctx.db += GetMouseWheelMove(); CLAMP(ctx->fsctx.db, -120, 0); diff --git a/beamformer.h b/beamformer.h @@ -11,18 +11,6 @@ #include "util.h" typedef union { - struct { u32 x, y; }; - struct { u32 w, h; }; - u32 E[2]; -} uv2; - -typedef union { - struct { u32 x, y, z; }; - struct { u32 w, h, d; }; - u32 E[3]; -} uv3; - -typedef union { struct { f32 x, y; }; f32 E[2]; Vector2 rl; @@ -46,6 +34,7 @@ enum compute_shaders { enum program_flags { RELOAD_SHADERS = 1 << 0, + DO_COMPUTE = 1 << 1, }; typedef struct { @@ -72,12 +61,13 @@ typedef struct { } FragmentShaderCtx; typedef struct { - i16 channel_row_mapping[128]; - i16 channel_column_mapping[128]; - i16 uforces_channels[128]; + u32 channel_row_mapping[128]; + u32 channel_column_mapping[128]; + u32 uforces_channels[128]; u32 channel_data_stride; f32 speed_of_sound; f32 sampling_frequency; + uv3 rf_data_dim; uv3 output_points; } BeamformerParameters; diff --git a/helpers/ogl_beamformer_lib.c b/helpers/ogl_beamformer_lib.c @@ -26,7 +26,7 @@ typedef struct { #error Unsupported Platform #endif -static BeamformerParameters *bp; +static volatile BeamformerParameters *g_bp; static char *shm_name = "/ogl_beamformer_parameters"; static os_pipe g_pipe = {.file = OS_INVALID_FILE}; @@ -95,6 +95,17 @@ os_close_pipe(void) } #endif +static void +check_shared_memory(void) +{ + if (g_bp) + return; + g_bp = os_open_shared_memory_area(shm_name); + if (g_bp == NULL) + mexErrMsgIdAndTxt("ogl_beamformer:shared_memory", + "failed to open shared memory area"); +} + void send_data(char *pipe_name, i16 *data, uv3 data_dim) { @@ -106,8 +117,11 @@ send_data(char *pipe_name, i16 *data, uv3 data_dim) } } - size data_size = data_dim.x * data_dim.y * data_dim.z * sizeof(i16); - size written = os_write_to_pipe(g_pipe, data, data_size); + check_shared_memory(); + /* TODO: this probably needs a mutex around it if we want to change it here */ + g_bp->rf_data_dim = data_dim; + size data_size = data_dim.x * data_dim.y * data_dim.z * sizeof(i16); + size written = os_write_to_pipe(g_pipe, data, data_size); if (written != data_size) mexWarnMsgIdAndTxt("ogl_beamformer:write_error", "failed to write full data to pipe: wrote: %ld", written); @@ -116,16 +130,8 @@ send_data(char *pipe_name, i16 *data, uv3 data_dim) void set_beamformer_parameters(BeamformerParameters *new_bp) { - if (bp == NULL) { - bp = os_open_shared_memory_area(shm_name); - if (bp == NULL) { - mexErrMsgIdAndTxt("ogl_beamformer:shared_memory", - "failed to open shared memory area"); - return; - } - } - - u8 *src = (u8 *)new_bp, *dest = (u8 *)bp; + check_shared_memory(); + u8 *src = (u8 *)new_bp, *dest = (u8 *)g_bp; for (size i = 0; i < sizeof(BeamformerParameters); i++) dest[i] = src[i]; } diff --git a/helpers/ogl_beamformer_lib.h b/helpers/ogl_beamformer_lib.h @@ -23,12 +23,13 @@ typedef struct { u32 x, y, z; } uv3; typedef struct { f32 x, y, z; } v3; typedef struct { - i16 channel_row_mapping[128]; - i16 channel_column_mapping[128]; - i16 uforces_channels[128]; + u32 channel_row_mapping[128]; + u32 channel_column_mapping[128]; + u32 uforces_channels[128]; u32 channel_data_stride; f32 speed_of_sound; f32 sampling_frequency; + uv3 rf_data_dim; uv3 output_points; } BeamformerParameters; diff --git a/main.c b/main.c @@ -65,26 +65,6 @@ do_debug(void) #endif /* _DEBUG */ -static void -fill_hadamard(i32 *m, u32 dim) -{ - ASSERT(dim && ISPOWEROF2(dim)); - - #define IND(i, j) ((i) * dim + (j)) - m[0] = 1; - for (u32 k = 1; k < dim; k *= 2) { - for (u32 i = 0; i < k; i++) { - for (u32 j = 0; j < k; j++) { - i32 val = m[IND(i, j)]; - m[IND(i + k, j)] = val; - m[IND(i, j + k)] = val; - m[IND(i + k, j + k)] = -val; - } - } - } - #undef IND -} - static u32 compile_shader(Arena a, u32 type, s8 shader) { @@ -117,28 +97,6 @@ compile_shader(Arena a, u32 type, s8 shader) } static void -init_compute_shader_ctx(ComputeShaderCtx *ctx, Arena a, uv3 rf_data_dim) -{ - ctx->rf_data_dim = rf_data_dim; - size rf_raw_size = rf_data_dim.w * rf_data_dim.h * rf_data_dim.d * sizeof(i16); - 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); - 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 }; - size hadamard_elements = ctx->hadamard_dim.x * ctx->hadamard_dim.y; - i32 *hadamard = alloc(&a, i32, hadamard_elements); - fill_hadamard(hadamard, ctx->hadamard_dim.x); - ctx->hadamard_ssbo = rlLoadShaderBuffer(hadamard_elements * sizeof(i32), hadamard, GL_STATIC_DRAW); -} - -static void init_fragment_shader_ctx(FragmentShaderCtx *ctx, uv3 out_data_dim) { ctx->output = LoadRenderTexture(out_data_dim.w, out_data_dim.h); @@ -158,6 +116,7 @@ reload_shaders(BeamformerCtx *ctx, Arena a) if (shader_id) { glDeleteProgram(csctx->programs[i]); csctx->programs[i] = rlLoadComputeShaderProgram(shader_id); + ctx->flags |= DO_COMPUTE; } glDeleteShader(shader_id); @@ -184,8 +143,8 @@ main(void) Arena temp_memory = os_new_arena(256 * MEGABYTE); - ctx.window_size = (uv2){.w = 2048, .h = 2048}; - ctx.out_data_dim = (uv3){.w = 2048, .h = 2048, .d = 1}; + ctx.window_size = (uv2){.w = 1024, .h = 1024}; + ctx.out_data_dim = (uv3){.w = 256, .h = 1024, .d = 1}; ctx.bg = PINK; ctx.fg = (Color){ .r = 0xea, .g = 0xe1, .b = 0xb4, .a = 0xff }; @@ -195,21 +154,6 @@ main(void) ctx.font = GetFontDefault(); - /* NOTE: allocate storage for beamformed output data; - * this is shared between compute and fragment shaders */ - { - uv3 odim = ctx.out_data_dim; - u32 max_dim = MAX(odim.x, MAX(odim.y, odim.z)); - /* TODO: does this actually matter or is 0 fine? */ - ctx.out_texture_unit = 0; - ctx.out_texture_mips = _tzcnt_u32(max_dim) + 1; - glActiveTexture(GL_TEXTURE0 + ctx.out_texture_unit); - glGenTextures(1, &ctx.out_texture); - glBindTexture(GL_TEXTURE_3D, ctx.out_texture); - glTexStorage3D(GL_TEXTURE_3D, ctx.out_texture_mips, GL_RG32F, odim.x, odim.y, odim.z); - } - - 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"); @@ -218,6 +162,8 @@ main(void) ASSERT(ctx.data_pipe.file != OS_INVALID_FILE); ASSERT(ctx.params); + ctx.params->output_points = ctx.out_data_dim; + ctx.flags |= RELOAD_SHADERS; while(!WindowShouldClose()) { diff --git a/util.c b/util.c @@ -44,3 +44,29 @@ s8alloc(Arena *a, size len) { return (s8){ .data = alloc(a, u8, len), .len = len }; } + +static b32 +uv3_equal(uv3 a, uv3 b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +static void +fill_hadamard(i32 *m, u32 dim) +{ + ASSERT(dim && ISPOWEROF2(dim)); + + #define IND(i, j) ((i) * dim + (j)) + m[0] = 1; + for (u32 k = 1; k < dim; k *= 2) { + for (u32 i = 0; i < k; i++) { + for (u32 j = 0; j < k; j++) { + i32 val = m[IND(i, j)]; + m[IND(i + k, j)] = val; + m[IND(i, j + k)] = val; + m[IND(i + k, j + k)] = -val; + } + } + } + #undef IND +} diff --git a/util.h b/util.h @@ -37,6 +37,18 @@ typedef struct { u8 *beg, *end; } Arena; typedef struct { size len; u8 *data; } s8; +typedef union { + struct { u32 x, y; }; + struct { u32 w, h; }; + u32 E[2]; +} uv2; + +typedef union { + struct { u32 x, y, z; }; + struct { u32 w, h, d; }; + u32 E[3]; +} uv3; + #include "util.c" #endif /* _UTIL_H_ */