ogl_beamforming

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

Commit: 0163bc9079c9e3c72220ae82a3c40923f160e76e
Parent: 2a64126fc9cf1a5c754c1700a7dcc5d2f1bedcb3
Author: Randy Palamar
Date:   Tue, 18 Jun 2024 12:40:45 -0600

start drawing rf data to the background

Diffstat:
Mbeamformer.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mbuild.sh | 2+-
Mmain.c | 176++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Aos_unix.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.c | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 387 insertions(+), 18 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -3,11 +3,81 @@ #include <raylib.h> #include <rlgl.h> +#include <stdio.h> +#include <stdlib.h> + +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES 1 +#include <GL/glcorearb.h> +#include <GL/glext.h> +#endif + #include "util.c" DEBUG_EXPORT void -do_beamformer(BeamformerCtx *ctx) +do_beamformer(BeamformerCtx *ctx, Arena arena, s8 rf_data) { - ClearBackground(DARKGRAY); + uv2 ws = ctx->window_size; + f32 dt = GetFrameTime(); + + static v2 pos = {.x = 32, .y = 128 }; + static v2 scale = {.x = 1.0, .y = 1.0}; + static u32 txt_idx = 0; + static char *txt[2] = { "-_-", "^_^" }; + + Font font = GetFontDefault(); + v2 fs = { .rl = MeasureTextEx(font, txt[txt_idx], 60, 6) }; + + pos.x += 130 * dt * scale.x; + pos.y += 120 * dt * scale.y; + + if (pos.x > (ws.w - fs.x) || pos.x < 0) { + txt_idx = !txt_idx; + fs = (v2){ .rl = MeasureTextEx(font, txt[txt_idx], 60, 6) }; + CLAMP(pos.x, 0, ws.w - fs.x); + scale.x *= -1.0; + } + + if (pos.y > (ws.h - fs.y) || pos.y < 0) { + txt_idx = !txt_idx; + fs = (v2){ .rl = MeasureTextEx(font, txt[txt_idx], 60, 6) }; + CLAMP(pos.y, 0, ws.h - fs.y); + scale.y *= -1.0; + } + + { + ComputeShaderCtx *csctx = &ctx->csctx; + glUseProgram(csctx->programs[CS_UFORCES]); + + glBindBuffer(GL_SHADER_STORAGE_BUFFER, csctx->rf_data_ssbo); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, rf_data.len, rf_data.data); + + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, csctx->rf_data_ssbo); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, csctx->out_img_ssbo); + + glUniform3uiv(csctx->u_rf_dim_id, 1, csctx->rf_data_dim.E); + glUniform3ui(csctx->u_out_dim_id, ctx->out_img_dim.x, ctx->out_img_dim.y, 0); + + glDispatchCompute(ctx->out_img_dim.x, ctx->out_img_dim.y, 1); + } + + BeginDrawing(); + + ClearBackground(PINK); + + BeginShaderMode(ctx->fsctx.shader); + glUseProgram(ctx->fsctx.shader.id); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ctx->csctx.out_img_ssbo); + glUniform2uiv(ctx->fsctx.u_out_dim_id, 1, ctx->out_img_dim.E); + ASSERT(ctx->fsctx.u_out_dim_id != -1); + DrawTexture(ctx->fsctx.output, 0, 0, WHITE); + EndShaderMode(); + DrawFPS(20, 20); + DrawTextEx(font, txt[txt_idx], pos.rl, 60, 6, BLACK); + + EndDrawing(); + + if (IsKeyPressed(KEY_R)) + ctx->flags |= RELOAD_SHADERS; } diff --git a/build.sh b/build.sh @@ -1,7 +1,7 @@ #!/bin/sh cflags="-march=native -ggdb -O0 -Wall" -ldflags="-lraylib" +ldflags="-lraylib -lGL" # Hot Reloading/Debugging cflags="$cflags -D_DEBUG" diff --git a/main.c b/main.c @@ -3,7 +3,20 @@ #include <raylib.h> #include <rlgl.h> +#include <stdio.h> +#include <stdlib.h> + +#define GL_GLEXT_PROTOTYPES 1 +#include <GL/glcorearb.h> +#include <GL/glext.h> + #include "util.c" +#include "os_unix.c" + +static char *compute_shader_paths[CS_LAST] = { + //[CS_MIN_MAX] = "shaders/min_max.glsl", + [CS_UFORCES] = "shaders/uforces.glsl", +}; #ifndef _DEBUG @@ -12,23 +25,19 @@ static void do_debug(void) { } #else #include <dlfcn.h> -#include <sys/stat.h> #include <time.h> -#include <unistd.h> -static const char *libname = "./beamformer.so"; +static char *libname = "./beamformer.so"; static void *libhandle; -typedef void (do_beamformer_fn)(BeamformerCtx*); +typedef void do_beamformer_fn(BeamformerCtx *, Arena, s8); static do_beamformer_fn *do_beamformer; -static struct timespec -get_filetime(const char *name) +static os_filetime +get_filetime(char *name) { - struct stat sb; - if (stat(name, &sb) < 0) - return (struct timespec){0}; - return sb.st_mtim; + os_file_stats fstats = os_get_file_stats(name); + return fstats.timestamp; } static b32 @@ -55,8 +64,8 @@ load_library(const char *lib) static void do_debug(void) { - static struct timespec updated_time; - struct timespec test_time = get_filetime(libname); + static os_filetime updated_time; + os_filetime test_time = get_filetime(libname); if (filetime_is_newer(test_time, updated_time)) { struct timespec sleep_time = {.tv_sec = 0, .tv_nsec = 100e6}; nanosleep(&sleep_time, &sleep_time); @@ -67,12 +76,141 @@ do_debug(void) #endif /* _DEBUG */ +#if 0 +static void +update_output_image_dimensions(BeamformerCtx *ctx, uv2 new_size) +{ + UnloadTexture(ctx->fsctx.output); + rlUnloadShaderBuffer(ctx->csctx.out_img_ssbo); + + size out_img_size = new_size.w * new_size.h * sizeof(f32); + ctx->csctx.out_img_ssbo = rlLoadShaderBuffer(out_img_size, NULL, GL_DYNAMIC_COPY); + + Texture2D t = ctx->fsctx.output; + t.width = new_size.w; + t.height = new_size.h; + t.id = rlLoadTexture(0, t.width, t.height, t.format, t.mipmaps); + ctx->fsctx.output = t; +} +#endif + +static void +init_compute_shader_ctx(ComputeShaderCtx *ctx, uv3 rf_data_dim, uv2 out_img_dim) +{ + for (u32 i = 0; i < ARRAY_COUNT(ctx->programs); i++) { + char *shader_text = LoadFileText(compute_shader_paths[i]); + u32 shader_id = rlCompileShader(shader_text, RL_COMPUTE_SHADER); + ctx->programs[i] = rlLoadComputeShaderProgram(shader_id); + glDeleteShader(shader_id); + UnloadFileText(shader_text); + } + + ctx->u_rf_dim_id = glGetUniformLocation(ctx->programs[CS_UFORCES], "u_rf_dim"); + ctx->u_out_dim_id = glGetUniformLocation(ctx->programs[CS_UFORCES], "u_out_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(f32); + size out_img_size = out_img_dim.w * out_img_dim.h * sizeof(f32); + + ctx->rf_data_ssbo = rlLoadShaderBuffer(rf_data_size, NULL, GL_DYNAMIC_COPY); + ctx->out_img_ssbo = rlLoadShaderBuffer(out_img_size, NULL, GL_DYNAMIC_COPY); +} + +static void +init_fragment_shader_ctx(FragmentShaderCtx *ctx, uv2 window_size) +{ + ctx->shader = LoadShader(NULL, "shaders/render.glsl"); + ctx->u_out_dim_id = glGetUniformLocation(ctx->shader.id, "u_out_img_dim"); + glUniform2uiv(ctx->u_out_dim_id, 1, window_size.E); + /* TODO: add min max uniform */ + + /* output texture for image blitting */ + Texture2D new; + new.width = window_size.w; + new.height = window_size.h; + new.mipmaps = 1; + new.format = RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32; + new.id = rlLoadTexture(0, new.width, new.height, new.format, new.mipmaps); + ctx->output = new; +} + +static u32 +compile_shader(Arena a, u32 type, s8 shader) +{ + u32 sid = glCreateShader(type); + glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len); + glCompileShader(sid); + + 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) { + TraceLog(LOG_WARNING, "SHADER: [ID %u] %s shader failed to compile", sid, stype); + i32 len = 0; + glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len); + s8 err = s8alloc(&a, len); + 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); + } + + return sid; +} + +static void +reload_shaders(Arena a, BeamformerCtx *ctx) +{ + u32 cs_programs[ARRAY_COUNT(((ComputeShaderCtx *)0)->programs)]; + + ComputeShaderCtx *csctx = &ctx->csctx; + for (u32 i = 0; i < ARRAY_COUNT(cs_programs); i++) { + Arena tmp = a; + os_file_stats fs = os_get_file_stats(compute_shader_paths[i]); + s8 shader_text = os_read_file(&tmp, compute_shader_paths[i], fs.filesize); + u32 shader_id = compile_shader(tmp, GL_COMPUTE_SHADER, shader_text); + + if (shader_id != rlGetShaderIdDefault()) { + glDeleteProgram(csctx->programs[i]); + csctx->programs[i] = rlLoadComputeShaderProgram(shader_id); + } + + glDeleteShader(shader_id); + } + + csctx->u_rf_dim_id = glGetUniformLocation(csctx->programs[CS_UFORCES], "u_rf_dim"); + csctx->u_out_dim_id = glGetUniformLocation(csctx->programs[CS_UFORCES], "u_out_dim"); + + Shader updated_fs = LoadShader(NULL, "shaders/render.glsl"); + if (updated_fs.id != rlGetShaderIdDefault()) { + UnloadShader(ctx->fsctx.shader); + ctx->fsctx.shader = updated_fs; + ctx->fsctx.u_out_dim_id = GetShaderLocation(updated_fs, "u_out_img_dim"); + glUniform2ui(ctx->fsctx.u_out_dim_id, ctx->out_img_dim.x, ctx->out_img_dim.y); + } +} + int main(void) { BeamformerCtx ctx = {0}; + Arena temp_arena = os_new_arena(256 * MEGABYTE); + + char *decoded_name = "/tmp/decoded.bin"; + os_file_stats decoded_stats = os_get_file_stats(decoded_name); + s8 raw_rf_data = os_read_file(&temp_arena, decoded_name, decoded_stats.filesize); + ctx.window_size = (uv2){.w = 720, .h = 720}; + ctx.out_img_dim = (uv2){.w = 720, .h = 720}; ctx.bg = DARKGRAY; ctx.fg = (Color){ .r = 0xea, .g = 0xe1, .b = 0xb4, .a = 0xff }; @@ -80,11 +218,19 @@ main(void) SetConfigFlags(FLAG_VSYNC_HINT); InitWindow(ctx.window_size.w, ctx.window_size.h, "OGL Beamformer"); + init_compute_shader_ctx(&ctx.csctx, (uv3){.w = 4093, .h = 128, .d = 1}, ctx.window_size); + init_fragment_shader_ctx(&ctx.fsctx, ctx.window_size); + + ctx.flags |= RELOAD_SHADERS; + while(!WindowShouldClose()) { do_debug(); - BeginDrawing(); - do_beamformer(&ctx); - EndDrawing(); + if (ctx.flags & RELOAD_SHADERS) { + ctx.flags &= ~RELOAD_SHADERS; + reload_shaders(temp_arena, &ctx); + } + + do_beamformer(&ctx, temp_arena, raw_rf_data); } } diff --git a/os_unix.c b/os_unix.c @@ -0,0 +1,61 @@ +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +typedef struct timespec os_filetime; + +typedef struct { + size filesize; + os_filetime timestamp; +} os_file_stats; + +static Arena +os_new_arena(size capacity) +{ + Arena a = {0}; + + size pagesize = sysconf(_SC_PAGESIZE); + if (capacity % pagesize != 0) + capacity += (pagesize - capacity % pagesize); + + a.beg = mmap(0, capacity, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + if (a.beg == MAP_FAILED) + die("os_new_arena: couldn't allocate memory\n"); + a.end = a.beg + capacity; + return a; +} + +static s8 +os_read_file(Arena *a, char *fname, size fsize) +{ + i32 fd = open(fname, O_RDONLY); + if (fd < 0) + die("os_read_file: couldn't open file: %s\n", fname); + + s8 ret = s8alloc(a, fsize); + + size rlen = read(fd, ret.data, ret.len); + close(fd); + + if (rlen != ret.len) + die("os_read_file: couldn't read file: %s\n", fname); + + return ret; +} + +static os_file_stats +os_get_file_stats(char *fname) +{ + struct stat st; + + if (stat(fname, &st) < 0) { + fputs("os_get_file_stats: couldn't stat file\n",stderr); + return (os_file_stats){0}; + } + + return (os_file_stats){ + .filesize = st.st_size, + .timestamp = st.st_mtim, + }; +} diff --git a/util.c b/util.c @@ -14,6 +14,7 @@ #define DEBUG_EXPORT static #endif +typedef uint8_t u8; typedef int32_t i32; typedef uint32_t u32; typedef uint32_t b32; @@ -21,6 +22,10 @@ typedef float f32; typedef double f64; typedef ptrdiff_t size; +typedef struct { u8 *beg, *end; } Arena; + +typedef struct { size len; u8 *data; } s8; + typedef union { struct { u32 x, y; }; struct { u32 w, h; }; @@ -28,6 +33,12 @@ typedef union { } 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; @@ -40,15 +51,96 @@ typedef union { Vector4 rl; } v4; +enum compute_shaders { +// CS_FORCES, +// CS_HADAMARD_DECODE, +// CS_HERCULES, +// CS_MIN_MAX, + CS_UFORCES, + CS_LAST +}; + +enum program_flags { + RELOAD_SHADERS = 1 << 0, +}; + +typedef struct { + u32 programs[CS_LAST]; + + u32 out_img_ssbo; + + u32 rf_data_ssbo; + uv3 rf_data_dim; + + i32 u_rf_dim_id; + i32 u_out_dim_id; +} ComputeShaderCtx; + +typedef struct { + Shader shader; + Texture2D output; + i32 u_out_dim_id; +} FragmentShaderCtx; + typedef struct { uv2 window_size; u32 flags; Color bg, fg; + + uv2 out_img_dim; /* shared output image dimension */ + + ComputeShaderCtx csctx; + FragmentShaderCtx fsctx; } BeamformerCtx; +#define MEGABYTE (1024ULL * 1024ULL) +#define GIGABYTE (1024ULL * 1024ULL * 1024ULL) + #define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a)) #define ABS(x) ((x) < 0 ? (-x) : (x)) #define CLAMP(x, a, b) ((x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)) +static void __attribute__((noreturn)) +die(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + exit(1); +} + +static void * +mem_clear(u8 *p, u8 c, size len) +{ + while (len) p[--len] = c; + return p; +} + +#define alloc(a, t, n) (t *)alloc_(a, sizeof(t), _Alignof(t), n) +__attribute((malloc, alloc_size(4, 2), alloc_align(3))) +static void * +alloc_(Arena *a, size len, size align, size count) +{ + size padding = -(uintptr_t)a->beg & (align - 1); + size available = a->end - a->beg - padding; + if (available < 0 || count > available / len) { + ASSERT(0); + die("arena OOM\n"); + } + void *p = a->beg + padding; + a->beg += padding + count * len; + /* TODO: Performance? */ + return mem_clear(p, 0, count * len); +} + +static s8 +s8alloc(Arena *a, size len) +{ + return (s8){ .data = alloc(a, u8, len), .len = len }; +} + #endif /*_UTIL_C_ */