ogl_beamforming

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

Commit: 744d93141298ce58994b740def9e7801d5735d10
Parent: b4c42403b82967692920ab8ed6614e259b166bb8
Author: Randy Palamar
Date:   Wed,  2 Jul 2025 10:09:07 -0600

ui: 3D X-Plane view

Diffstat:
Mbeamformer.c | 28++++++++++++++++------------
Mbeamformer.h | 49++++++++++++++++++++++++++++++++++---------------
Mbeamformer_parameters.h | 1+
Mbeamformer_work_queue.h | 6+++---
Mintrinsics.c | 6++++++
Amath.c | 444+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mopengl.h | 1+
Mshaders/render_2d.frag.glsl | 9+++------
Ashaders/render_3d.frag.glsl | 48++++++++++++++++++++++++++++++++++++++++++++++++
Mstatic.c | 171++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mui.c | 591+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mutil.c | 285+------------------------------------------------------------------------------
Mutil.h | 10++++++++--
13 files changed, 1130 insertions(+), 519 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -35,7 +35,7 @@ global renderdoc_end_frame_capture_fn *end_frame_capture; #endif typedef struct { - BeamformComputeFrame *frames; + BeamformerComputeFrame *frames; u32 capacity; u32 offset; u32 cursor; @@ -66,10 +66,10 @@ compute_frame_iterator(BeamformerCtx *ctx, u32 start_index, u32 needed_frames) return result; } -function BeamformComputeFrame * +function BeamformerComputeFrame * frame_next(ComputeFrameIterator *bfi) { - BeamformComputeFrame *result = 0; + BeamformerComputeFrame *result = 0; if (bfi->cursor != bfi->needed_frames) { u32 index = (bfi->offset + bfi->cursor++) % bfi->capacity; result = bfi->frames + index; @@ -78,7 +78,7 @@ frame_next(ComputeFrameIterator *bfi) } function void -alloc_beamform_frame(GLParams *gp, BeamformFrame *out, uv3 out_dim, s8 name, Arena arena) +alloc_beamform_frame(GLParams *gp, BeamformerFrame *out, uv3 out_dim, s8 name, Arena arena) { out->dim.x = MAX(1, out_dim.x); out->dim.y = MAX(1, out_dim.y); @@ -104,6 +104,10 @@ alloc_beamform_frame(GLParams *gp, BeamformFrame *out, uv3 out_dim, s8 name, Are glDeleteTextures(1, &out->texture); glCreateTextures(GL_TEXTURE_3D, 1, &out->texture); glTextureStorage3D(out->texture, out->mips, GL_RG32F, out->dim.x, out->dim.y, out->dim.z); + + glTextureParameteri(out->texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTextureParameteri(out->texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + LABEL_GL_OBJECT(GL_TEXTURE, out->texture, stream_to_s8(&label)); } @@ -263,7 +267,7 @@ compute_cursor_finished(struct compute_cursor *cursor) } function void -do_compute_shader(BeamformerCtx *ctx, Arena arena, BeamformComputeFrame *frame, BeamformerShaderKind shader) +do_compute_shader(BeamformerCtx *ctx, Arena arena, BeamformerComputeFrame *frame, BeamformerShaderKind shader) { ComputeShaderCtx *csctx = &ctx->csctx; BeamformerSharedMemory *sm = ctx->shared_memory.region; @@ -373,9 +377,9 @@ do_compute_shader(BeamformerCtx *ctx, Arena arena, BeamformComputeFrame *frame, }break; case BeamformerShaderKind_Sum:{ u32 aframe_index = ctx->averaged_frame_index % ARRAY_COUNT(ctx->averaged_frames); - BeamformComputeFrame *aframe = ctx->averaged_frames + aframe_index; - aframe->ready_to_present = 0; - aframe->frame.id = ctx->averaged_frame_index; + BeamformerComputeFrame *aframe = ctx->averaged_frames + aframe_index; + aframe->ready_to_present = 0; + aframe->frame.id = ctx->averaged_frame_index; /* TODO(rnp): hack we need a better way of specifying which frames to sum; * this is fine for rolling averaging but what if we want to do something else */ assert(frame >= ctx->beamform_frames); @@ -386,10 +390,10 @@ do_compute_shader(BeamformerCtx *ctx, Arena arena, BeamformComputeFrame *frame, u32 *in_textures = push_array(&arena, u32, MAX_BEAMFORMED_SAVED_FRAMES); ComputeFrameIterator cfi = compute_frame_iterator(ctx, 1 + base_index - to_average, to_average); - for (BeamformComputeFrame *it = frame_next(&cfi); it; it = frame_next(&cfi)) + for (BeamformerComputeFrame *it = frame_next(&cfi); it; it = frame_next(&cfi)) in_textures[frame_count++] = it->frame.texture; - ASSERT(to_average == frame_count); + assert(to_average == frame_count); do_sum_shader(csctx, in_textures, frame_count, 1 / (f32)frame_count, aframe->frame.texture, aframe->frame.dim); @@ -541,7 +545,7 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena arena, iptr gl_co BeamformerExportContext *ec = &work->export_context; switch (ec->kind) { case BeamformerExportKind_BeamformedData:{ - BeamformComputeFrame *frame = ctx->latest_frame; + BeamformerComputeFrame *frame = ctx->latest_frame; assert(frame->ready_to_present); u32 texture = frame->frame.texture; uv3 dim = frame->frame.dim; @@ -644,7 +648,7 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena arena, iptr gl_co atomic_store_u32(&cs->processing_compute, 1); start_renderdoc_capture(gl_context); - BeamformComputeFrame *frame = work->frame; + BeamformerComputeFrame *frame = work->frame; uv3 try_dim = make_valid_test_dim(bp->output_points); if (!uv3_equal(try_dim, frame->frame.dim)) alloc_beamform_frame(&ctx->gl, &frame->frame, try_dim, s8("Beamformed_Data"), arena); diff --git a/beamformer.h b/beamformer.h @@ -57,13 +57,23 @@ typedef struct { #undef X } CudaLib; -#define FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC 1 -#define FRAME_VIEW_RENDER_THRESHOLD_LOC 2 -#define FRAME_VIEW_RENDER_GAMMA_LOC 3 -#define FRAME_VIEW_RENDER_LOG_SCALE_LOC 4 +/* TODO(rnp): this should be a UBO */ +#define FRAME_VIEW_MODEL_MATRIX_LOC 0 +#define FRAME_VIEW_VIEW_MATRIX_LOC 1 +#define FRAME_VIEW_PROJ_MATRIX_LOC 2 +#define FRAME_VIEW_DYNAMIC_RANGE_LOC 3 +#define FRAME_VIEW_THRESHOLD_LOC 4 +#define FRAME_VIEW_GAMMA_LOC 5 +#define FRAME_VIEW_LOG_SCALE_LOC 6 +#define FRAME_VIEW_BB_COLOUR_LOC 7 +#define FRAME_VIEW_BB_FRACTION_LOC 8 + +#define FRAME_VIEW_BB_COLOUR 0.92, 0.88, 0.78, 1.0 +#define FRAME_VIEW_BB_FRACTION 0.007f typedef struct { - u32 shader; + /* NOTE(rnp): shaders[0] -> 2D render, shader[1] -> 3D render */ + u32 shaders[2]; u32 framebuffer; u32 vao; u32 vbo; @@ -73,6 +83,12 @@ typedef struct { #include "beamformer_parameters.h" #include "beamformer_work_queue.h" +typedef struct { + iptr elements_offset; + i32 elements; + u32 buffer; + u32 vao; +} BeamformerRenderModel; typedef struct { u32 programs[BeamformerShaderKind_ComputeCount]; @@ -99,6 +115,8 @@ typedef struct { uv4 dec_data_dim; u32 rf_raw_size; + + BeamformerRenderModel unit_cube_model; } ComputeShaderCtx; typedef enum { @@ -142,7 +160,8 @@ typedef struct { ComputeTimingInfo buffer[4096]; } ComputeTimingTable; -typedef struct BeamformFrame { +typedef struct BeamformerFrame BeamformerFrame; +struct BeamformerFrame { uv3 dim; u32 texture; @@ -156,12 +175,12 @@ typedef struct BeamformFrame { u32 compound_count; u32 id; - struct BeamformFrame *next; -} BeamformFrame; + BeamformerFrame *next; +}; -struct BeamformComputeFrame { - BeamformFrame frame; - b32 ready_to_present; +struct BeamformerComputeFrame { + BeamformerFrame frame; + b32 ready_to_present; BeamformerViewPlaneTag view_plane_tag; }; @@ -194,14 +213,14 @@ typedef struct { /* TODO(rnp): this is nasty and should be removed */ b32 ui_read_params; - BeamformComputeFrame beamform_frames[MAX_BEAMFORMED_SAVED_FRAMES]; - BeamformComputeFrame *latest_frame; + BeamformerComputeFrame beamform_frames[MAX_BEAMFORMED_SAVED_FRAMES]; + BeamformerComputeFrame *latest_frame; u32 next_render_frame_index; u32 display_frame_index; /* NOTE: this will only be used when we are averaging */ - u32 averaged_frame_index; - BeamformComputeFrame averaged_frames[2]; + u32 averaged_frame_index; + BeamformerComputeFrame averaged_frames[2]; ComputeShaderCtx csctx; diff --git a/beamformer_parameters.h b/beamformer_parameters.h @@ -24,6 +24,7 @@ typedef enum { COMPUTE_SHADERS #undef X BeamformerShaderKind_Render2D, + BeamformerShaderKind_Render3D, BeamformerShaderKind_Count, BeamformerShaderKind_ComputeCount = BeamformerShaderKind_Render2D, diff --git a/beamformer_work_queue.h b/beamformer_work_queue.h @@ -4,8 +4,8 @@ #define BEAMFORMER_SHARED_MEMORY_VERSION (7UL) -typedef struct BeamformComputeFrame BeamformComputeFrame; -typedef struct ShaderReloadContext ShaderReloadContext; +typedef struct BeamformerComputeFrame BeamformerComputeFrame; +typedef struct ShaderReloadContext ShaderReloadContext; typedef enum { BeamformerWorkKind_Compute, @@ -58,7 +58,7 @@ typedef enum {BEAMFORMER_SHARED_MEMORY_LOCKS BeamformerSharedMemoryLockKind_Coun /* NOTE: discriminated union based on type */ typedef struct { union { - BeamformComputeFrame *frame; + BeamformerComputeFrame *frame; BeamformerUploadContext upload_context; BeamformerExportContext export_context; ShaderReloadContext *shader_reload_context; diff --git a/intrinsics.c b/intrinsics.c @@ -39,6 +39,9 @@ #define atomic_or_u32(ptr, n) _InterlockedOr((volatile u32 *)(ptr), (n)) #define atan2_f32(y, x) atan2f(y, x) + #define cos_f32(a) cosf(a) + #define sin_f32(a) sinf(a) + #define tan_f32(a) tanf(a) #define ceil_f32(a) ceilf(a) #define sqrt_f32(a) sqrtf(a) @@ -68,6 +71,9 @@ #define atomic_store_u32 atomic_store_u64 #define atan2_f32(y, x) __builtin_atan2f(y, x) + #define cos_f32(a) __builtin_cosf(a) + #define sin_f32(a) __builtin_sinf(a) + #define tan_f32(a) __builtin_tanf(a) #define ceil_f32(a) __builtin_ceilf(a) #define sqrt_f32(a) __builtin_sqrtf(a) diff --git a/math.c b/math.c @@ -0,0 +1,444 @@ +function void +fill_kronecker_sub_matrix(i32 *out, i32 out_stride, i32 scale, i32 *b, uv2 b_dim) +{ + f32x4 vscale = dup_f32x4(scale); + for (u32 i = 0; i < b_dim.y; i++) { + for (u32 j = 0; j < b_dim.x; j += 4, b += 4) { + f32x4 vb = cvt_i32x4_f32x4(load_i32x4(b)); + store_i32x4(cvt_f32x4_i32x4(mul_f32x4(vscale, vb)), out + j); + } + out += out_stride; + } +} + +/* NOTE: this won't check for valid space/etc and assumes row major order */ +function void +kronecker_product(i32 *out, i32 *a, uv2 a_dim, i32 *b, uv2 b_dim) +{ + uv2 out_dim = {.x = a_dim.x * b_dim.x, .y = a_dim.y * b_dim.y}; + ASSERT(out_dim.y % 4 == 0); + for (u32 i = 0; i < a_dim.y; i++) { + i32 *vout = out; + for (u32 j = 0; j < a_dim.x; j++, a++) { + fill_kronecker_sub_matrix(vout, out_dim.y, *a, b, b_dim); + vout += b_dim.y; + } + out += out_dim.y * b_dim.x; + } +} + +/* NOTE/TODO: to support even more hadamard sizes use the Paley construction */ +function i32 * +make_hadamard_transpose(Arena *a, u32 dim) +{ + read_only local_persist i32 hadamard_12_12_transpose[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, + 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, + 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, + 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, + 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, + 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, + 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, + 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, + 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, + 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, + 1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, + }; + + read_only local_persist i32 hadamard_20_20_transpose[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, + 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, + 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, + 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, + 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, + 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, + 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, + 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, + 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, + 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, + 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, + 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, + 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, + 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, + 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, + 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, + 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, + 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, + 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, + }; + + i32 *result = 0; + + b32 power_of_2 = ISPOWEROF2(dim); + b32 multiple_of_12 = dim % 12 == 0; + b32 multiple_of_20 = dim % 20 == 0; + iz elements = dim * dim; + + u32 base_dim = 0; + if (power_of_2) { + base_dim = dim; + } else if (multiple_of_20 && ISPOWEROF2(dim / 20)) { + base_dim = 20; + dim /= 20; + } else if (multiple_of_12 && ISPOWEROF2(dim / 12)) { + base_dim = 12; + dim /= 12; + } + + if (ISPOWEROF2(dim) && base_dim && arena_capacity(a, i32) >= elements * (1 + (dim != base_dim))) { + result = push_array(a, i32, elements); + + Arena tmp = *a; + i32 *m = dim == base_dim ? result : push_array(&tmp, i32, elements); + + #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 + + i32 *m2 = 0; + uv2 m2_dim; + switch (base_dim) { + case 12:{ m2 = hadamard_12_12_transpose; m2_dim = (uv2){{12, 12}}; }break; + case 20:{ m2 = hadamard_20_20_transpose; m2_dim = (uv2){{20, 20}}; }break; + } + if (m2) kronecker_product(result, m, (uv2){{dim, dim}}, m2, m2_dim); + } + + return result; +} + +function b32 +uv2_equal(uv2 a, uv2 b) +{ + return a.x == b.x && a.y == b.y; +} + +function b32 +uv3_equal(uv3 a, uv3 b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z; +} + +function v2 +clamp_v2_rect(v2 v, Rect r) +{ + v2 result = v; + result.x = CLAMP(v.x, r.pos.x, r.pos.x + r.size.x); + result.y = CLAMP(v.y, r.pos.y, r.pos.y + r.size.y); + return result; +} + +function v2 +v2_scale(v2 a, f32 scale) +{ + v2 result; + result.x = a.x * scale; + result.y = a.y * scale; + return result; +} + +function v2 +v2_add(v2 a, v2 b) +{ + v2 result; + result.x = a.x + b.x; + result.y = a.y + b.y; + return result; +} + +function v2 +v2_sub(v2 a, v2 b) +{ + v2 result = v2_add(a, v2_scale(b, -1.0f)); + return result; +} + +function v2 +v2_mul(v2 a, v2 b) +{ + v2 result; + result.x = a.x * b.x; + result.y = a.y * b.y; + return result; +} + +function v2 +v2_div(v2 a, v2 b) +{ + v2 result; + result.x = a.x / b.x; + result.y = a.y / b.y; + return result; +} + +function v2 +v2_floor(v2 a) +{ + v2 result; + result.x = (i32)a.x; + result.y = (i32)a.y; + return result; +} + +function f32 +v2_magnitude(v2 a) +{ + f32 result = sqrt_f32(a.x * a.x + a.y * a.y); + return result; +} + + +function v3 +cross(v3 a, v3 b) +{ + v3 result; + result.x = a.y * b.z - a.z * b.y; + result.y = a.z * b.x - a.x * b.z; + result.z = a.x * b.y - a.y * b.x; + return result; +} + +function v3 +v3_scale(v3 a, f32 scale) +{ + v3 result; + result.x = scale * a.x; + result.y = scale * a.y; + result.z = scale * a.z; + return result; +} + +function v3 +v3_add(v3 a, v3 b) +{ + v3 result; + result.x = a.x + b.x; + result.y = a.y + b.y; + result.z = a.z + b.z; + return result; +} + +function v3 +v3_sub(v3 a, v3 b) +{ + v3 result = v3_add(a, v3_scale(b, -1.0f)); + return result; +} + +function f32 +v3_dot(v3 a, v3 b) +{ + f32 result = a.x * b.x + a.y * b.y + a.z * b.z; + return result; +} + +function f32 +v3_length_squared(v3 a) +{ + f32 result = v3_dot(a, a); + return result; +} + +function v3 +v3_normalize(v3 a) +{ + v3 result = v3_scale(a, 1.0f / sqrt_f32(v3_length_squared(a))); + return result; +} + +function uv4 +uv4_from_u32_array(u32 v[4]) +{ + uv4 result; + result.E[0] = v[0]; + result.E[1] = v[1]; + result.E[2] = v[2]; + result.E[3] = v[3]; + return result; +} + +function b32 +uv4_equal(uv4 a, uv4 b) +{ + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; +} + +function v4 +v4_from_f32_array(f32 v[4]) +{ + v4 result; + result.E[0] = v[0]; + result.E[1] = v[1]; + result.E[2] = v[2]; + result.E[3] = v[3]; + return result; +} + +function v4 +v4_scale(v4 a, f32 scale) +{ + v4 result; + result.x = scale * a.x; + result.y = scale * a.y; + result.z = scale * a.z; + result.w = scale * a.w; + return result; +} + +function v4 +v4_add(v4 a, v4 b) +{ + v4 result; + result.x = a.x + b.x; + result.y = a.y + b.y; + result.z = a.z + b.z; + result.w = a.w + b.w; + return result; +} + +function v4 +v4_sub(v4 a, v4 b) +{ + v4 result = v4_add(a, v4_scale(b, -1)); + return result; +} + +function f32 +v4_dot(v4 a, v4 b) +{ + f32 result = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + return result; +} + +function v4 +v4_lerp(v4 a, v4 b, f32 t) +{ + v4 result = v4_add(a, v4_scale(v4_sub(b, a), t)); + return result; +} + +function v4 +m4_row(m4 a, u32 row) +{ + v4 result; + result.E[0] = a.c[0].E[row]; + result.E[1] = a.c[1].E[row]; + result.E[2] = a.c[2].E[row]; + result.E[3] = a.c[3].E[row]; + return result; +} + +function v4 +m4_column(m4 a, u32 column) +{ + v4 result = a.c[column]; + return result; +} + +function m4 +m4_mul(m4 a, m4 b) +{ + m4 result; + for (u32 i = 0; i < countof(result.E); i++) { + u32 base = i / 4; + u32 sub = i % 4; + v4 v1 = m4_row(a, base); + v4 v2 = m4_column(b, sub); + result.E[i] = v4_dot(v1, v2); + } + return result; +} + +function m4 +m4_rotation_about_y(f32 turns) +{ + f32 sa = sin_f32(turns * 2 * PI); + f32 ca = cos_f32(turns * 2 * PI); + m4 result; + result.c[0] = (v4){{ca, 0, -sa, 0}}; + result.c[1] = (v4){{0, 1, 0, 0}}; + result.c[2] = (v4){{sa, 0, ca, 0}}; + result.c[3] = (v4){{0, 0, 0, 1}}; + return result; +} + +function m4 +y_aligned_volume_transform(v3 extent, v3 translation, f32 rotation_turns) +{ + m4 S; + S.c[0] = (v4){{extent.x, 0, 0, 0}}; + S.c[1] = (v4){{0, extent.y, 0, 0}}; + S.c[2] = (v4){{0, 0, extent.z, 0}}; + S.c[3] = (v4){{0, 0, 0, 1}}; + + m4 R = m4_rotation_about_y(rotation_turns); + + m4 T; + T.c[0] = (v4){{1, 0, 0, translation.x}}; + T.c[1] = (v4){{0, 1, 0, translation.y}}; + T.c[2] = (v4){{0, 0, 1, translation.z}}; + T.c[3] = (v4){{0, 0, 0, 1}}; + + m4 result = m4_mul(m4_mul(R, S), T); + return result; +} + +function v4 +m4_mul_v4(m4 a, v4 v) +{ + v4 result; + result.x = v4_dot(m4_row(a, 0), v); + result.y = v4_dot(m4_row(a, 1), v); + result.z = v4_dot(m4_row(a, 2), v); + result.w = v4_dot(m4_row(a, 3), v); + return result; +} + +function m4 +perspective_projection(f32 n, f32 f, f32 fov, f32 aspect) +{ + m4 result; + f32 t = tan_f32(fov / 2.0f); + f32 r = t * aspect; + f32 a = -(f + n) / (f - n); + f32 b = -2 * f * n / (f - n); + result.c[0] = (v4){{1 / r, 0, 0, 0}}; + result.c[1] = (v4){{0, 1 / t, 0, 0}}; + result.c[2] = (v4){{0, 0, a, -1}}; + result.c[3] = (v4){{0, 0, b, 0}}; + return result; +} + +function m4 +camera_look_at(v3 camera, v3 point) +{ + v3 orthogonal = {{0, 1.0f, 0}}; + v3 normal = v3_normalize(v3_sub(camera, point)); + v3 right = cross(orthogonal, normal); + v3 up = cross(normal, right); + + v3 translate; + camera = v3_sub((v3){0}, camera); + translate.x = v3_dot(camera, right); + translate.y = v3_dot(camera, up); + translate.z = v3_dot(camera, normal); + + m4 result; + result.c[0] = (v4){{right.x, up.x, normal.x, 0}}; + result.c[1] = (v4){{right.y, up.y, normal.y, 0}}; + result.c[2] = (v4){{right.z, up.z, normal.z, 0}}; + result.c[3] = (v4){{translate.x, translate.y, translate.z, 1}}; + return result; +} diff --git a/opengl.h b/opengl.h @@ -21,6 +21,7 @@ #define GL_MAX_3D_TEXTURE_SIZE 0x8073 #define GL_MULTISAMPLE 0x809D #define GL_CLAMP_TO_BORDER 0x812D +#define GL_CLAMP_TO_EDGE 0x812F #define GL_DEPTH_COMPONENT24 0x81A6 #define GL_MAJOR_VERSION 0x821B #define GL_MINOR_VERSION 0x821C diff --git a/shaders/render_2d.frag.glsl b/shaders/render_2d.frag.glsl @@ -1,5 +1,4 @@ /* See LICENSE for license details. */ -layout(binding = 0) uniform sampler3D u_out_data_tex; /* input: h [0,360] | s,v [0, 1] * * output: rgb [0,1] */ @@ -12,15 +11,13 @@ vec3 hsv2rgb(vec3 hsv) void main() { - ivec3 out_data_dim = textureSize(u_out_data_tex, 0); + ivec3 out_data_dim = textureSize(u_texture, 0); //vec2 min_max = texelFetch(u_out_data_tex, ivec3(0), textureQueryLevels(u_out_data_tex) - 1).xy; /* TODO(rnp): select between x and y and specify slice */ - ivec2 coord = ivec2(texture_coordinate * vec2(out_data_dim.xz)); - ivec3 smp_coord = ivec3(coord.x, out_data_dim.y / 2, coord.y); - float smp = length(texelFetch(u_out_data_tex, smp_coord, 0).xy); - + vec3 tex_coord = vec3(texture_coordinate.x, 0.5, texture_coordinate.y); + float smp = length(texture(u_texture, tex_coord).xy); float threshold_val = pow(10.0f, u_threshold / 20.0f); smp = clamp(smp, 0.0f, threshold_val); smp = smp / threshold_val; diff --git a/shaders/render_3d.frag.glsl b/shaders/render_3d.frag.glsl @@ -0,0 +1,48 @@ +/* See LICENSE for license details. */ + +/* input: h [0,360] | s,v [0, 1] * + * output: rgb [0,1] */ +vec3 hsv2rgb(vec3 hsv) +{ + vec3 k = mod(vec3(5, 3, 1) + hsv.x / 60, 6); + k = max(min(min(k, 4 - k), 1), 0); + return hsv.z - hsv.z * hsv.y * k; +} + +bool bounding_box_test(vec3 coord, float p) +{ + bool result = false; + bvec3 tests = bvec3(1 - step(vec3(p), coord) * step(coord, vec3(1 - p))); + if ((tests.x && tests.y) || (tests.x && tests.z) || (tests.y && tests.z)) + result = true; + return result; +} + +void main() +{ + float smp = length(texture(u_texture, texture_coordinate).xy); + float threshold_val = pow(10.0f, u_threshold / 20.0f); + smp = clamp(smp, 0.0f, threshold_val); + smp = smp / threshold_val; + smp = pow(smp, u_gamma); + + //float t = test_texture_coordinate.y; + //smp = smp * smoothstep(-0.4, 1.1, t) * u_gain; + + if (u_log_scale) { + smp = 20 * log(smp) / log(10); + smp = clamp(smp, -u_db_cutoff, 0) / -u_db_cutoff; + smp = 1 - smp; + } + + if (bounding_box_test(test_texture_coordinate, u_bb_fraction)) { + out_colour = u_bb_colour; + } else { + out_colour = vec4(smp, smp, smp, 1); + } + + //out_colour = vec4(textureQueryLod(u_texture, texture_coordinate).y, 0, 0, 1); + //out_colour = vec4(abs(normal), 1); + //out_colour = vec4(1, 1, 1, smp); + //out_colour = vec4(smp * abs(normal), 1); +} diff --git a/static.c b/static.c @@ -212,6 +212,42 @@ function FILE_WATCH_CALLBACK_FN(load_cuda_lib) return result; } +function BeamformerRenderModel +render_model_from_arrays(f32 *vertices, f32 *normals, u16 *indices, u32 index_count) +{ + BeamformerRenderModel result = {0}; + + i32 buffer_size = index_count * (6 * sizeof(f32) + sizeof(u16)); + i32 indices_offset = index_count * (6 * sizeof(f32)); + i32 vert_size = index_count * 3 * sizeof(f32); + i32 ind_size = index_count * sizeof(u16); + + result.elements = index_count; + result.elements_offset = indices_offset; + + glCreateBuffers(1, &result.buffer); + glNamedBufferStorage(result.buffer, buffer_size, 0, GL_DYNAMIC_STORAGE_BIT); + glNamedBufferSubData(result.buffer, 0, vert_size, vertices); + glNamedBufferSubData(result.buffer, vert_size, vert_size, normals); + glNamedBufferSubData(result.buffer, indices_offset, ind_size, indices); + + glCreateVertexArrays(1, &result.vao); + glVertexArrayVertexBuffer(result.vao, 0, result.buffer, 0, 3 * sizeof(f32)); + glVertexArrayVertexBuffer(result.vao, 1, result.buffer, vert_size, 3 * sizeof(f32)); + glVertexArrayElementBuffer(result.vao, result.buffer); + + glEnableVertexArrayAttrib(result.vao, 0); + glEnableVertexArrayAttrib(result.vao, 1); + + glVertexArrayAttribFormat(result.vao, 0, 3, GL_FLOAT, 0, 0); + glVertexArrayAttribFormat(result.vao, 1, 3, GL_FLOAT, 0, vert_size); + + glVertexArrayAttribBinding(result.vao, 0, 0); + glVertexArrayAttribBinding(result.vao, 1, 0); + + return result; +} + #define GLFW_VISIBLE 0x00020004 void glfwWindowHint(i32, i32); iptr glfwCreateWindow(i32, i32, char *, iptr, iptr); @@ -375,15 +411,16 @@ setup_beamformer(BeamformerCtx *ctx, BeamformerInput *input, Arena *memory) render_2d->name = s8("shaders/render_2d.glsl"); render_2d->gl_type = GL_FRAGMENT_SHADER; render_2d->kind = BeamformerShaderKind_Render2D; - render_2d->shader = &fvr->shader; + render_2d->shader = fvr->shaders + 0; render_2d->header = s8("" "layout(location = 0) in vec2 texture_coordinate;\n" "layout(location = 0) out vec4 v_out_colour;\n\n" - "layout(location = " str(FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n" - "layout(location = " str(FRAME_VIEW_RENDER_THRESHOLD_LOC) ") uniform float u_threshold = 40;\n" - "layout(location = " str(FRAME_VIEW_RENDER_GAMMA_LOC) ") uniform float u_gamma = 1;\n" - "layout(location = " str(FRAME_VIEW_RENDER_LOG_SCALE_LOC) ") uniform bool u_log_scale;\n" - "\n#line 1\n"); + "layout(location = " str(FRAME_VIEW_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n" + "layout(location = " str(FRAME_VIEW_THRESHOLD_LOC) ") uniform float u_threshold = 40;\n" + "layout(location = " str(FRAME_VIEW_GAMMA_LOC) ") uniform float u_gamma = 1;\n" + "layout(location = " str(FRAME_VIEW_LOG_SCALE_LOC) ") uniform bool u_log_scale;\n" + "\n" + "layout(binding = 0) uniform sampler3D u_texture;\n"); render_2d->link = push_struct(memory, typeof(*render_2d)); render_2d->link->gl_type = GL_VERTEX_SHADER; render_2d->link->link = render_2d; @@ -400,6 +437,128 @@ setup_beamformer(BeamformerCtx *ctx, BeamformerInput *input, Arena *memory) "}\n"); reload_shader(&ctx->os, render_2d->path, (iptr)render_2d, *memory); os_add_file_watch(&ctx->os, memory, render_2d->path, reload_shader, (iptr)render_2d); + + ShaderReloadContext *render_3d = push_struct(memory, typeof(*render_3d)); + render_3d->beamformer_context = ctx; + render_3d->path = s8(static_path_join("shaders", "render_3d.frag.glsl")); + render_3d->name = s8("shaders/render_3d.glsl"); + render_3d->gl_type = GL_FRAGMENT_SHADER; + render_3d->kind = BeamformerShaderKind_Render3D; + render_3d->shader = fvr->shaders + 1; + render_3d->header = s8("" + "layout(location = 0) in vec3 normal;\n" + "layout(location = 1) in vec3 texture_coordinate;\n\n" + "layout(location = 2) in vec3 test_texture_coordinate;\n\n" + "layout(location = 0) out vec4 out_colour;\n\n" + "layout(location = " str(FRAME_VIEW_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n" + "layout(location = " str(FRAME_VIEW_THRESHOLD_LOC) ") uniform float u_threshold = 40;\n" + "layout(location = " str(FRAME_VIEW_GAMMA_LOC) ") uniform float u_gamma = 1;\n" + "layout(location = " str(FRAME_VIEW_LOG_SCALE_LOC) ") uniform bool u_log_scale;\n" + "layout(location = " str(FRAME_VIEW_BB_COLOUR_LOC) ") uniform vec4 u_bb_colour = vec4(" str(FRAME_VIEW_BB_COLOUR) ");\n" + "layout(location = " str(FRAME_VIEW_BB_FRACTION_LOC) ") uniform float u_bb_fraction = " str(FRAME_VIEW_BB_FRACTION) ";\n" + "\n" + "layout(binding = 0) uniform sampler3D u_texture;\n"); + + render_3d->link = push_struct(memory, typeof(*render_3d)); + render_3d->link->gl_type = GL_VERTEX_SHADER; + render_3d->link->link = render_3d; + render_3d->link->header = s8("" + "layout(location = 0) in vec3 v_position;\n" + "layout(location = 1) in vec3 v_normal;\n" + "\n" + "layout(location = 0) out vec3 f_normal;\n" + "layout(location = 1) out vec3 f_texture_coordinate;\n" + "layout(location = 2) out vec3 f_orig_texture_coordinate;\n" + "\n" + "layout(location = " str(FRAME_VIEW_MODEL_MATRIX_LOC) ") uniform mat4 u_model;\n" + "layout(location = " str(FRAME_VIEW_VIEW_MATRIX_LOC) ") uniform mat4 u_view;\n" + "layout(location = " str(FRAME_VIEW_PROJ_MATRIX_LOC) ") uniform mat4 u_projection;\n" + "\n" + "\n" + "void main()\n" + "{\n" + "\tvec3 pos = v_position;\n" + "\tf_orig_texture_coordinate = (2 * v_position + 1) / 2;\n" + //"\tif (v_position.y == -1) pos.x = clamp(v_position.x, -u_clip_fraction, u_clip_fraction);\n" + "\tvec3 tex_coord = (2 * pos + 1) / 2;\n" + "\tf_texture_coordinate = tex_coord.xzy;\n" + //"\tf_texture_coordinate = u_swizzle? tex_coord.xzy : tex_coord;\n" + //"\tf_normal = normalize(mat3(u_model) * v_normal);\n" + "\tf_normal = v_normal;\n" + "\tgl_Position = u_projection * u_view * u_model * vec4(pos, 1);\n" + "}\n"); + reload_shader(&ctx->os, render_3d->path, (iptr)render_3d, *memory); + os_add_file_watch(&ctx->os, memory, render_3d->path, reload_shader, (iptr)render_3d); + + f32 unit_cube_vertices[] = { + 0.5f, 0.5f, -0.5f, + 0.5f, 0.5f, -0.5f, + 0.5f, 0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, -0.5f, + -0.5f, 0.5f, -0.5f, + -0.5f, 0.5f, -0.5f, + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, -0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, -0.5f, 0.5f + }; + f32 unit_cube_normals[] = { + 0.0f, 0.0f, -1.0f, + 0.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, + 0.0f, -1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, -1.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, + 0.0f, 1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, + 0.0f, -1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 1.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, -1.0f, 0.0f, + -1.0f, 0.0f, 0.0f + }; + u16 unit_cube_indices[] = { + 1, 13, 19, + 1, 19, 7, + 9, 6, 18, + 9, 18, 21, + 23, 20, 14, + 23, 14, 17, + 16, 4, 10, + 16, 10, 22, + 5, 2, 8, + 5, 8, 11, + 15, 12, 0, + 15, 0, 3 + }; + + cs->unit_cube_model = render_model_from_arrays(unit_cube_vertices, unit_cube_normals, + unit_cube_indices, countof(unit_cube_indices)); } function void diff --git a/ui.c b/ui.c @@ -1,5 +1,7 @@ /* See LICENSE for license details. */ /* TODO(rnp): + * [ ]: MSAA for 3D views + * [ ]: refactor: render_2d.frag should be merged into render_3d.frag * [ ]: refactor: ui should be in its own thread and that thread should only be concerned with the ui * [ ]: refactor: ui shouldn't fully destroy itself on hot reload * [ ]: refactor: remove all the excessive measure_texts (cell drawing, hover_interaction in params table) @@ -198,8 +200,10 @@ typedef struct { X(FV_COPY_VERTICAL, "Copy Vertical") #define GLOBAL_MENU_BUTTONS \ - X(GM_OPEN_LIVE_VIEW_RIGHT, "Open Live View Right") \ - X(GM_OPEN_LIVE_VIEW_BELOW, "Open Live View Below") + X(GM_OPEN_LIVE_VIEW_RIGHT, "Open Live View Right") \ + X(GM_OPEN_LIVE_VIEW_BELOW, "Open Live View Below") \ + X(GM_OPEN_XPLANE_VIEW_RIGHT, "Open Live X-Plane Right") \ + X(GM_OPEN_XPLANE_VIEW_BELOW, "Open Live X-Plane Below") #define X(id, text) UI_BID_ ##id, typedef enum { @@ -228,6 +232,7 @@ typedef enum { V_INPUT = 1 << 0, V_TEXT = 1 << 1, V_RADIO_BUTTON = 1 << 2, + V_IMAGING_PARAM = 1 << 28, V_CAUSES_COMPUTE = 1 << 29, V_UPDATE_VIEW = 1 << 30, } VariableFlags; @@ -268,21 +273,28 @@ typedef enum { } BeamformerFrameViewKind; typedef struct BeamformerFrameView { - Variable lateral_scale_bar; - Variable axial_scale_bar; + union { + Variable plane_offsets[2]; + struct { + Variable lateral_scale_bar; + Variable axial_scale_bar; + }; + }; /* NOTE(rnp): these are pointers because they are added to the menu and will - * be put onto the freelist if the view is closed */ + * be put onto the freelist if the view is closed. Some are optional based on kind. */ Variable *lateral_scale_bar_active; Variable *axial_scale_bar_active; Variable *log_scale; + Variable *demo; /* NOTE(rnp): if type is LATEST selects which type of latest to use * if type is INDEXED selects the index */ Variable *cycler; u32 cycler_state; - v4 min_coordinate; - v4 max_coordinate; + v3 min_coordinate; + v3 max_coordinate; + f32 rotation; Ruler ruler; @@ -290,13 +302,12 @@ typedef struct BeamformerFrameView { Variable dynamic_range; Variable gamma; - FrameViewRenderContext *ctx; - BeamformFrame *frame; + BeamformerFrame *frame; struct BeamformerFrameView *prev, *next; uv2 texture_dim; + u32 textures[2]; u32 texture_mipmaps; - u32 texture; BeamformerFrameViewKind kind; b32 needs_update; @@ -339,7 +350,7 @@ struct BeamformerUI { BeamformerFrameView *views; BeamformerFrameView *view_freelist; - BeamformFrame *frame_freelist; + BeamformerFrame *frame_freelist; Interaction interaction; Interaction hot_interaction; @@ -347,9 +358,12 @@ struct BeamformerUI { InputState text_input_state; + /* TODO(rnp): ideally this isn't copied all over the place */ + BeamformerRenderModel unit_cube_model; + v2_sll *scale_bar_savepoint_freelist; - BeamformFrame *latest_plane[BeamformerViewPlaneTag_Count + 1]; + BeamformerFrame *latest_plane[BeamformerViewPlaneTag_Count + 1]; BeamformerUIParameters params; b32 flush_params; @@ -503,7 +517,7 @@ function Texture make_raylib_texture(BeamformerFrameView *v) { Texture result; - result.id = v->texture; + result.id = v->textures[0]; result.width = v->texture_dim.w; result.height = v->texture_dim.h; result.mipmaps = v->texture_mipmaps; @@ -590,7 +604,7 @@ table_iterator_new(Table *table, TableIteratorKind kind, Arena *a, i32 starting_ result->frame.row_index = starting_row; result->start_x = at.x; result->cell_rect.size.h = font->baseSize; - result->cell_rect.pos = add_v2(at, scale_v2(table->cell_pad, 0.5)); + result->cell_rect.pos = v2_add(at, v2_scale(table->cell_pad, 0.5)); result->cell_rect.pos.y += (starting_row - 1) * (result->cell_rect.size.h + table->cell_pad.h + table->row_border_thick); da_reserve(a, &result->stack, 4); return result; @@ -754,24 +768,26 @@ table_end_subtable(Table *table) function void resize_frame_view(BeamformerFrameView *view, uv2 dim) { - glDeleteTextures(1, &view->texture); - glCreateTextures(GL_TEXTURE_2D, 1, &view->texture); + glDeleteTextures(countof(view->textures), view->textures); + glCreateTextures(GL_TEXTURE_2D, countof(view->textures), view->textures); view->texture_dim = dim; view->texture_mipmaps = ctz_u32(MAX(dim.x, dim.y)) + 1; - /* TODO(rnp): HDR? */ - glTextureStorage2D(view->texture, view->texture_mipmaps, GL_RGBA8, dim.x, dim.y); - glGenerateTextureMipmap(view->texture); + glTextureStorage2D(view->textures[0], view->texture_mipmaps, GL_RGBA8, dim.x, dim.y); + glTextureStorage2D(view->textures[1], 1, GL_DEPTH_COMPONENT24, dim.x, dim.y); + + glGenerateTextureMipmap(view->textures[0]); /* NOTE(rnp): work around raylib's janky texture sampling */ - glTextureParameteri(view->texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTextureParameteri(view->texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTextureParameterfv(view->texture, GL_TEXTURE_BORDER_COLOR, (f32 []){0, 0, 0, 1}); - glTextureParameteri(view->texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTextureParameteri(view->texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTextureParameteri(view->textures[0], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTextureParameteri(view->textures[0], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTextureParameterfv(view->textures[0], GL_TEXTURE_BORDER_COLOR, (f32 []){0, 0, 0, 1}); + /* TODO(rnp): better choice when depth component is included */ + glTextureParameteri(view->textures[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTextureParameteri(view->textures[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST); /* TODO(rnp): add some ID for the specific view here */ - LABEL_GL_OBJECT(GL_TEXTURE, view->texture, s8("Frame View Texture")); + LABEL_GL_OBJECT(GL_TEXTURE, view->textures[0], s8("Frame View Texture")); } function void @@ -1063,23 +1079,40 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, bv->gamma.scaled_real32.val = 1.0f; bv->gamma.scaled_real32.scale = 0.05f; - fill_variable(&bv->lateral_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); - fill_variable(&bv->axial_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); - ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; - ScaleBar *axial = &bv->axial_scale_bar.scale_bar; - lateral->direction = SB_LATERAL; - axial->direction = SB_AXIAL; - lateral->scroll_scale = (v2){.x = -0.5e-3, .y = 0.5e-3}; - axial->scroll_scale = (v2){.x = 0, .y = 1e-3}; - lateral->zoom_starting_coord = F32_INFINITY; - axial->zoom_starting_coord = F32_INFINITY; - Variable *menu = result->view.menu = add_variable_group(ui, 0, arena, s8(""), VariableGroupKind_List, ui->small_font); menu->parent = result; - #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, 0, ui->small_font); - FRAME_VIEW_BUTTONS - #undef X + + switch (kind) { + case BeamformerFrameViewKind_3DXPlane:{ + resize_frame_view(bv, (uv2){{1024, 1024}}); + fill_variable(&bv->plane_offsets[0], var, s8("XZ Offset"), V_INPUT|V_IMAGING_PARAM, VT_F32, ui->small_font); + fill_variable(&bv->plane_offsets[1], var, s8("YZ Offset"), V_INPUT|V_IMAGING_PARAM, VT_F32, ui->small_font); + bv->demo = add_variable(ui, menu, arena, s8("Demo Mode"), V_INPUT|V_UPDATE_VIEW|V_RADIO_BUTTON, + VT_B32, ui->small_font); + }break; + default:{ + fill_variable(&bv->lateral_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); + fill_variable(&bv->axial_scale_bar, var, s8(""), V_INPUT, VT_SCALE_BAR, ui->small_font); + ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; + ScaleBar *axial = &bv->axial_scale_bar.scale_bar; + lateral->direction = SB_LATERAL; + axial->direction = SB_AXIAL; + lateral->scroll_scale = (v2){{-0.5e-3, 0.5e-3}}; + axial->scroll_scale = (v2){{ 0, 1.0e-3}}; + lateral->zoom_starting_coord = F32_INFINITY; + axial->zoom_starting_coord = F32_INFINITY; + + #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, 0, ui->small_font); + FRAME_VIEW_BUTTONS + #undef X + + bv->axial_scale_bar_active = add_variable(ui, menu, arena, s8("Axial Scale Bar"), + V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); + bv->lateral_scale_bar_active = add_variable(ui, menu, arena, s8("Lateral Scale Bar"), + V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); + }break; + } switch (kind) { case BeamformerFrameViewKind_Latest:{ @@ -1097,13 +1130,9 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena, default:{}break; } - bv->log_scale = add_variable(ui, menu, arena, s8("Log Scale"), - V_INPUT|V_UPDATE_VIEW|V_RADIO_BUTTON, VT_B32, - ui->small_font); - bv->axial_scale_bar_active = add_variable(ui, menu, arena, s8("Axial Scale Bar"), - V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); - bv->lateral_scale_bar_active = add_variable(ui, menu, arena, s8("Lateral Scale Bar"), - V_INPUT|V_RADIO_BUTTON, VT_B32, ui->small_font); + bv->log_scale = add_variable(ui, menu, arena, s8("Log Scale"), + V_INPUT|V_UPDATE_VIEW|V_RADIO_BUTTON, VT_B32, ui->small_font); + add_global_menu_to_group(ui, arena, menu); return result; } @@ -1165,30 +1194,30 @@ ui_split_region(BeamformerUI *ui, Variable *region, Variable *split_side, Region function void ui_fill_live_frame_view(BeamformerUI *ui, BeamformerFrameView *bv) { - ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; - ScaleBar *axial = &bv->axial_scale_bar.scale_bar; - lateral->min_value = ui->params.output_min_coordinate + 0; - lateral->max_value = ui->params.output_max_coordinate + 0; - axial->min_value = ui->params.output_min_coordinate + 2; - axial->max_value = ui->params.output_max_coordinate + 2; - bv->axial_scale_bar_active->bool32 = 1; - bv->lateral_scale_bar_active->bool32 = 1; - bv->ctx = ui->frame_view_render_context; - bv->axial_scale_bar.flags |= V_CAUSES_COMPUTE; - bv->lateral_scale_bar.flags |= V_CAUSES_COMPUTE; + if (bv->kind != BeamformerFrameViewKind_3DXPlane) { + ScaleBar *lateral = &bv->lateral_scale_bar.scale_bar; + ScaleBar *axial = &bv->axial_scale_bar.scale_bar; + lateral->min_value = ui->params.output_min_coordinate + 0; + lateral->max_value = ui->params.output_max_coordinate + 0; + axial->min_value = ui->params.output_min_coordinate + 2; + axial->max_value = ui->params.output_max_coordinate + 2; + bv->axial_scale_bar_active->bool32 = 1; + bv->lateral_scale_bar_active->bool32 = 1; + bv->axial_scale_bar.flags |= V_CAUSES_COMPUTE; + bv->lateral_scale_bar.flags |= V_CAUSES_COMPUTE; + } } function void -ui_add_live_frame_view(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) +ui_add_live_frame_view(BeamformerUI *ui, Variable *view, RegionSplitDirection direction, + BeamformerFrameViewKind kind) { Variable *region = view->parent; assert(region->type == VT_UI_REGION_SPLIT); assert(view->type == VT_UI_VIEW); Variable *new_region = ui_split_region(ui, region, view, direction); - new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, - BeamformerFrameViewKind_Latest, 1); - + new_region->region_split.right = add_beamformer_frame_view(ui, new_region, &ui->arena, kind, 1); ui_fill_live_frame_view(ui, new_region->region_split.right->group.first->generic); } @@ -1216,14 +1245,13 @@ ui_copy_frame(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) axial->min_value = &bv->min_coordinate.z; axial->max_value = &bv->max_coordinate.z; - bv->ctx = old->ctx; bv->needs_update = 1; bv->threshold.real32 = old->threshold.real32; bv->dynamic_range.real32 = old->dynamic_range.real32; bv->gamma.real32 = old->gamma.real32; bv->log_scale->bool32 = old->log_scale->bool32; - bv->min_coordinate = old->frame->min_coordinate; - bv->max_coordinate = old->frame->max_coordinate; + bv->min_coordinate = old->frame->min_coordinate.xyz; + bv->max_coordinate = old->frame->max_coordinate.xyz; bv->frame = SLLPop(ui->frame_freelist); if (!bv->frame) bv->frame = push_struct(&ui->arena, typeof(*bv->frame)); @@ -1241,6 +1269,103 @@ ui_copy_frame(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) resize_frame_view(bv, (uv2){.x = bv->frame->dim.x, .y = bv->frame->dim.z}); } +function v3 +x_plane_size(BeamformerUI *ui) +{ + v3 min = v4_from_f32_array(ui->params.output_min_coordinate).xyz; + v3 max = v4_from_f32_array(ui->params.output_max_coordinate).xyz; + v3 result = v3_sub(max, min); + swap(result.y, result.z); + result.x = MAX(1e-3, result.x); + result.y = MAX(1e-3, result.y); + result.z = MAX(1e-3, result.z); + return result; +} + +function v3 +x_plane_position(BeamformerUI *ui) +{ + f32 y_min = ui->params.output_min_coordinate[2]; + f32 y_max = ui->params.output_max_coordinate[2]; + v3 result = {.y = y_min + (y_max - y_min) / 2}; + return result; +} + +function v3 +camera_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view) +{ + v3 size = x_plane_size(ui); + v3 target = x_plane_position(ui); + f32 dist = v2_magnitude(XY(size)); + v3 result = v3_add(target, (v3){{dist, -0.5f * size.y * tan_f32(50.0f * PI / 180.0f), dist}}); + return result; +} + +function m4 +view_matrix_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view, v3 camera) +{ + assert(view->kind == BeamformerFrameViewKind_3DXPlane); + m4 result = camera_look_at(camera, x_plane_position(ui)); + return result; +} + +function m4 +projection_matrix_for_x_plane_view(BeamformerFrameView *view) +{ + assert(view->kind == BeamformerFrameViewKind_3DXPlane); + f32 aspect = (f32)view->texture_dim.w / view->texture_dim.h; + m4 result = perspective_projection(10e-3, 500e-3, 45.0f * PI / 180.0f, aspect); + return result; +} + +function void +render_3D_xplane(BeamformerUI *ui, BeamformerFrameView *view, FrameViewRenderContext *ctx) +{ + u32 program = ctx->shaders[1]; + glBindVertexArray(ui->unit_cube_model.vao); + + if (view->demo->bool32) { + view->rotation += dt_for_frame * 0.125f; + if (view->rotation > 1.0f) view->rotation -= 1.0f; + } + + v3 camera = camera_for_x_plane_view(ui, view); + m4 view_m = view_matrix_for_x_plane_view(ui, view, camera); + m4 projection = projection_matrix_for_x_plane_view(view); + + v3 scale = x_plane_size(ui); + v3 model_translate = x_plane_position(ui); + m4 model_transform = y_aligned_volume_transform(scale, model_translate, view->rotation); + + v4 colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, view->plane_offsets[0].hover_t); + + u32 texture = 0; + if (ui->latest_plane[BeamformerViewPlaneTag_XZ]) + texture = ui->latest_plane[BeamformerViewPlaneTag_XZ]->texture; + + glProgramUniformMatrix4fv(program, FRAME_VIEW_VIEW_MATRIX_LOC, 1, 0, view_m.E); + glProgramUniformMatrix4fv(program, FRAME_VIEW_PROJ_MATRIX_LOC, 1, 0, projection.E); + glProgramUniformMatrix4fv(program, FRAME_VIEW_MODEL_MATRIX_LOC, 1, 0, model_transform.E); + glProgramUniform4fv(program, FRAME_VIEW_BB_COLOUR_LOC, 1, colour.E); + glBindTextureUnit(0, texture); + glDrawElements(GL_TRIANGLES, ui->unit_cube_model.elements, GL_UNSIGNED_SHORT, + (void *)ui->unit_cube_model.elements_offset); + + model_translate.y -= 0.0001; + model_transform = y_aligned_volume_transform(scale, model_translate, view->rotation + 0.25f); + colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, view->plane_offsets[1].hover_t); + + texture = 0; + if (ui->latest_plane[BeamformerViewPlaneTag_YZ]) + texture = ui->latest_plane[BeamformerViewPlaneTag_YZ]->texture; + + glProgramUniformMatrix4fv(program, FRAME_VIEW_MODEL_MATRIX_LOC, 1, 0, model_transform.E); + glProgramUniform4fv(program, FRAME_VIEW_BB_COLOUR_LOC, 1, colour.E); + glBindTextureUnit(0, texture); + glDrawElements(GL_TRIANGLES, ui->unit_cube_model.elements, GL_UNSIGNED_SHORT, + (void *)ui->unit_cube_model.elements_offset); +} + function b32 view_update(BeamformerUI *ui, BeamformerFrameView *view) { @@ -1249,8 +1374,8 @@ view_update(BeamformerUI *ui, BeamformerFrameView *view) view->needs_update |= view->frame != ui->latest_plane[index]; view->frame = ui->latest_plane[index]; if (view->needs_update) { - view->min_coordinate = v4_from_f32_array(ui->params.output_min_coordinate); - view->max_coordinate = v4_from_f32_array(ui->params.output_max_coordinate); + view->min_coordinate = v4_from_f32_array(ui->params.output_min_coordinate).xyz; + view->max_coordinate = v4_from_f32_array(ui->params.output_max_coordinate).xyz; } } @@ -1258,39 +1383,59 @@ view_update(BeamformerUI *ui, BeamformerFrameView *view) /* TODO(rnp): add method of setting a target size in frame view */ uv2 current = view->texture_dim; uv2 target = {.w = ui->params.output_points[0], .h = ui->params.output_points[2]}; - if (view->kind != BeamformerFrameViewKind_Copy && !uv2_equal(current, target) && !uv2_equal(target, (uv2){0})) { + if (view->kind != BeamformerFrameViewKind_Copy && + view->kind != BeamformerFrameViewKind_3DXPlane && + !uv2_equal(current, target) && !uv2_equal(target, (uv2){0})) + { resize_frame_view(view, target); view->needs_update = 1; } - return (view->ctx->updated || view->needs_update) && view->frame; + b32 result = view->kind == BeamformerFrameViewKind_3DXPlane; + result |= (ui->frame_view_render_context->updated || view->needs_update) && view->frame; + return result; } function void update_frame_views(BeamformerUI *ui, Rect window) { - b32 fbo_bound = 0; + FrameViewRenderContext *ctx = ui->frame_view_render_context; + b32 fbo_bound = 0, shader_3d = 0; for (BeamformerFrameView *view = ui->views; view; view = view->next) { if (view_update(ui, view)) { if (!fbo_bound) { fbo_bound = 1; - glBindFramebuffer(GL_FRAMEBUFFER, view->ctx->framebuffer); - glUseProgram(view->ctx->shader); - glBindVertexArray(view->ctx->vao); + glBindFramebuffer(GL_FRAMEBUFFER, ctx->framebuffer); + glUseProgram(ctx->shaders[0]); + glEnable(GL_DEPTH_TEST); } + + if (view->kind == BeamformerFrameViewKind_3DXPlane) { + if (!shader_3d) { glUseProgram(ctx->shaders[1]); shader_3d = 1; } + } else { + if ( shader_3d) { glUseProgram(ctx->shaders[0]); shader_3d = 0; } + } + + glNamedFramebufferTexture(ctx->framebuffer, GL_COLOR_ATTACHMENT0, view->textures[0], 0); + glNamedFramebufferTexture(ctx->framebuffer, GL_DEPTH_ATTACHMENT, view->textures[1], 0); + glClearNamedFramebufferfv(ctx->framebuffer, GL_COLOR, 0, (f32 []){0, 0, 0, 0}); + glClearNamedFramebufferfv(ctx->framebuffer, GL_DEPTH, 0, (f32 []){1}); + + u32 program = ctx->shaders[shader_3d]; glViewport(0, 0, view->texture_dim.w, view->texture_dim.h); - glNamedFramebufferTexture(view->ctx->framebuffer, GL_COLOR_ATTACHMENT0, - view->texture, 0); - glClearNamedFramebufferfv(view->ctx->framebuffer, GL_COLOR, 0, - (f32 []){0.79, 0.46, 0.77, 1}); - glBindTextureUnit(0, view->frame->texture); - glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC, view->dynamic_range.real32); - glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_THRESHOLD_LOC, view->threshold.real32); - glProgramUniform1f(view->ctx->shader, FRAME_VIEW_RENDER_GAMMA_LOC, view->gamma.scaled_real32.val); - glProgramUniform1ui(view->ctx->shader, FRAME_VIEW_RENDER_LOG_SCALE_LOC, view->log_scale->bool32); - - glDrawArrays(GL_TRIANGLES, 0, 6); - glGenerateTextureMipmap(view->texture); + glProgramUniform1f(program, FRAME_VIEW_THRESHOLD_LOC, view->threshold.real32); + glProgramUniform1f(program, FRAME_VIEW_DYNAMIC_RANGE_LOC, view->dynamic_range.real32); + glProgramUniform1f(program, FRAME_VIEW_GAMMA_LOC, view->gamma.scaled_real32.val); + glProgramUniform1ui(program, FRAME_VIEW_LOG_SCALE_LOC, view->log_scale->bool32); + + if (view->kind == BeamformerFrameViewKind_3DXPlane) { + render_3D_xplane(ui, view, ctx); + } else { + glBindVertexArray(ctx->vao); + glBindTextureUnit(0, view->frame->texture); + glDrawArrays(GL_TRIANGLES, 0, 6); + } + glGenerateTextureMipmap(view->textures[0]); view->needs_update = 0; } } @@ -1299,6 +1444,7 @@ update_frame_views(BeamformerUI *ui, Rect window) glViewport(window.pos.x, window.pos.y, window.size.w, window.size.h); /* NOTE(rnp): I don't trust raylib to not mess with us */ glBindVertexArray(0); + glDisable(GL_DEPTH_TEST); } } @@ -1322,17 +1468,6 @@ fade(Color a, f32 visibility) return a; } -function v4 -lerp_v4(v4 a, v4 b, f32 t) -{ - return (v4){ - .x = a.x + t * (b.x - a.x), - .y = a.y + t * (b.y - a.y), - .z = a.z + t * (b.z - a.z), - .w = a.w + t * (b.w - a.w), - }; -} - function s8 push_das_shader_kind(Stream *s, DASShaderKind shader, u32 transmit_count) { @@ -1399,7 +1534,7 @@ push_custom_view_title(Stream *s, Variable *var) function v2 draw_text_base(Font font, s8 text, v2 pos, Color colour) { - v2 off = floor_v2(pos); + v2 off = v2_floor(pos); for (iz i = 0; i < text.len; i++) { /* NOTE: assumes font glyphs are ordered ASCII */ i32 idx = text.data[i] - 0x20; @@ -1432,10 +1567,10 @@ draw_outlined_text(s8 text, v2 pos, TextSpec *ts) f32 ow = ts->outline_thick; Color outline = colour_from_normalized(ts->outline_colour); Color colour = colour_from_normalized(ts->colour); - draw_text_base(*ts->font, text, sub_v2(pos, (v2){.x = ow, .y = ow}), outline); - draw_text_base(*ts->font, text, sub_v2(pos, (v2){.x = ow, .y = -ow}), outline); - draw_text_base(*ts->font, text, sub_v2(pos, (v2){.x = -ow, .y = ow}), outline); - draw_text_base(*ts->font, text, sub_v2(pos, (v2){.x = -ow, .y = -ow}), outline); + draw_text_base(*ts->font, text, v2_sub(pos, (v2){{ ow, ow}}), outline); + draw_text_base(*ts->font, text, v2_sub(pos, (v2){{ ow, -ow}}), outline); + draw_text_base(*ts->font, text, v2_sub(pos, (v2){{-ow, ow}}), outline); + draw_text_base(*ts->font, text, v2_sub(pos, (v2){{-ow, -ow}}), outline); v2 result = draw_text_base(*ts->font, text, pos, colour); @@ -1539,7 +1674,7 @@ interaction_is_hot(BeamformerUI *ui, Interaction a) function b32 point_in_rect(v2 p, Rect r) { - v2 end = add_v2(r.pos, r.size); + v2 end = v2_add(r.pos, r.size); b32 result = BETWEEN(p.x, r.pos.x, end.x) & BETWEEN(p.y, r.pos.y, end.y); return result; } @@ -1547,16 +1682,16 @@ point_in_rect(v2 p, Rect r) function v2 screen_point_to_world_2d(v2 p, v2 screen_min, v2 screen_max, v2 world_min, v2 world_max) { - v2 pixels_to_m = div_v2(sub_v2(world_max, world_min), sub_v2(screen_max, screen_min)); - v2 result = add_v2(mul_v2(sub_v2(p, screen_min), pixels_to_m), world_min); + v2 pixels_to_m = v2_div(v2_sub(world_max, world_min), v2_sub(screen_max, screen_min)); + v2 result = v2_add(v2_mul(v2_sub(p, screen_min), pixels_to_m), world_min); return result; } function v2 world_point_to_screen_2d(v2 p, v2 world_min, v2 world_max, v2 screen_min, v2 screen_max) { - v2 m_to_pixels = div_v2(sub_v2(screen_max, screen_min), sub_v2(world_max, world_min)); - v2 result = add_v2(mul_v2(sub_v2(p, world_min), m_to_pixels), screen_min); + v2 m_to_pixels = v2_div(v2_sub(screen_max, screen_min), v2_sub(world_max, world_min)); + v2 result = v2_add(v2_mul(v2_sub(p, world_min), m_to_pixels), screen_min); return result; } @@ -1578,11 +1713,11 @@ draw_close_button(BeamformerUI *ui, Variable *close, v2 mouse, Rect r, v2 x_scal assert(close->type == VT_UI_BUTTON); hover_interaction(ui, mouse, auto_interaction(r, close)); - Color colour = colour_from_normalized(lerp_v4(MENU_CLOSE_COLOUR, FG_COLOUR, close->hover_t)); + Color colour = colour_from_normalized(v4_lerp(MENU_CLOSE_COLOUR, FG_COLOUR, close->hover_t)); r = scale_rect_centered(r, x_scale); - DrawLineEx(r.pos.rl, add_v2(r.pos, r.size).rl, 4, colour); - DrawLineEx(add_v2(r.pos, (v2){.x = r.size.w}).rl, - add_v2(r.pos, (v2){.y = r.size.h}).rl, 4, colour); + DrawLineEx(r.pos.rl, v2_add(r.pos, r.size).rl, 4, colour); + DrawLineEx(v2_add(r.pos, (v2){.x = r.size.w}).rl, + v2_add(r.pos, (v2){.y = r.size.h}).rl, 4, colour); } function Rect @@ -1620,12 +1755,12 @@ draw_title_bar(BeamformerUI *ui, Arena arena, Variable *ui_view, Rect r, v2 mous Interaction interaction = {.kind = InteractionKind_Menu, .var = view->menu, .rect = menu}; hover_interaction(ui, mouse, interaction); - Color colour = colour_from_normalized(lerp_v4(MENU_PLUS_COLOUR, FG_COLOUR, view->menu->hover_t)); + Color colour = colour_from_normalized(v4_lerp(MENU_PLUS_COLOUR, FG_COLOUR, view->menu->hover_t)); menu = shrink_rect_centered(menu, (v2){.x = 14, .y = 14}); - DrawLineEx(add_v2(menu.pos, (v2){.x = menu.size.w / 2}).rl, - add_v2(menu.pos, (v2){.x = menu.size.w / 2, .y = menu.size.h}).rl, 4, colour); - DrawLineEx(add_v2(menu.pos, (v2){.y = menu.size.h / 2}).rl, - add_v2(menu.pos, (v2){.x = menu.size.w, .y = menu.size.h / 2}).rl, 4, colour); + DrawLineEx(v2_add(menu.pos, (v2){.x = menu.size.w / 2}).rl, + v2_add(menu.pos, (v2){.x = menu.size.w / 2, .y = menu.size.h}).rl, 4, colour); + DrawLineEx(v2_add(menu.pos, (v2){.y = menu.size.h / 2}).rl, + v2_add(menu.pos, (v2){.x = menu.size.w, .y = menu.size.h / 2}).rl, 4, colour); } v2 title_pos = title_rect.pos; @@ -1646,14 +1781,14 @@ draw_ruler(BeamformerUI *ui, Arena arena, v2 start_point, v2 end_point, { b32 draw_plus = SIGN(start_value) != SIGN(end_value); - end_point = sub_v2(end_point, start_point); + end_point = v2_sub(end_point, start_point); f32 rotation = atan2_f32(end_point.y, end_point.x) * 180 / PI; rlPushMatrix(); rlTranslatef(start_point.x, start_point.y, 0); rlRotatef(rotation, 0, 0, 1); - f32 inc = magnitude_v2(end_point) / segments; + f32 inc = v2_magnitude(end_point) / segments; f32 value_inc = (end_value - start_value) / segments; f32 value = start_value; @@ -1702,7 +1837,7 @@ do_scale_bar(BeamformerUI *ui, Arena arena, Variable *scale_bar, v2 mouse, Rect Rect tick_rect = draw_rect; v2 start_pos = tick_rect.pos; v2 end_pos = tick_rect.pos; - v2 relative_mouse = sub_v2(mouse, tick_rect.pos); + v2 relative_mouse = v2_sub(mouse, tick_rect.pos); f32 markers[2]; u32 marker_count = 1; @@ -1731,7 +1866,7 @@ do_scale_bar(BeamformerUI *ui, Arena arena, Variable *scale_bar, v2 mouse, Rect marker_count = 2; draw_ruler(ui, arena, start_pos, end_pos, start_value, end_value, markers, marker_count, - tick_count, suffix, RULER_COLOUR, lerp_v4(FG_COLOUR, HOVERED_COLOUR, scale_bar->hover_t)); + tick_count, suffix, RULER_COLOUR, v4_lerp(FG_COLOUR, HOVERED_COLOUR, scale_bar->hover_t)); } function v2 @@ -1753,7 +1888,7 @@ draw_radio_button(BeamformerUI *ui, Variable *var, v2 at, v2 mouse, v4 base_colo hover_rect = shrink_rect_centered(hover_rect, (v2){.x = 8, .y = 8}); Rect inner = shrink_rect_centered(hover_rect, (v2){.x = 4, .y = 4}); - v4 fill = lerp_v4(value? base_colour : (v4){0}, HOVERED_COLOUR, var->hover_t); + v4 fill = v4_lerp(value? base_colour : (v4){0}, HOVERED_COLOUR, var->hover_t); DrawRectangleRoundedLinesEx(hover_rect.rl, 0.2, 0, 2, colour_from_normalized(base_colour)); DrawRectangleRec(inner.rl, colour_from_normalized(fill)); @@ -1777,7 +1912,7 @@ draw_variable(BeamformerUI *ui, Arena arena, Variable *var, v2 at, v2 mouse, v4 text_rect = extend_rect_centered(text_rect, (v2){.x = 8}); if (hover_interaction(ui, mouse, auto_interaction(text_rect, var)) && (var->flags & V_TEXT)) ui->text_input_state.hot_font = text_spec.font; - text_spec.colour = lerp_v4(base_colour, HOVERED_COLOUR, var->hover_t); + text_spec.colour = v4_lerp(base_colour, HOVERED_COLOUR, var->hover_t); } draw_text(text, at, &text_spec); @@ -1878,11 +2013,11 @@ draw_table(BeamformerUI *ui, Arena arena, Table *table, Rect draw_rect, TextSpec function void draw_view_ruler(BeamformerFrameView *view, Arena a, Rect view_rect, TextSpec ts) { - v2 vr_max_p = add_v2(view_rect.pos, view_rect.size); + v2 vr_max_p = v2_add(view_rect.pos, view_rect.size); v2 start_p = world_point_to_screen_2d(view->ruler.start, XZ(view->min_coordinate), XZ(view->max_coordinate), view_rect.pos, vr_max_p); v2 end_p = world_point_to_screen_2d(view->ruler.end, XZ(view->min_coordinate), - XZ(view->max_coordinate), view_rect.pos, vr_max_p); + XZ(view->max_coordinate), view_rect.pos, vr_max_p); Color rl_colour = colour_from_normalized(ts.colour); DrawCircleV(start_p.rl, 3, rl_colour); @@ -1890,12 +2025,12 @@ draw_view_ruler(BeamformerFrameView *view, Arena a, Rect view_rect, TextSpec ts) DrawCircleV(end_p.rl, 3, rl_colour); Stream buf = arena_stream(a); - stream_append_f64(&buf, 1e3 * magnitude_v2(sub_v2(view->ruler.end, view->ruler.start)), 100); + stream_append_f64(&buf, 1e3 * v2_magnitude(v2_sub(view->ruler.end, view->ruler.start)), 100); stream_append_s8(&buf, s8(" mm")); v2 txt_p = start_p; v2 txt_s = measure_text(*ts.font, stream_to_s8(&buf)); - v2 pixel_delta = sub_v2(start_p, end_p); + v2 pixel_delta = v2_sub(start_p, end_p); if (pixel_delta.y < 0) txt_p.y -= txt_s.y; if (pixel_delta.x < 0) txt_p.x -= txt_s.x; if (txt_p.x < view_rect.pos.x) txt_p.x = view_rect.pos.x; @@ -1904,19 +2039,72 @@ draw_view_ruler(BeamformerFrameView *view, Arena a, Rect view_rect, TextSpec ts) draw_text(stream_to_s8(&buf), txt_p, &ts); } +function v2 +draw_frame_view_controls(BeamformerUI *ui, Arena arena, BeamformerFrameView *view, Rect vr, v2 mouse) +{ + TextSpec text_spec = {.font = &ui->small_font, .flags = TF_LIMITED|TF_OUTLINED, + .colour = RULER_COLOUR, .outline_thick = 1, .outline_colour.a = 1, + .limits.size.x = vr.size.w}; + + Table *table = table_new(&arena, 3, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); + table_push_parameter_row(table, &arena, view->gamma.name, &view->gamma, s8("")); + table_push_parameter_row(table, &arena, view->threshold.name, &view->threshold, s8("")); + if (view->log_scale->bool32) + table_push_parameter_row(table, &arena, view->dynamic_range.name, &view->dynamic_range, s8("[dB]")); + + Rect table_rect = vr; + f32 height = table_extent(table, arena, text_spec.font).y; + height = MIN(height, vr.size.h); + table_rect.pos.w += 8; + table_rect.pos.y += vr.size.h - height - 8; + table_rect.size.h = height; + table_rect.size.w = vr.size.w - 16; + + return draw_table(ui, arena, table, table_rect, text_spec, mouse, 0); +} + +function void +draw_3D_xplane_frame_view(BeamformerUI *ui, Arena arena, Variable *var, Rect display_rect, v2 mouse) +{ + assert(var->type == VT_BEAMFORMER_FRAME_VIEW); + BeamformerFrameView *view = var->generic; + + f32 aspect = (f32)view->texture_dim.w / (f32)view->texture_dim.h; + Rect vr = display_rect; + if (aspect > 1.0f) vr.size.w = vr.size.h; + else vr.size.h = vr.size.w; + + if (vr.size.w > display_rect.size.w) { + vr.size.w -= (vr.size.w - display_rect.size.w); + vr.size.h = vr.size.w / aspect; + } else if (vr.size.h > display_rect.size.h) { + vr.size.h -= (vr.size.h - display_rect.size.h); + vr.size.w = vr.size.h * aspect; + } + vr.pos = v2_add(vr.pos, v2_scale(v2_sub(display_rect.size, vr.size), 0.5)); + + hover_interaction(ui, mouse, auto_interaction(vr, var)); + + Rectangle tex_r = {0, 0, view->texture_dim.w, view->texture_dim.h}; + NPatchInfo tex_np = {tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH}; + DrawTextureNPatch(make_raylib_texture(view), tex_np, vr.rl, (Vector2){0}, 0, WHITE); + + draw_frame_view_controls(ui, arena, view, vr, mouse); +} + function void draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect display_rect, v2 mouse) { assert(var->type == VT_BEAMFORMER_FRAME_VIEW); - BeamformerFrameView *view = var->generic; - BeamformFrame *frame = view->frame; + BeamformerFrameView *view = var->generic; + BeamformerFrame *frame = view->frame; v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); f32 scale_bar_size = 1.2 * txt_s.x + RULER_TICK_LENGTH; - v4 min = view->min_coordinate; - v4 max = view->max_coordinate; - v2 requested_dim = sub_v2(XZ(max), XZ(min)); + v3 min = view->min_coordinate; + v3 max = view->max_coordinate; + v2 requested_dim = v2_sub(XZ(max), XZ(min)); f32 aspect = requested_dim.w / requested_dim.h; Rect vr = display_rect; @@ -1933,11 +2121,11 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa scale_bar_area.y += scale_bar_size; } - vr.size = sub_v2(vr.size, scale_bar_area); + vr.size = v2_sub(vr.size, scale_bar_area); if (aspect > 1) vr.size.h = vr.size.w / aspect; else vr.size.w = vr.size.h * aspect; - v2 occupied = add_v2(vr.size, scale_bar_area); + v2 occupied = v2_add(vr.size, scale_bar_area); if (occupied.w > display_rect.size.w) { vr.size.w -= (occupied.w - display_rect.size.w); vr.size.h = vr.size.w / aspect; @@ -1945,21 +2133,19 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa vr.size.h -= (occupied.h - display_rect.size.h); vr.size.w = vr.size.h * aspect; } - occupied = add_v2(vr.size, scale_bar_area); - vr.pos = add_v2(vr.pos, scale_v2(sub_v2(display_rect.size, occupied), 0.5)); + occupied = v2_add(vr.size, scale_bar_area); + vr.pos = v2_add(vr.pos, v2_scale(v2_sub(display_rect.size, occupied), 0.5)); /* TODO(rnp): make this depend on the requested draw orientation (x-z or y-z or x-y) */ - v2 output_dim = { - .x = frame->max_coordinate.x - frame->min_coordinate.x, - .y = frame->max_coordinate.z - frame->min_coordinate.z, - }; - + v2 output_dim = v2_sub(XZ(frame->max_coordinate), XZ(frame->min_coordinate)); v2 pixels_per_meter = { .w = (f32)view->texture_dim.w / output_dim.w, .h = (f32)view->texture_dim.h / output_dim.h, }; - v2 texture_points = mul_v2(pixels_per_meter, requested_dim); + /* NOTE(rnp): math to resize the texture without stretching when the view changes + * but the texture hasn't been (or cannot be) rebeamformed */ + v2 texture_points = v2_mul(pixels_per_meter, requested_dim); /* TODO(rnp): this also depends on x-y, y-z, x-z */ v2 texture_start = { .x = pixels_per_meter.x * 0.5 * (output_dim.x - requested_dim.x), @@ -2000,11 +2186,11 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa if (point_in_rect(mouse, viewer.rect)) { ui->next_interaction = viewer; - v2 world = screen_point_to_world_2d(mouse, vr.pos, add_v2(vr.pos, vr.size), + v2 world = screen_point_to_world_2d(mouse, vr.pos, v2_add(vr.pos, vr.size), XZ(view->min_coordinate), XZ(view->max_coordinate)); Stream buf = arena_stream(a); - stream_append_v2(&buf, scale_v2(world, 1e3)); + stream_append_v2(&buf, v2_scale(world, 1e3)); text_spec.limits.size.w -= 4; v2 txt_s = measure_text(*text_spec.font, stream_to_s8(&buf)); @@ -2035,21 +2221,8 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa if (view->ruler.state != RulerState_None) draw_view_ruler(view, a, vr, text_spec); - Table *table = table_new(&a, 3, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); - table_push_parameter_row(table, &a, view->gamma.name, &view->gamma, s8("")); - table_push_parameter_row(table, &a, view->threshold.name, &view->threshold, s8("")); - if (view->log_scale->bool32) - table_push_parameter_row(table, &a, view->dynamic_range.name, &view->dynamic_range, s8("[dB]")); - - Rect table_rect = vr; - f32 height = table_extent(table, a, text_spec.font).y; - height = MIN(height, vr.size.h); - table_rect.pos.w += 8; - table_rect.pos.y += vr.size.h - height - 8; - table_rect.size.h = height; - table_rect.size.w = draw_table_width - 16; - - draw_table(ui, a, table, table_rect, text_spec, mouse, 0); + vr.size.w = draw_table_width; + draw_frame_view_controls(ui, a, view, vr, mouse); } function v2 @@ -2130,14 +2303,14 @@ draw_compute_stats_bar_view(BeamformerUI *ui, Arena arena, ComputeShaderStats *s u32 frame_index = (stats->latest_frame_index - row_index) % countof(stats->table.times); f32 total_width = average_width * total_times[row_index] / compute_time_sum; Rect rect; - rect.pos = add_v2(cr.pos, (v2){.x = cr.size.w + table->cell_pad.w , .y = cr.size.h * 0.15}); + rect.pos = v2_add(cr.pos, (v2){.x = cr.size.w + table->cell_pad.w , .y = cr.size.h * 0.15}); rect.size = (v2){.y = 0.7 * cr.size.h}; for (u32 i = 0; i < stages_count; i++) { rect.size.w = total_width * stats->table.times[frame_index][stages[i]] / total_times[row_index]; Color color = colour_from_normalized(g_colour_palette[stages[i] % countof(g_colour_palette)]); DrawRectangleRec(rect.rl, color); if (point_in_rect(mouse, rect)) { - text_pos = add_v2(rect.pos, (v2){.x = table->cell_pad.w}); + text_pos = v2_add(rect.pos, (v2){.x = table->cell_pad.w}); mouse_text = push_compute_time(&arena, labels[stages[i]], stats->table.times[frame_index][stages[i]]); } @@ -2146,8 +2319,8 @@ draw_compute_stats_bar_view(BeamformerUI *ui, Arena arena, ComputeShaderStats *s row_index++; } - v2 start = add_v2(r.pos, (v2){.x = table->widths[0] + average_width + table->cell_pad.w}); - v2 end = add_v2(start, (v2){.y = result.y}); + v2 start = v2_add(r.pos, (v2){.x = table->widths[0] + average_width + table->cell_pad.w}); + v2 end = v2_add(start, (v2){.y = result.y}); DrawLineEx(start.rl, end.rl, 4, colour_from_normalized(FG_COLOUR)); if (mouse_text.len) { @@ -2213,7 +2386,7 @@ draw_compute_stats_view(BeamformerUI *ui, Arena arena, Variable *view, Rect r, v case ComputeStatsViewKind_Bar:{ result = draw_compute_stats_bar_view(ui, arena, stats, compute_stages, stages, compute_time_sum, text_spec, r, mouse); - r.pos = add_v2(r.pos, (v2){.y = result.y}); + r.pos = v2_add(r.pos, (v2){.y = result.y}); }break; InvalidDefaultCase; } @@ -2221,7 +2394,7 @@ draw_compute_stats_view(BeamformerUI *ui, Arena arena, Variable *view, Rect r, v push_table_time_row(table, &arena, s8("Compute Total:"), compute_time_sum); push_table_time_row(table, &arena, s8("RF Upload Delta:"), stats->rf_time_delta_average); - result = add_v2(result, table_extent(table, arena, text_spec.font)); + result = v2_add(result, table_extent(table, arena, text_spec.font)); draw_table(ui, arena, table, r, text_spec, (v2){0}, 0); return result; } @@ -2352,7 +2525,7 @@ draw_ui_view_container(BeamformerUI *ui, Variable *var, v2 mouse, Rect bounds) f32 line_height = ui->small_font.baseSize; if (fw->rect.pos.y - line_height < 0) fw->rect.pos.y += line_height - fw->rect.pos.y; - f32 delta_x = add_v2(fw->rect.pos, fw->rect.size).x - add_v2(bounds.size, bounds.pos).x; + f32 delta_x = (fw->rect.pos.x + fw->rect.size.x) - (bounds.size.x + bounds.pos.x); if (delta_x > 0) { fw->rect.pos.x -= delta_x; fw->rect.pos.x = MAX(0, fw->rect.pos.x); @@ -2413,8 +2586,12 @@ draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec tex }break; case VT_BEAMFORMER_FRAME_VIEW: { BeamformerFrameView *bv = var->generic; - if (frame_view_ready_to_present(bv)) - draw_beamformer_frame_view(ui, ui->arena, var, r, mouse); + if (bv->kind == BeamformerFrameViewKind_3DXPlane) { + draw_3D_xplane_frame_view(ui, ui->arena, var, r, mouse); + } else { + if (frame_view_ready_to_present(bv)) + draw_beamformer_frame_view(ui, ui->arena, var, r, mouse); + } } break; case VT_COMPUTE_PROGRESS_BAR: { size = draw_compute_progress_bar(ui, ui->arena, &var->compute_progress_bar, r); @@ -2432,7 +2609,7 @@ draw_layout_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse) if (var->type != VT_UI_REGION_SPLIT) { v2 shrink = {.x = UI_REGION_PAD, .y = UI_REGION_PAD}; draw_rect = shrink_rect_centered(draw_rect, shrink); - draw_rect.size = floor_v2(draw_rect.size); + draw_rect.size = v2_floor(draw_rect.size); BeginScissorMode(draw_rect.pos.x, draw_rect.pos.y, draw_rect.size.w, draw_rect.size.h); draw_rect = draw_title_bar(ui, ui->arena, var, draw_rect, mouse); EndScissorMode(); @@ -2442,7 +2619,7 @@ draw_layout_variable(BeamformerUI *ui, Variable *var, Rect draw_rect, v2 mouse) if (!CheckCollisionPointRec(mouse.rl, draw_rect.rl)) mouse = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; - draw_rect.size = floor_v2(draw_rect.size); + draw_rect.size = v2_floor(draw_rect.size); BeginScissorMode(draw_rect.pos.x, draw_rect.pos.y, draw_rect.size.w, draw_rect.size.h); switch (var->type) { case VT_UI_VIEW: { @@ -2559,7 +2736,7 @@ draw_floating_widgets(BeamformerUI *ui, Rect window_rect, v2 mouse) v4 cursor_colour = FOCUSED_COLOUR; cursor_colour.a = CLAMP01(is->cursor_blink_t); - v4 text_colour = lerp_v4(FG_COLOUR, HOVERED_COLOUR, fw->child->hover_t); + v4 text_colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, fw->child->hover_t); TextSpec text_spec = {.font = is->font, .colour = text_colour}; draw_text(text, text_position, &text_spec); @@ -2716,7 +2893,7 @@ scale_bar_interaction(BeamformerUI *ui, ScaleBar *sb, v2 mouse) if (mouse_left_pressed) { v2 world_mouse = screen_point_to_world_2d(mouse, it->rect.pos, - add_v2(it->rect.pos, it->rect.size), + v2_add(it->rect.pos, it->rect.size), (v2){{*sb->min_value, *sb->min_value}}, (v2){{*sb->max_value, *sb->max_value}}); f32 new_coord = F32_INFINITY; @@ -2832,10 +3009,16 @@ ui_button_interaction(BeamformerUI *ui, Variable *button) ui_copy_frame(ui, button->parent->parent, RSD_VERTICAL); }break; case UI_BID_GM_OPEN_LIVE_VIEW_RIGHT:{ - ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL); + ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL, BeamformerFrameViewKind_Latest); }break; case UI_BID_GM_OPEN_LIVE_VIEW_BELOW:{ - ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL); + ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL, BeamformerFrameViewKind_Latest); + }break; + case UI_BID_GM_OPEN_XPLANE_VIEW_RIGHT:{ + ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL, BeamformerFrameViewKind_3DXPlane); + }break; + case UI_BID_GM_OPEN_XPLANE_VIEW_BELOW:{ + ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL, BeamformerFrameViewKind_3DXPlane); }break; } } @@ -2871,19 +3054,27 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll) if (scroll) { hot->kind = InteractionKind_Scroll; } else { - hot->kind = InteractionKind_Nop; BeamformerFrameView *bv = hot->var->generic; - switch (++bv->ruler.state) { - case RulerState_Start:{ - hot->kind = InteractionKind_Ruler; - v2 r_max = add_v2(hot->rect.pos, hot->rect.size); - v2 p = screen_point_to_world_2d(input->mouse, hot->rect.pos, r_max, - XZ(bv->min_coordinate), - XZ(bv->max_coordinate)); - bv->ruler.start = p; + switch (bv->kind) { + case BeamformerFrameViewKind_3DXPlane:{ + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) hot->kind = InteractionKind_Drag; + else hot->kind = InteractionKind_Nop; + }break; + default:{ + hot->kind = InteractionKind_Nop; + switch (++bv->ruler.state) { + case RulerState_Start:{ + hot->kind = InteractionKind_Ruler; + v2 r_max = v2_add(hot->rect.pos, hot->rect.size); + v2 p = screen_point_to_world_2d(input->mouse, hot->rect.pos, r_max, + XZ(bv->min_coordinate), + XZ(bv->max_coordinate)); + bv->ruler.start = p; + }break; + case RulerState_Hold:{}break; + default:{ bv->ruler.state = RulerState_None; }break; + } }break; - case RulerState_Hold:{}break; - default:{ bv->ruler.state = RulerState_None; }break; } } }break; @@ -2918,6 +3109,13 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll) } else { ui->interaction.kind = InteractionKind_Nop; } + + if (ui->interaction.kind == InteractionKind_Drag) { + HideCursor(); + DisableCursor(); + /* wtf raylib */ + SetMousePosition(input->mouse.x, input->mouse.y); + } } function void @@ -2927,7 +3125,7 @@ ui_end_interact(BeamformerUI *ui, v2 mouse) b32 start_compute = (it->var->flags & V_CAUSES_COMPUTE) != 0; switch (it->kind) { case InteractionKind_Nop:{}break; - case InteractionKind_Drag:{}break; + case InteractionKind_Drag:{ EnableCursor(); }break; case InteractionKind_Set:{ switch (it->var->type) { case VT_B32:{ it->var->bool32 = !it->var->bool32; }break; @@ -3021,7 +3219,7 @@ ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect) case InteractionKind_Ruler:{ assert(it->var->type == VT_BEAMFORMER_FRAME_VIEW); BeamformerFrameView *bv = it->var->generic; - v2 r_max = add_v2(it->rect.pos, it->rect.size); + v2 r_max = v2_add(it->rect.pos, it->rect.size); v2 mouse = clamp_v2_rect(input->mouse, it->rect); bv->ruler.end = screen_point_to_world_2d(mouse, it->rect.pos, r_max, XZ(bv->min_coordinate), @@ -3032,16 +3230,27 @@ ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect) ui_end_interact(ui, input->mouse); } else { v2 ws = window_rect.size; - v2 dMouse = sub_v2(input->mouse, input->last_mouse); + v2 dMouse = v2_sub(input->mouse, input->last_mouse); switch (ui->interaction.var->type) { + case VT_BEAMFORMER_FRAME_VIEW:{ + BeamformerFrameView *bv = it->var->generic; + switch (bv->kind) { + case BeamformerFrameViewKind_3DXPlane:{ + bv->rotation += dMouse.x / ws.w; + if (bv->rotation > 1.0f) bv->rotation -= 1.0f; + if (bv->rotation < 0.0f) bv->rotation += 1.0f; + }break; + InvalidDefaultCase; + } + }break; case VT_UI_MENU:{ v2 *pos = &ui->interaction.var->view.rect.pos; - *pos = clamp_v2_rect(add_v2(*pos, dMouse), window_rect); + *pos = clamp_v2_rect(v2_add(*pos, dMouse), window_rect); }break; case VT_UI_REGION_SPLIT:{ f32 min_fraction = 0; - dMouse = mul_v2(dMouse, (v2){.x = 1.0f / ws.w, .y = 1.0f / ws.h}); + dMouse = v2_mul(dMouse, (v2){{1.0f / ws.w, 1.0f / ws.h}}); RegionSplit *rs = &ui->interaction.var->region_split; switch (rs->direction) { case RSD_VERTICAL: { @@ -3084,14 +3293,15 @@ ui_init(BeamformerCtx *ctx, Arena store) UnloadFont(ui->small_font); for (BeamformerFrameView *view = ui->views; view; view = view->next) - if (view->texture) - glDeleteTextures(1, &view->texture); + if (view->textures[0]) + glDeleteTextures(countof(view->textures), view->textures); } ui = ctx->ui = push_struct(&store, typeof(*ui)); ui->os = &ctx->os; ui->arena = store; ui->frame_view_render_context = &ctx->frame_view_render_context; + ui->unit_cube_model = ctx->csctx.unit_cube_model; /* TODO: build these into the binary */ /* TODO(rnp): better font, this one is jank at small sizes */ @@ -3107,7 +3317,6 @@ ui_init(BeamformerCtx *ctx, Arena store) RSD_VERTICAL, ui->font); split->region_split.right = add_beamformer_frame_view(ui, split, &ui->arena, BeamformerFrameViewKind_Latest, 0); - ui_fill_live_frame_view(ui, split->region_split.right->view.child->generic); split = split->region_split.left; @@ -3135,7 +3344,7 @@ validate_ui_parameters(BeamformerUIParameters *p) } function void -draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw, BeamformerViewPlaneTag frame_plane) +draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformerFrame *frame_to_draw, BeamformerViewPlaneTag frame_plane) { BeamformerUI *ui = ctx->ui; BeamformerSharedMemory *sm = ctx->shared_memory.region; diff --git a/util.c b/util.c @@ -22,7 +22,6 @@ mem_move(u8 *dest, u8 *src, iz n) else while (n) { n--; dest[n] = src[n]; } } - function u8 * arena_commit(Arena *a, iz size) { @@ -524,169 +523,6 @@ round_up_to(iz value, iz multiple) return result; } -function b32 -uv2_equal(uv2 a, uv2 b) -{ - return a.x == b.x && a.y == b.y; -} - -function b32 -uv3_equal(uv3 a, uv3 b) -{ - return a.x == b.x && a.y == b.y && a.z == b.z; -} - -function v3 -cross(v3 a, v3 b) -{ - v3 result = { - .x = a.y * b.z - a.z * b.y, - .y = a.z * b.x - a.x * b.z, - .z = a.x * b.y - a.y * b.x, - }; - return result; -} - -function v3 -sub_v3(v3 a, v3 b) -{ - v3 result = { - .x = a.x - b.x, - .y = a.y - b.y, - .z = a.z - b.z, - }; - return result; -} - -function f32 -length_v3(v3 a) -{ - f32 result = a.x * a.x + a.y * a.y + a.z * a.z; - return result; -} - -function v3 -normalize_v3(v3 a) -{ - f32 length = length_v3(a); - v3 result = {.x = a.x / length, .y = a.y / length, .z = a.z / length}; - return result; -} - -function v2 -clamp_v2_rect(v2 v, Rect r) -{ - v2 result = v; - result.x = CLAMP(v.x, r.pos.x, r.pos.x + r.size.x); - result.y = CLAMP(v.y, r.pos.y, r.pos.y + r.size.y); - return result; -} - -function v2 -add_v2(v2 a, v2 b) -{ - v2 result = { - .x = a.x + b.x, - .y = a.y + b.y, - }; - return result; -} - -function v2 -sub_v2(v2 a, v2 b) -{ - v2 result = { - .x = a.x - b.x, - .y = a.y - b.y, - }; - return result; -} - -function v2 -scale_v2(v2 a, f32 scale) -{ - v2 result = { - .x = a.x * scale, - .y = a.y * scale, - }; - return result; -} - -function v2 -mul_v2(v2 a, v2 b) -{ - v2 result = { - .x = a.x * b.x, - .y = a.y * b.y, - }; - return result; -} - -function v2 -div_v2(v2 a, v2 b) -{ - v2 result; - result.x = a.x / b.x; - result.y = a.y / b.y; - return result; -} - - -function v2 -floor_v2(v2 a) -{ - v2 result; - result.x = (i32)a.x; - result.y = (i32)a.y; - return result; -} - -function f32 -magnitude_v2(v2 a) -{ - f32 result = sqrt_f32(a.x * a.x + a.y * a.y); - return result; -} - -function uv4 -uv4_from_u32_array(u32 v[4]) -{ - uv4 result; - result.E[0] = v[0]; - result.E[1] = v[1]; - result.E[2] = v[2]; - result.E[3] = v[3]; - return result; -} - -function b32 -uv4_equal(uv4 a, uv4 b) -{ - return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; -} - -function v4 -v4_from_f32_array(f32 v[4]) -{ - v4 result; - result.E[0] = v[0]; - result.E[1] = v[1]; - result.E[2] = v[2]; - result.E[3] = v[3]; - return result; -} - -function v4 -sub_v4(v4 a, v4 b) -{ - v4 result; - result.x = a.x - b.x; - result.y = a.y - b.y; - result.z = a.z - b.z; - result.w = a.w - b.w; - return result; -} - function void split_rect_horizontal(Rect rect, f32 fraction, Rect *left, Rect *right) { @@ -792,123 +628,4 @@ lookup_file_watch_directory(FileWatchContext *ctx, u64 hash) return result; } -function void -fill_kronecker_sub_matrix(i32 *out, i32 out_stride, i32 scale, i32 *b, uv2 b_dim) -{ - f32x4 vscale = dup_f32x4(scale); - for (u32 i = 0; i < b_dim.y; i++) { - for (u32 j = 0; j < b_dim.x; j += 4, b += 4) { - f32x4 vb = cvt_i32x4_f32x4(load_i32x4(b)); - store_i32x4(cvt_f32x4_i32x4(mul_f32x4(vscale, vb)), out + j); - } - out += out_stride; - } -} - -/* NOTE: this won't check for valid space/etc and assumes row major order */ -function void -kronecker_product(i32 *out, i32 *a, uv2 a_dim, i32 *b, uv2 b_dim) -{ - uv2 out_dim = {.x = a_dim.x * b_dim.x, .y = a_dim.y * b_dim.y}; - ASSERT(out_dim.y % 4 == 0); - for (u32 i = 0; i < a_dim.y; i++) { - i32 *vout = out; - for (u32 j = 0; j < a_dim.x; j++, a++) { - fill_kronecker_sub_matrix(vout, out_dim.y, *a, b, b_dim); - vout += b_dim.y; - } - out += out_dim.y * b_dim.x; - } -} - -/* NOTE/TODO: to support even more hadamard sizes use the Paley construction */ -function i32 * -make_hadamard_transpose(Arena *a, u32 dim) -{ - read_only local_persist i32 hadamard_12_12_transpose[] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, - 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, - 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, - 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, - 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, - 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, - 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, - 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, - 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, - 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, - 1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, - }; - - read_only local_persist i32 hadamard_20_20_transpose[] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, - 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, - 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, - 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, - 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, - 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, - 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, - 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, - 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, - 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, - 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, - 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, - 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, - 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, - 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, - 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, - 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, - 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, - 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, - }; - - i32 *result = 0; - - b32 power_of_2 = ISPOWEROF2(dim); - b32 multiple_of_12 = dim % 12 == 0; - b32 multiple_of_20 = dim % 20 == 0; - iz elements = dim * dim; - - u32 base_dim = 0; - if (power_of_2) { - base_dim = dim; - } else if (multiple_of_20 && ISPOWEROF2(dim / 20)) { - base_dim = 20; - dim /= 20; - } else if (multiple_of_12 && ISPOWEROF2(dim / 12)) { - base_dim = 12; - dim /= 12; - } - - if (ISPOWEROF2(dim) && base_dim && arena_capacity(a, i32) >= elements * (1 + (dim != base_dim))) { - result = push_array(a, i32, elements); - - Arena tmp = *a; - i32 *m = dim == base_dim ? result : push_array(&tmp, i32, elements); - - #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 - - i32 *m2 = 0; - uv2 m2_dim; - switch (base_dim) { - case 12:{ m2 = hadamard_12_12_transpose; m2_dim = (uv2){{12, 12}}; }break; - case 20:{ m2 = hadamard_20_20_transpose; m2_dim = (uv2){{20, 20}}; }break; - } - if (m2) kronecker_product(result, m, (uv2){{dim, dim}}, m2, m2_dim); - } - - return result; -} +#include "math.c" diff --git a/util.h b/util.h @@ -55,8 +55,8 @@ #define static_assert _Static_assert /* NOTE: garbage to get the prepocessor to properly stringize the value of a macro */ -#define str_(x) #x -#define str(x) str_(x) +#define str_(...) #__VA_ARGS__ +#define str(...) str_(__VA_ARGS__) #define countof(a) (sizeof(a) / sizeof(*a)) #define ARRAY_COUNT(a) (sizeof(a) / sizeof(*a)) @@ -71,6 +71,8 @@ #define SIGN(x) ((x) < 0? -1 : 1) #define swap(a, b) do {typeof(a) __tmp = (a); (a) = (b); (b) = __tmp;} while(0) +#define f32_cmp(x, y) (ABS((x) - (y)) <= F32_EPSILON * MAX(1.0f, MAX(ABS(x), ABS(y)))) + #define EachElement(array, it) (u64 it = 0; it < countof(array); it += 1) #define EachEnumValue(type, it) (type it = (type)0; it < type##_Count; it = (type)(it + 1)) #define EachNonZeroEnumValue(type, it) (type it = (type)1; it < type##_Count; it = (type)(it + 1)) @@ -100,6 +102,10 @@ #define I32_MAX (0x7FFFFFFFL) #define U32_MAX (0xFFFFFFFFUL) #define F32_INFINITY (1e+300*1e+300) +#define F32_EPSILON (1e-6) +#ifndef PI + #define PI (3.14159265358979323846f) +#endif typedef char c8; typedef uint8_t u8;