ogl_beamforming

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

Commit: d869fa9a2ae8f730205133bda65ae2ce8261e21f
Parent: 6ec7908d14402bbfa240372c5ee79f8eb2be67f2
Author: Randy Palamar
Date:   Sat, 21 Jun 2025 16:21:58 -0600

core: store large backlog of compute timings

Diffstat:
Mbeamformer.c | 129++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mbeamformer.h | 52++++++++++++++++++++++++++++++++++++++++++----------
Mopengl.h | 3++-
Mstatic.c | 4+++-
Mui.c | 67++++++++++++++++++++++++++++++-------------------------------------
Mutil.h | 4++++
6 files changed, 182 insertions(+), 77 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -79,8 +79,7 @@ frame_next(ComputeFrameIterator *bfi) } function void -alloc_beamform_frame(GLParams *gp, BeamformFrame *out, ComputeShaderStats *out_stats, - uv3 out_dim, s8 name, Arena arena) +alloc_beamform_frame(GLParams *gp, BeamformFrame *out, uv3 out_dim, s8 name, Arena arena) { out->dim.x = MAX(1, out_dim.x); out->dim.y = MAX(1, out_dim.y); @@ -107,11 +106,6 @@ alloc_beamform_frame(GLParams *gp, BeamformFrame *out, ComputeShaderStats *out_s glCreateTextures(GL_TEXTURE_3D, 1, &out->texture); glTextureStorage3D(out->texture, out->mips, GL_RG32F, out->dim.x, out->dim.y, out->dim.z); LABEL_GL_OBJECT(GL_TEXTURE, out->texture, stream_to_s8(&label)); - - if (out_stats) { - glDeleteQueries(ARRAY_COUNT(out_stats->timer_ids), out_stats->timer_ids); - glCreateQueries(GL_TIME_ELAPSED, ARRAY_COUNT(out_stats->timer_ids), out_stats->timer_ids); - } } function void @@ -159,6 +153,13 @@ alloc_shader_storage(BeamformerCtx *ctx, u32 rf_raw_size, Arena a) } } +function void +push_compute_timing_info(ComputeTimingTable *t, ComputeTimingInfo info) +{ + u32 index = atomic_add_u32(&t->write_index, 1) % countof(t->buffer); + t->buffer[index] = info; +} + function b32 fill_frame_compute_work(BeamformerCtx *ctx, BeamformWork *work, ImagePlaneTag plane) { @@ -565,7 +566,14 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena arena, iptr gl_co alloc_shader_storage(ctx, uc->size, arena); } buffer = cs->raw_data_ssbo; - } break; + + ComputeTimingInfo info = {0}; + info.kind = ComputeTimingInfoKind_RF_Data; + /* TODO(rnp): this could stall. what should we do about it? */ + glGetQueryObjectui64v(cs->rf_data_timestamp_query, GL_QUERY_RESULT, &info.timer_count); + glQueryCounter(cs->rf_data_timestamp_query, GL_TIMESTAMP); + push_compute_timing_info(ctx->compute_timing_table, info); + }break; InvalidDefaultCase; } @@ -594,6 +602,9 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena arena, iptr gl_co sm->locks, work->lock); } + push_compute_timing_info(ctx->compute_timing_table, + (ComputeTimingInfo){.kind = ComputeTimingInfoKind_ComputeFrameBegin}); + i32 mask = 1 << (BeamformerSharedMemoryLockKind_Parameters - 1); if (sm->dirty_regions & mask) { glNamedBufferSubData(cs->shared_ubo, 0, sizeof(sm->parameters), &sm->parameters); @@ -606,16 +617,13 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena arena, iptr gl_co BeamformComputeFrame *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, &frame->stats, try_dim, - s8("Beamformed_Data"), arena); + alloc_beamform_frame(&ctx->gl, &frame->frame, try_dim, s8("Beamformed_Data"), arena); if (bp->output_points[3] > 1) { if (!uv3_equal(try_dim, ctx->averaged_frames[0].frame.dim)) { alloc_beamform_frame(&ctx->gl, &ctx->averaged_frames[0].frame, - &ctx->averaged_frames[0].stats, try_dim, s8("Averaged Frame"), arena); alloc_beamform_frame(&ctx->gl, &ctx->averaged_frames[1].frame, - &ctx->averaged_frames[1].stats, try_dim, s8("Averaged Frame"), arena); } } @@ -631,8 +639,7 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena arena, iptr gl_co ComputeShaderKind *stages = sm->compute_stages; for (u32 i = 0; i < stage_count; i++) { did_sum_shader |= stages[i] == ComputeShaderKind_Sum; - frame->stats.timer_active[stages[i]] = 1; - glBeginQuery(GL_TIME_ELAPSED, frame->stats.timer_ids[stages[i]]); + glBeginQuery(GL_TIME_ELAPSED, cs->shader_timer_ids[i]); do_compute_shader(ctx, arena, frame, (ShaderKind)stages[i]); glEndQuery(GL_TIME_ELAPSED); } @@ -640,29 +647,26 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena arena, iptr gl_co glFinish(); cs->processing_progress = 1; - for (u32 i = 0; i < ARRAY_COUNT(frame->stats.timer_ids); i++) { - u64 ns = 0; - if (frame->stats.timer_active[i]) { - glGetQueryObjectui64v(frame->stats.timer_ids[i], - GL_QUERY_RESULT, &ns); - frame->stats.timer_active[i] = 0; - } - frame->stats.times[i] = (f32)ns / 1e9; + for (u32 i = 0; i < stage_count; i++) { + ComputeTimingInfo info = {0}; + info.kind = ComputeTimingInfoKind_Shader; + info.shader = (ShaderKind)stages[i]; + glGetQueryObjectui64v(cs->shader_timer_ids[i], GL_QUERY_RESULT, &info.timer_count); + push_compute_timing_info(ctx->compute_timing_table, info); } if (did_sum_shader) { - u32 aframe_index = (ctx->averaged_frame_index % - ARRAY_COUNT(ctx->averaged_frames)); + u32 aframe_index = (ctx->averaged_frame_index % countof(ctx->averaged_frames)); ctx->averaged_frames[aframe_index].image_plane_tag = frame->image_plane_tag; ctx->averaged_frames[aframe_index].ready_to_present = 1; - /* TODO(rnp): not really sure what to do here */ - mem_copy(&ctx->averaged_frames[aframe_index].stats.times, - &frame->stats.times, sizeof(frame->stats.times)); atomic_add_u32(&ctx->averaged_frame_index, 1); } frame->ready_to_present = 1; cs->processing_compute = 0; + push_compute_timing_info(ctx->compute_timing_table, + (ComputeTimingInfo){.kind = ComputeTimingInfoKind_ComputeFrameEnd}); + end_renderdoc_capture(gl_context); } break; case BW_SAVE_FRAME: { @@ -684,6 +688,67 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena arena, iptr gl_co } } +function void +coalesce_timing_table(ComputeTimingTable *t, ComputeShaderStats *stats) +{ + /* TODO(rnp): we do not currently do anything to handle the potential for a half written + * info item. this could result in garbage entries but they shouldn't really matter */ + + u32 target = atomic_load_u32(&t->write_index); + u32 stats_index = (stats->latest_frame_index + 1) % countof(stats->times[0]); + + static_assert(ShaderKind_Count + 1 <= 32, "timing coalescence bitfield test"); + u32 seen_info_test = 0; + + while (t->read_index != target) { + ComputeTimingInfo info = t->buffer[t->read_index++]; + switch (info.kind) { + case ComputeTimingInfoKind_ComputeFrameBegin:{ + assert(t->compute_frame_active == 0); + t->compute_frame_active = 1; + /* NOTE(rnp): allow multiple instances of same shader to accumulate */ + for EachEnumValue(ShaderKind, shader) + stats->times[shader][stats_index] = 0; + }break; + case ComputeTimingInfoKind_ComputeFrameEnd:{ + assert(t->compute_frame_active == 1); + t->compute_frame_active = 0; + stats->latest_frame_index = stats_index; + stats_index = (stats_index + 1) % countof(stats->times[0]); + }break; + case ComputeTimingInfoKind_Shader:{ + stats->times[info.shader][stats_index] += (f32)info.timer_count / 1.0e9; + seen_info_test |= (1 << info.shader); + }break; + case ComputeTimingInfoKind_RF_Data:{ + stats->latest_rf_index = (stats->latest_rf_index + 1) % countof(stats->rf_time_deltas); + f32 delta = (f32)(info.timer_count - stats->last_rf_timer_count) / 1.0e9; + stats->rf_time_deltas[stats->latest_rf_index] = delta; + stats->last_rf_timer_count = info.timer_count; + seen_info_test |= (1 << ShaderKind_Count); + }break; + } + } + + if (seen_info_test) { + for EachEnumValue(ShaderKind, shader) { + if (seen_info_test & (1 << shader)) { + f32 sum = 0; + for EachElement(stats->times[shader], i) + sum += stats->times[shader][i]; + stats->average_times[shader] = sum / countof(stats->times[shader]); + } + } + + if (seen_info_test & (1 << ShaderKind_Count)) { + f32 sum = 0; + for EachElement(stats->rf_time_deltas, i) + sum += stats->rf_time_deltas[i]; + stats->rf_time_delta_average = sum / countof(stats->rf_time_deltas); + } + } +} + DEBUG_EXPORT BEAMFORMER_COMPUTE_SETUP_FN(beamformer_compute_setup) { BeamformerCtx *ctx = (BeamformerCtx *)user_context; @@ -704,6 +769,12 @@ DEBUG_EXPORT BEAMFORMER_COMPUTE_SETUP_FN(beamformer_compute_setup) LABEL_GL_OBJECT(GL_TEXTURE, cs->focal_vectors_texture, s8("Focal_Vectors")); LABEL_GL_OBJECT(GL_TEXTURE, cs->sparse_elements_texture, s8("Sparse_Elements")); LABEL_GL_OBJECT(GL_BUFFER, cs->shared_ubo, s8("Beamformer_Parameters")); + + glCreateQueries(GL_TIME_ELAPSED, countof(cs->shader_timer_ids), cs->shader_timer_ids); + glCreateQueries(GL_TIMESTAMP, 1, &cs->rf_data_timestamp_query); + + /* NOTE(rnp): start this here so we don't have to worry about it being started or not */ + glQueryCounter(cs->rf_data_timestamp_query, GL_TIMESTAMP); } DEBUG_EXPORT BEAMFORMER_COMPLETE_COMPUTE_FN(beamformer_complete_compute) @@ -725,6 +796,8 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) ctx->window_size.w = GetScreenWidth(); } + coalesce_timing_table(ctx->compute_timing_table, ctx->compute_shader_stats); + if (input->executable_reloaded) { ui_init(ctx, ctx->ui_backing_store); DEBUG_DECL(start_frame_capture = ctx->os.start_frame_capture); @@ -784,7 +857,7 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) } draw_ui(ctx, input, frame_to_draw->ready_to_present? &frame_to_draw->frame : 0, - frame_to_draw->image_plane_tag, &frame_to_draw->stats); + frame_to_draw->image_plane_tag); ctx->frame_view_render_context.updated = 0; diff --git a/beamformer.h b/beamformer.h @@ -95,6 +95,10 @@ typedef struct { f32 processing_progress; b32 processing_compute; + u32 rf_data_timestamp_query; + + u32 shader_timer_ids[MAX_COMPUTE_SHADER_STAGES]; + uv4 dec_data_dim; u32 rf_raw_size; } ComputeShaderCtx; @@ -115,14 +119,40 @@ typedef enum { } ShaderKind; typedef struct { - /* TODO(rnp): there is assumption here that each shader will occur only once - * per compute. add an insertion index and change these to hold the max number - * of executed compute stages */ - u32 timer_ids[ComputeShaderKind_Count]; - f32 times[ComputeShaderKind_Count]; - b32 timer_active[ComputeShaderKind_Count]; + f32 times[ShaderKind_Count][32]; + f32 average_times[ShaderKind_Count]; + + u64 last_rf_timer_count; + f32 rf_time_deltas[32]; + f32 rf_time_delta_average; + + u32 latest_frame_index; + u32 latest_rf_index; } ComputeShaderStats; +/* TODO(rnp): maybe this also gets used for CPU timing info as well */ +typedef enum { + ComputeTimingInfoKind_ComputeFrameBegin, + ComputeTimingInfoKind_ComputeFrameEnd, + ComputeTimingInfoKind_Shader, + ComputeTimingInfoKind_RF_Data, +} ComputeTimingInfoKind; + +typedef struct { + u64 timer_count; + ComputeTimingInfoKind kind; + union { + ShaderKind shader; + }; +} ComputeTimingInfo; + +typedef struct { + u32 write_index; + u32 read_index; + b32 compute_frame_active; + ComputeTimingInfo buffer[4096]; +} ComputeTimingTable; + typedef struct BeamformFrame { uv3 dim; u32 texture; @@ -141,11 +171,10 @@ typedef struct BeamformFrame { } BeamformFrame; struct BeamformComputeFrame { - BeamformFrame frame; - ComputeShaderStats stats; + BeamformFrame frame; ImagePlaneTag image_plane_tag; - b32 in_flight; - b32 ready_to_present; + b32 in_flight; + b32 ready_to_present; }; #define GL_PARAMETERS \ @@ -199,6 +228,9 @@ typedef struct { BeamformWorkQueue *beamform_work_queue; + ComputeShaderStats *compute_shader_stats; + ComputeTimingTable *compute_timing_table; + SharedMemoryRegion shared_memory; } BeamformerCtx; diff --git a/opengl.h b/opengl.h @@ -50,6 +50,7 @@ #define GL_FRAMEBUFFER 0x8D40 #define GL_RENDERBUFFER 0x8D41 #define GL_RED_INTEGER 0x8D94 +#define GL_TIMESTAMP 0x8E28 #define GL_SHADER_STORAGE_BUFFER 0x90D2 #define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE #define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111 @@ -86,7 +87,6 @@ typedef uint64_t GLuint64; X(glDebugMessageCallback, void, (void (*)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user), void *user)) \ X(glDeleteBuffers, void, (GLsizei n, const GLuint *buffers)) \ X(glDeleteProgram, void, (GLuint program)) \ - X(glDeleteQueries, void, (GLsizei n, const GLuint *ids)) \ X(glDeleteShader, void, (GLuint shader)) \ X(glDispatchCompute, void, (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z)) \ X(glEndQuery, void, (GLenum target)) \ @@ -113,6 +113,7 @@ typedef uint64_t GLuint64; X(glProgramUniform3iv, void, (GLuint program, GLint location, GLsizei count, const GLint *value)) \ X(glProgramUniform4fv, void, (GLuint program, GLint location, GLsizei count, const GLfloat *value)) \ X(glProgramUniformMatrix4fv, void, (GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)) \ + X(glQueryCounter, void, (GLuint id, GLenum target)) \ X(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar **strings, const GLint *lengths)) \ X(glTextureParameteri, void, (GLuint texture, GLenum pname, GLint param)) \ X(glTextureParameterfv, void, (GLuint texture, GLenum pname, const GLfloat *param)) \ diff --git a/static.c b/static.c @@ -277,7 +277,9 @@ setup_beamformer(BeamformerCtx *ctx, BeamformerInput *input, Arena *memory) glfwMakeContextCurrent(raylib_window_handle); - ctx->beamform_work_queue = push_struct(memory, BeamformWorkQueue); + ctx->beamform_work_queue = push_struct(memory, BeamformWorkQueue); + ctx->compute_shader_stats = push_struct(memory, ComputeShaderStats); + ctx->compute_timing_table = push_struct(memory, ComputeTimingTable); ctx->shared_memory = os_create_shared_memory_area(memory, OS_SHARED_MEMORY_NAME, BeamformerSharedMemoryLockKind_Count, diff --git a/ui.c b/ui.c @@ -136,7 +136,6 @@ typedef enum { VT_BEAMFORMER_VARIABLE, VT_BEAMFORMER_FRAME_VIEW, VT_COMPUTE_STATS_VIEW, - VT_COMPUTE_LATEST_STATS_VIEW, VT_COMPUTE_PROGRESS_BAR, VT_SCALE_BAR, VT_UI_BUTTON, @@ -221,7 +220,6 @@ struct Variable { void *generic; BeamformerVariable beamformer_variable; ComputeProgressBar compute_progress_bar; - ComputeStatsView compute_stats_view; RegionSplit region_split; ScaleBar scale_bar; UIButtonID button; @@ -331,8 +329,7 @@ struct BeamformerUI { v2_sll *scale_bar_savepoint_freelist; - BeamformFrame *latest_plane[IPT_LAST + 1]; - ComputeShaderStats *latest_compute_stats; + BeamformFrame *latest_plane[IPT_LAST + 1]; BeamformerUIParameters params; b32 flush_params; @@ -1196,7 +1193,7 @@ ui_copy_frame(BeamformerUI *ui, Variable *view, RegionSplitDirection direction) mem_copy(bv->frame, old->frame, sizeof(*bv->frame)); bv->frame->texture = 0; bv->frame->next = 0; - alloc_beamform_frame(0, bv->frame, 0, old->frame->dim, s8("Frame Copy: "), ui->arena); + alloc_beamform_frame(0, bv->frame, old->frame->dim, s8("Frame Copy: "), ui->arena); glCopyImageSubData(old->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0, bv->frame->texture, GL_TEXTURE_3D, 0, 0, 0, 0, @@ -1321,13 +1318,8 @@ function s8 push_custom_view_title(Stream *s, Variable *var) { switch (var->type) { - case VT_COMPUTE_STATS_VIEW: - case VT_COMPUTE_LATEST_STATS_VIEW: { - stream_append_s8(s, s8("Compute Stats")); - if (var->type == VT_COMPUTE_LATEST_STATS_VIEW) - stream_append_s8(s, s8(": Live")); - } break; - case VT_COMPUTE_PROGRESS_BAR: { + case VT_COMPUTE_STATS_VIEW:{ stream_append_s8(s, s8("Compute Stats (Average)")); } break; + case VT_COMPUTE_PROGRESS_BAR:{ stream_append_s8(s, s8("Compute Progress: ")); stream_append_f64(s, 100 * *var->compute_progress_bar.progress, 100); stream_append_byte(s, '%'); @@ -2038,30 +2030,35 @@ draw_compute_progress_bar(BeamformerUI *ui, Arena arena, ComputeProgressBar *sta } function v2 -draw_compute_stats_view(BeamformerCtx *ctx, Arena arena, ComputeShaderStats *stats, Rect r) +draw_compute_stats_view(BeamformerCtx *ctx, Arena arena, Rect r) { #define X(e, n, s, h, pn) [ComputeShaderKind_##e] = s8_comp(pn ":"), read_only local_persist s8 labels[ComputeShaderKind_Count] = {COMPUTE_SHADERS}; #undef X - BeamformerSharedMemory *sm = ctx->shared_memory.region; + BeamformerSharedMemory *sm = ctx->shared_memory.region; + ComputeShaderStats *stats = ctx->compute_shader_stats; BeamformerUI *ui = ctx->ui; f32 compute_time_sum = 0; u32 stages = sm->compute_stages_count; TextSpec text_spec = {.font = &ui->font, .colour = FG_COLOUR, .flags = TF_LIMITED}; - Table *table = table_new(&arena, stages + 1, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); + static_assert(ShaderKind_Count <= 32, "shader kind bitfield test"); + u32 seen_shaders = 0; + Table *table = table_new(&arena, stages + 2, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); for (u32 i = 0; i < stages; i++) { TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; Stream sb = arena_stream(arena); ShaderKind index = (ShaderKind)sm->compute_stages[i]; - compute_time_sum += stats->times[index]; - stream_append_f64_e(&sb, stats->times[index]); - - cells[0].text = labels[index]; - cells[1].text = arena_stream_commit(&arena, &sb); - cells[2].text = s8("[s]"); + if ((seen_shaders & (1 << index)) == 0) { + compute_time_sum += stats->average_times[index]; + stream_append_f64_e(&sb, stats->average_times[index]); + seen_shaders |= (1 << index); + cells[0].text = labels[index]; + cells[1].text = arena_stream_commit(&arena, &sb); + cells[2].text = s8("[s]"); + } } TableCell *cells = table_push_row(table, &arena, TRK_CELLS)->data; @@ -2071,6 +2068,13 @@ draw_compute_stats_view(BeamformerCtx *ctx, Arena arena, ComputeShaderStats *sta cells[1].text = arena_stream_commit(&arena, &sb); cells[2].text = s8("[s]"); + cells = table_push_row(table, &arena, TRK_CELLS)->data; + sb = arena_stream(arena); + stream_append_f64_e(&sb, stats->rf_time_delta_average); + cells[0].text = s8("RF Upload Delta:"); + cells[1].text = arena_stream_commit(&arena, &sb); + cells[2].text = s8("[s]"); + table_extent(table, arena, text_spec.font); return draw_table(ui, arena, table, r, text_spec, (v2){0}, 0); } @@ -2268,13 +2272,7 @@ draw_ui_view(BeamformerUI *ui, Variable *ui_view, Rect r, v2 mouse, TextSpec tex case VT_COMPUTE_PROGRESS_BAR: { size = draw_compute_progress_bar(ui, ui->arena, &var->compute_progress_bar, r); } break; - case VT_COMPUTE_LATEST_STATS_VIEW: - case VT_COMPUTE_STATS_VIEW: { - ComputeShaderStats *stats = var->compute_stats_view.stats; - if (var->type == VT_COMPUTE_LATEST_STATS_VIEW) - stats = *(ComputeShaderStats **)stats; - size = draw_compute_stats_view(var->compute_stats_view.ctx, ui->arena, stats, r); - } break; + case VT_COMPUTE_STATS_VIEW:{ size = draw_compute_stats_view(var->generic, ui->arena, r); }break; InvalidDefaultCase; } @@ -2971,12 +2969,9 @@ ui_init(BeamformerCtx *ctx, Arena store) split = split->region_split.right; split->region_split.left = add_compute_progress_bar(split, ctx); - split->region_split.right = add_compute_stats_view(ui, split, &ui->arena, - VT_COMPUTE_LATEST_STATS_VIEW); - - ComputeStatsView *compute_stats = &split->region_split.right->group.first->compute_stats_view; - compute_stats->ctx = ctx; - compute_stats->stats = &ui->latest_compute_stats; + split->region_split.right = add_compute_stats_view(ui, split, &ui->arena, VT_COMPUTE_STATS_VIEW); + /* TODO(rnp): refactor to not need the beamformer ctx */ + split->region_split.right->group.first->generic = ctx; ctx->ui_read_params = 1; @@ -2994,15 +2989,13 @@ validate_ui_parameters(BeamformerUIParameters *p) } function void -draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw, ImagePlaneTag frame_plane, - ComputeShaderStats *latest_compute_stats) +draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw, ImagePlaneTag frame_plane) { BeamformerUI *ui = ctx->ui; BeamformerSharedMemory *sm = ctx->shared_memory.region; ui->latest_plane[IPT_LAST] = frame_to_draw; ui->latest_plane[frame_plane] = frame_to_draw; - ui->latest_compute_stats = latest_compute_stats; /* TODO(rnp): there should be a better way of detecting this */ if (ctx->ui_read_params) { diff --git a/util.h b/util.h @@ -70,6 +70,10 @@ #define SIGN(x) ((x) < 0? -1 : 1) #define swap(a, b) do {typeof(a) __tmp = (a); (a) = (b); (b) = __tmp;} while(0) +#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)) + /* NOTE(rnp): no guarantees about actually getting an element */ #define SLLPop(list) list; list = list ? list->next : 0 #define SLLPush(v, list) do { \