ogl_beamforming

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

Commit: d401677f05f7c413d9ce3578b84e662573427b3a
Parent: f6d1edb4001a7f81f71f1c6b7b5a8b2436e06cc0
Author: Randy Palamar
Date:   Sat, 23 Aug 2025 12:34:46 -0600

core: basic support for matched filtering

Diffstat:
Mbeamformer.c | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mbeamformer.h | 20++++++++++++--------
Mbeamformer_parameters.h | 60++++++++++++++++++++++++++++++++++++------------------------
Mbeamformer_shared_memory.c | 9++++++---
Mbuild.c | 139++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mhelpers/ogl_beamformer_lib.c | 29++++++++++++++++++-----------
Mhelpers/ogl_beamformer_lib_base.h | 11+++++++----
Mmath.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Dshaders/demod.glsl | 70----------------------------------------------------------------------
Ashaders/filter.glsl | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mstatic.c | 6+++---
Mui.c | 4++--
Mutil.c | 6+++---
Mutil.h | 1+
14 files changed, 546 insertions(+), 238 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -4,7 +4,6 @@ * - this means that das should have a RF version and an IQ version * - this will also flip the current hack to support demodulate after decode to * being a hack to support CudaHilbert after decode - * [ ]: filter sampling frequency should be a filter creation parameter * [ ]: measure performance of doing channel mapping in a separate shader * [ ]: BeamformWorkQueue -> BeamformerWorkQueue * [ ]: need to keep track of gpu memory in some way @@ -104,38 +103,53 @@ beamformer_compute_plan_for_block(BeamformerComputeContext *cc, u32 block, Arena function void beamformer_filter_update(BeamformerFilter *f, BeamformerFilterKind kind, - BeamformerFilterParameters fp, Arena arena) + BeamformerFilterParameters fp, u32 block, u32 slot, Arena arena) { - glDeleteTextures(1, &f->texture); - glCreateTextures(GL_TEXTURE_1D, 1, &f->texture); - glTextureStorage1D(f->texture, 1, GL_R32F, fp.length); - - f32 *filter = 0; + #define X(k, ...) s8(#k "Filter"), + read_only local_persist s8 filter_kinds[] = {BEAMFORMER_FILTER_KIND_LIST(,)}; + #undef X + + Stream sb = arena_stream(arena); + stream_append_s8s(&sb, filter_kinds[kind % countof(filter_kinds)], s8("[")); + stream_append_u64(&sb, block); + stream_append_s8(&sb, s8("][")); + stream_append_u64(&sb, slot); + stream_append_byte(&sb, ']'); + s8 label = arena_stream_commit(&arena, &sb); + + void *filter = 0; switch (kind) { case BeamformerFilterKind_Kaiser:{ - filter = kaiser_low_pass_filter(&arena, fp.cutoff_frequency, fp.sampling_frequency, - fp.beta, fp.length); + /* TODO(rnp): this should also support complex */ + /* TODO(rnp): implement this as an IFIR filter instead to reduce computation */ + filter = kaiser_low_pass_filter(&arena, fp.Kaiser.cutoff_frequency, fp.sampling_frequency, + fp.Kaiser.beta, (i32)fp.Kaiser.length); + f->length = (i32)fp.Kaiser.length; + f->time_delay = (f32)f->length / 2.0f / fp.sampling_frequency; + }break; + case BeamformerFilterKind_MatchedChirp:{ + typeof(fp.MatchedChirp) *mc = &fp.MatchedChirp; + f32 fs = fp.sampling_frequency; + f->length = (i32)(mc->duration * fs); + if (fp.complex) { + filter = baseband_chirp(&arena, mc->min_frequency, mc->max_frequency, fs, f->length, 1, 0.5f); + f->time_delay = complex_filter_first_moment(filter, f->length, fs); + } else { + filter = rf_chirp(&arena, mc->min_frequency, mc->max_frequency, fs, f->length, 1); + f->time_delay = real_filter_first_moment(filter, f->length, fs); + } }break; InvalidDefaultCase; } f->kind = kind; f->parameters = fp; - glTextureSubImage1D(f->texture, 0, 0, fp.length, GL_RED, GL_FLOAT, filter); -} -function f32 -beamformer_filter_time_offset(BeamformerFilter *f) -{ - f32 result = 0; - BeamformerFilterParameters *fp = &f->parameters; - switch (f->kind) { - case BeamformerFilterKind_Kaiser:{ - result = (f32)fp->length / 2.0f / fp->sampling_frequency; - }break; - InvalidDefaultCase; - } - return result; + glDeleteTextures(1, &f->texture); + glCreateTextures(GL_TEXTURE_1D, 1, &f->texture); + glTextureStorage1D(f->texture, 1, fp.complex? GL_RG32F : GL_R32F, f->length); + glTextureSubImage1D(f->texture, 0, 0, f->length, fp.complex? GL_RG : GL_RED, GL_FLOAT, filter); + glObjectLabel(GL_TEXTURE, f->texture, (i32)label.len, (c8 *)label.data); } function iv3 @@ -421,9 +435,20 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb) commit = 1; }break; case BeamformerShaderKind_Demodulate:{ - if (decode_first || (!decode_first && data_kind == BeamformerDataKind_Float32)) - shader = BeamformerShaderKind_DemodulateFloat; - bp->time_offset += beamformer_filter_time_offset(cp->filters + sp->filter_slot); + BeamformerFilter *f = cp->filters + sp->filter_slot; + if (decode_first || (!decode_first && data_kind == BeamformerDataKind_Float32)) { + if (f->parameters.complex) shader = BeamformerShaderKind_DemodulateFloatCF; + else shader = BeamformerShaderKind_DemodulateFloat; + } else if (f->parameters.complex) { + shader = BeamformerShaderKind_DemodulateCF; + } + bp->time_offset += f->time_delay; + commit = 1; + }break; + case BeamformerShaderKind_Filter:{ + BeamformerFilter *f = cp->filters + sp->filter_slot; + if (f->parameters.complex) shader = BeamformerShaderKind_FilterCF; + bp->time_offset += f->time_delay; commit = 1; }break; case BeamformerShaderKind_DAS:{ @@ -491,7 +516,7 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb) * IQ[n] = I[n] - j*Q[n] */ if (demodulate) { - BeamformerDemodulateUBO *mp = &cp->demod_ubo_data; + BeamformerFilterUBO *mp = &cp->demod_ubo_data; mp->demodulation_frequency = bp->center_frequency; mp->sampling_frequency = bp->sampling_frequency / 2; mp->decimation_rate = bp->decimation_rate; @@ -520,13 +545,28 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb) cp->decode_dispatch.x = (u32)ceil_f32((f32)bp->dec_data_dim[0] / DECODE_LOCAL_SIZE_X); } - - cp->demod_dispatch.x = (u32)ceil_f32((f32)bp->dec_data_dim[0] / DEMOD_LOCAL_SIZE_X); - cp->demod_dispatch.y = (u32)ceil_f32((f32)bp->dec_data_dim[1] / DEMOD_LOCAL_SIZE_Y); - cp->demod_dispatch.z = (u32)ceil_f32((f32)bp->dec_data_dim[2] / DEMOD_LOCAL_SIZE_Z); } + + /* TODO(rnp): filter may need a different dispatch layout */ + cp->demod_dispatch.x = (u32)ceil_f32((f32)bp->dec_data_dim[0] / FILTER_LOCAL_SIZE_X); + cp->demod_dispatch.y = (u32)ceil_f32((f32)bp->dec_data_dim[1] / FILTER_LOCAL_SIZE_Y); + cp->demod_dispatch.z = (u32)ceil_f32((f32)bp->dec_data_dim[2] / FILTER_LOCAL_SIZE_Z); + /* TODO(rnp): if IQ (* 8) else (* 4) */ cp->rf_size = bp->dec_data_dim[0] * bp->dec_data_dim[1] * bp->dec_data_dim[2] * 8; + + /* TODO(rnp): UBO per filter stage */ + BeamformerFilterUBO *flt = &cp->filter_ubo_data; + flt->demodulation_frequency = bp->center_frequency; + flt->sampling_frequency = bp->sampling_frequency; + flt->decimation_rate = 1; + flt->map_channels = 0; + flt->output_channel_stride = bp->dec_data_dim[0] * bp->dec_data_dim[2]; + flt->output_sample_stride = 1; + flt->output_transmit_stride = bp->dec_data_dim[0]; + flt->input_channel_stride = bp->dec_data_dim[0] * bp->dec_data_dim[2]; + flt->input_sample_stride = 1; + flt->input_transmit_stride = bp->dec_data_dim[0]; } function void @@ -684,17 +724,26 @@ do_compute_shader(BeamformerCtx *ctx, BeamformerComputePlan *cp, BeamformerFrame cuda_hilbert(input_ssbo_idx, output_ssbo_idx); cc->last_output_ssbo_index = !cc->last_output_ssbo_index; }break; + case BeamformerShaderKind_Filter: + case BeamformerShaderKind_FilterCF: case BeamformerShaderKind_Demodulate: + case BeamformerShaderKind_DemodulateCF: case BeamformerShaderKind_DemodulateFloat: + case BeamformerShaderKind_DemodulateFloatCF: { - BeamformerDemodulateUBO *ubo = &cp->demod_ubo_data; + BeamformerFilterUBO *ubo = &cp->demod_ubo_data; + if (shader == BeamformerShaderKind_Filter) + ubo = &cp->filter_ubo_data; - glBindBufferBase(GL_UNIFORM_BUFFER, 0, cp->ubos[BeamformerComputeUBOKind_Demodulate]); + u32 index = shader == BeamformerShaderKind_Filter ? BeamformerComputeUBOKind_Filter + : BeamformerComputeUBOKind_Demodulate; + glBindBufferBase(GL_UNIFORM_BUFFER, 0, cp->ubos[index]); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, cc->ping_pong_ssbos[output_ssbo_idx]); if (!ubo->map_channels) glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, cc->ping_pong_ssbos[input_ssbo_idx]); - glBindImageTexture(0, cp->filters[sp->filter_slot].texture, 0, 0, 0, GL_READ_ONLY, GL_R32F); + GLenum kind = cp->filters[sp->filter_slot].parameters.complex? GL_RG32F : GL_R32F; + glBindImageTexture(0, cp->filters[sp->filter_slot].texture, 0, 0, 0, GL_READ_ONLY, kind); if (ubo->map_channels) glBindImageTexture(1, cp->textures[BeamformerComputeTextureKind_ChannelMapping], 0, 0, 0, GL_READ_ONLY, GL_R16I); @@ -826,16 +875,52 @@ shader_text_with_header(ShaderReloadContext *ctx, OS *os, Arena *arena) stream_append_s8s(&sb, s8("#version 460 core\n\n"), ctx->header); switch (ctx->kind) { + case BeamformerShaderKind_Filter: + case BeamformerShaderKind_FilterCF: case BeamformerShaderKind_Demodulate: + case BeamformerShaderKind_DemodulateCF: case BeamformerShaderKind_DemodulateFloat: + case BeamformerShaderKind_DemodulateFloatCF: { - stream_append_s8(&sb, s8("" - "layout(local_size_x = " str(DEMOD_LOCAL_SIZE_X) ", " - "local_size_y = " str(DEMOD_LOCAL_SIZE_Y) ", " - "local_size_z = " str(DEMOD_LOCAL_SIZE_Z) ") in;\n\n" - )); - if (ctx->kind == BeamformerShaderKind_DemodulateFloat) - stream_append_s8(&sb, s8("#define INPUT_DATA_TYPE_FLOAT\n\n")); + switch (ctx->kind) { + case BeamformerShaderKind_FilterCF: + case BeamformerShaderKind_DemodulateCF: + case BeamformerShaderKind_DemodulateFloatCF: + { + stream_append_s8(&sb, s8("#define COMPLEX_FILTER 1\n\n")); + }break; + default:{ + stream_append_s8(&sb, s8("#define COMPLEX_FILTER 0\n\n")); + }break; + } + + switch (ctx->kind) { + case BeamformerShaderKind_Filter: + case BeamformerShaderKind_FilterCF: + case BeamformerShaderKind_DemodulateFloat: + case BeamformerShaderKind_DemodulateFloatCF: + { + stream_append_s8(&sb, s8("#define INPUT_DATA_TYPE_FLOAT\n\n")); + }break; + default:{}break; + } + + switch (ctx->kind) { + case BeamformerShaderKind_Demodulate: + case BeamformerShaderKind_DemodulateCF: + case BeamformerShaderKind_DemodulateFloat: + case BeamformerShaderKind_DemodulateFloatCF: + { + stream_append_s8(&sb, s8("#define DEMODULATE\n\n")); + }break; + default:{}break; + } + + stream_append_s8(&sb, s8("" + "layout(local_size_x = " str(FILTER_LOCAL_SIZE_X) ", " + "local_size_y = " str(FILTER_LOCAL_SIZE_Y) ", " + "local_size_z = " str(FILTER_LOCAL_SIZE_Z) ") in;\n\n" + )); }break; case BeamformerShaderKind_DAS: case BeamformerShaderKind_DASFast: @@ -967,31 +1052,32 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena *arena, iptr gl_c src->shader = cs->programs + src->kind; }break; case BeamformerShaderKind_Decode:{ - src->kind = BeamformerShaderKind_DecodeFloatComplex; - src->shader = cs->programs + src->kind; - success &= reload_compute_shader(ctx, src, s8(" (F32C)"), *arena); - - src->kind = BeamformerShaderKind_DecodeFloat; - src->shader = cs->programs + src->kind; - success &= reload_compute_shader(ctx, src, s8(" (F32)"), *arena); - - src->kind = BeamformerShaderKind_DecodeInt16Complex; - src->shader = cs->programs + src->kind; - success &= reload_compute_shader(ctx, src, s8(" (I16C)"), *arena); - - src->kind = BeamformerShaderKind_DecodeInt16ToFloat; - src->shader = cs->programs + src->kind; - success &= reload_compute_shader(ctx, src, s8(" (I16-F32)"), *arena); - + read_only local_persist struct { BeamformerShaderKind kind; s8 suffix; } derivatives[] = { + #define X(k, __1, __2, suffix, ...) {BeamformerShaderKind_## k, s8(suffix)}, + DECODE_SHADER_VARIATIONS + #undef X + }; + for EachElement(derivatives, it) { + src->kind = derivatives[it].kind; + src->shader = cs->programs + src->kind; + success &= reload_compute_shader(ctx, src, derivatives[it].suffix, *arena); + } src->kind = BeamformerShaderKind_Decode; src->shader = cs->programs + src->kind; }break; - case BeamformerShaderKind_Demodulate:{ - src->kind = BeamformerShaderKind_DemodulateFloat; - src->shader = cs->programs + src->kind; - success &= reload_compute_shader(ctx, src, s8(" (F32)"), *arena); - - src->kind = BeamformerShaderKind_Demodulate; + case BeamformerShaderKind_Filter:{ + read_only local_persist struct { BeamformerShaderKind kind; s8 suffix; } derivatives[] = { + {BeamformerShaderKind_Demodulate, s8(" (Demodulate)")}, + #define X(k, __1, __2, suffix, ...) {BeamformerShaderKind_## k, s8(suffix)}, + FILTER_SHADER_VARIATIONS + #undef X + }; + for EachElement(derivatives, it) { + src->kind = derivatives[it].kind; + src->shader = cs->programs + src->kind; + success &= reload_compute_shader(ctx, src, derivatives[it].suffix, *arena); + } + src->kind = BeamformerShaderKind_Filter; src->shader = cs->programs + src->kind; }break; default:{}break; @@ -1037,8 +1123,10 @@ complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena *arena, iptr gl_c case BeamformerWorkKind_CreateFilter:{ /* TODO(rnp): this should probably get deleted and moved to lazy loading */ BeamformerCreateFilterContext *fctx = &work->create_filter_context; - BeamformerComputePlan *cp = beamformer_compute_plan_for_block(cs, fctx->parameter_block, arena); - beamformer_filter_update(cp->filters + fctx->filter_slot, fctx->kind, fctx->parameters, *arena); + u32 block = fctx->parameter_block; + u32 slot = fctx->filter_slot; + BeamformerComputePlan *cp = beamformer_compute_plan_for_block(cs, block, arena); + beamformer_filter_update(cp->filters + slot, fctx->kind, fctx->parameters, block, slot, *arena); }break; case BeamformerWorkKind_ComputeIndirect:{ fill_frame_compute_work(ctx, work, work->compute_indirect_context.view_plane, diff --git a/beamformer.h b/beamformer.h @@ -98,13 +98,15 @@ typedef struct { } BeamformerRenderModel; typedef struct { - BeamformerFilterKind kind; + BeamformerFilterKind kind; BeamformerFilterParameters parameters; + f32 time_delay; + i32 length; u32 texture; } BeamformerFilter; /* X(name, type, gltype) */ -#define BEAMFORMER_DEMOD_UBO_PARAM_LIST \ +#define BEAMFORMER_FILTER_UBO_PARAM_LIST \ X(input_channel_stride, u32, uint) \ X(input_sample_stride, u32, uint) \ X(input_transmit_stride, u32, uint) \ @@ -136,17 +138,19 @@ static_assert((sizeof(BeamformerDecodeUBO) & 15) == 0, "UBO size must be a multi typedef alignas(16) struct { #define X(name, type, ...) type name; - BEAMFORMER_DEMOD_UBO_PARAM_LIST + BEAMFORMER_FILTER_UBO_PARAM_LIST #undef X float _pad[2]; -} BeamformerDemodulateUBO; -static_assert((sizeof(BeamformerDemodulateUBO) & 15) == 0, "UBO size must be a multiple of 16"); +} BeamformerFilterUBO; +static_assert((sizeof(BeamformerFilterUBO) & 15) == 0, "UBO size must be a multiple of 16"); /* TODO(rnp): das should remove redundant info and add voxel transform */ +/* TODO(rnp): need 1 UBO per filter slot */ #define BEAMFORMER_COMPUTE_UBO_LIST \ - X(DAS, BeamformerParameters, das) \ - X(Decode, BeamformerDecodeUBO, decode) \ - X(Demodulate, BeamformerDemodulateUBO, demod) + X(DAS, BeamformerParameters, das) \ + X(Decode, BeamformerDecodeUBO, decode) \ + X(Filter, BeamformerFilterUBO, filter) \ + X(Demodulate, BeamformerFilterUBO, demod) #define X(k, ...) BeamformerComputeUBOKind_##k, typedef enum {BEAMFORMER_COMPUTE_UBO_LIST BeamformerComputeUBOKind_Count} BeamformerComputeUBOKind; diff --git a/beamformer_parameters.h b/beamformer_parameters.h @@ -10,27 +10,37 @@ * be organized for simple offset access per frame). */ -/* X(enumarant, number, shader file name, pretty name) */ +/* X(enumarant, shader file name, pretty name) */ #define COMPUTE_SHADERS \ - X(CudaDecode, 0, "", "CUDA Decode") \ - X(CudaHilbert, 1, "", "CUDA Hilbert") \ - X(DAS, 2, "das", "DAS") \ - X(Decode, 3, "decode", "Decode (I16)") \ - X(Demodulate, 4, "demod", "Demodulate (I16)") \ - X(MinMax, 5, "min_max", "Min/Max") \ - X(Sum, 6, "sum", "Sum") + X(CudaDecode, "", "CUDA Decode") \ + X(CudaHilbert, "", "CUDA Hilbert") \ + X(DAS, "das", "DAS") \ + X(Decode, "decode", "Decode (I16)") \ + X(Filter, "filter", "Filter (F32C)") \ + X(Demodulate, "", "Demodulate (I16)") \ + X(MinMax, "min_max", "Min/Max") \ + X(Sum, "sum", "Sum") + +#define DECODE_SHADER_VARIATIONS \ + X(DecodeInt16Complex, "", "Decode (I16C)", " (I16)") \ + X(DecodeFloat, "", "Decode (F32)", " (F32)") \ + X(DecodeFloatComplex, "", "Decode (F32C)", " (F32C)") \ + X(DecodeInt16ToFloat, "", "Decode (I16-F32)", " (I16-F32)") + +#define FILTER_SHADER_VARIATIONS \ + X(FilterCF, "", "Filter (F32C-CF)", " (F32C-CF)") \ + X(DemodulateCF, "", "Demodulate (I16-CF)", " (I16-CF)") \ + X(DemodulateFloat, "", "Demodulate (F32)", " (F32)") \ + X(DemodulateFloatCF, "", "Demodulate (F32-CF)", " (F32-CF)") #define COMPUTE_SHADERS_INTERNAL \ - COMPUTE_SHADERS \ - X(DecodeInt16Complex, 7, "", "Decode (I16C)") \ - X(DecodeFloat, 8, "", "Decode (F32)") \ - X(DecodeFloatComplex, 9, "", "Decode (F32C)") \ - X(DecodeInt16ToFloat, 10, "", "Decode (I16-F32)") \ - X(DemodulateFloat, 11, "", "Demodulate (F32)") \ - X(DASFast, 12, "", "DAS (Fast)") + COMPUTE_SHADERS \ + DECODE_SHADER_VARIATIONS \ + FILTER_SHADER_VARIATIONS \ + X(DASFast, "", "DAS (Fast)") typedef enum { - #define X(e, n, ...) BeamformerShaderKind_##e = n, + #define X(e, ...) BeamformerShaderKind_##e, COMPUTE_SHADERS_INTERNAL #undef X BeamformerShaderKind_Render3D, @@ -61,12 +71,14 @@ typedef struct { typedef enum {BEAMFORMER_DATA_KIND_LIST} BeamformerDataKind; #undef X -#define BEAMFORMER_FILTER_KIND_LIST \ - X(Kaiser, 0) \ - X(MatchedSine, 1) +/* TODO(rnp): this is an absolute abuse of the preprocessor, but now is + * not a good time to write a full metaprogram */ +#define BEAMFORMER_FILTER_KIND_LIST(type, _) \ + X(Kaiser, type cutoff_frequency _ type beta _ type length) \ + X(MatchedChirp, type duration _ type min_frequency _ type max_frequency) -#define X(k, id) BeamformerFilterKind_##k = id, -typedef enum {BEAMFORMER_FILTER_KIND_LIST} BeamformerFilterKind; +#define X(kind, ...) BeamformerFilterKind_##kind, +typedef enum {BEAMFORMER_FILTER_KIND_LIST(,) BeamformerFilterKind_Count} BeamformerFilterKind; #undef X /* X(type, id, pretty name) */ @@ -97,9 +109,9 @@ typedef enum { X(EPIC_UHERCULES, 9, "EPIC-UHERCULES", 0) \ X(FLASH, 10, "Flash", 0) -#define DEMOD_LOCAL_SIZE_X 64 -#define DEMOD_LOCAL_SIZE_Y 1 -#define DEMOD_LOCAL_SIZE_Z 1 +#define FILTER_LOCAL_SIZE_X 64 +#define FILTER_LOCAL_SIZE_Y 1 +#define FILTER_LOCAL_SIZE_Z 1 #define DECODE_LOCAL_SIZE_X 4 #define DECODE_LOCAL_SIZE_Y 1 diff --git a/beamformer_shared_memory.c b/beamformer_shared_memory.c @@ -13,13 +13,16 @@ typedef enum { BeamformerWorkKind_UploadBuffer, } BeamformerWorkKind; +/* TODO(rnp): this is massively bloating the queue; think of some other + * way to communicate these to the beamformer */ typedef struct { union { - struct {f32 beta; f32 cutoff_frequency;}; - f32 xdc_center_frequency; + #define X(kind, ...) struct {__VA_ARGS__ ;} kind; + BEAMFORMER_FILTER_KIND_LIST(f32, ;) + #undef X }; f32 sampling_frequency; - i16 length; + b16 complex; } BeamformerFilterParameters; typedef struct { diff --git a/build.c b/build.c @@ -657,7 +657,7 @@ build_helper_library(Arena arena, CommandList cc) ///////////// // header char *lib_header_out = OUTPUT("ogl_beamformer_lib.h"); - if (needs_rebuild(lib_header_out, "beamformer_parameters.h", "helpers/ogl_beamformer_lib_base.h")) { + if (needs_rebuild(lib_header_out, "helpers/ogl_beamformer_lib_base.h")) { s8 parameters_header = os_read_whole_file(&arena, "beamformer_parameters.h"); s8 base_header = os_read_whole_file(&arena, "helpers/ogl_beamformer_lib_base.h"); result = parameters_header.len != 0 && base_header.len != 0 && @@ -710,6 +710,45 @@ build_tests(Arena arena, CommandList cc) return result; } +typedef struct { + s8 *data; + iz count; + iz capacity; +} s8_list; + +function void +s8_split(s8 str, s8 *left, s8 *right, u8 byte) +{ + iz i; + for (i = 0; i < str.len; i++) if (str.data[i] == byte) break; + + if (left) *left = (s8){.data = str.data, .len = i}; + if (right) { + right->data = str.data + i + 1; + right->len = MAX(0, str.len - (i + 1)); + } +} + +function s8 +s8_trim(s8 in) +{ + s8 result = in; + for (iz i = 0; i < in.len && *result.data == ' '; i++) result.data++; + result.len -= result.data - in.data; + for (; result.len > 0 && result.data[result.len - 1] == ' '; result.len--); + return result; +} + +function void +s8_list_from_s8(s8_list *list, Arena *arena, s8 str) +{ + s8 right = str, left; + while (right.len > 0) { + s8_split(right, &left, &right, ' '); + left = s8_trim(left); + if (left.len > 0) { *da_push(arena, list) = left; } + } +} typedef struct { Stream stream; @@ -733,27 +772,18 @@ meta_indent(MetaprogramContext *m) stream_append_byte(&m->stream, '\t'); } +#define meta_push(m, ...) meta_push_(m, arg_list(s8, __VA_ARGS__)) function void -meta_begin_scope(MetaprogramContext *m, s8 line) -{ - meta_indent(m); - m->indentation_level++; - stream_append_s8s(&m->stream, line, s8("\n")); -} - -function void -meta_push_line(MetaprogramContext *m, s8 line) +meta_push_(MetaprogramContext *m, s8 *items, iz count) { - meta_indent(m); - stream_append_s8s(&m->stream, line, s8("\n")); + stream_append_s8s_(&m->stream, items, count); } -function void -meta_end_scope(MetaprogramContext *m, s8 line) -{ - m->indentation_level--; - meta_push_line(m, line); -} +#define meta_begin_line(m, ...) do { meta_indent(m); meta_push(m, __VA_ARGS__); } while(0) +#define meta_end_line(m, ...) do { meta_push(m, __VA_ARGS__, s8("\n")); } while(0) +#define meta_push_line(m, ...) do { meta_indent(m); meta_push(m, __VA_ARGS__, s8("\n")); } while(0) +#define meta_begin_scope(m, ...) do { meta_push_line(m, __VA_ARGS__); (m)->indentation_level++; } while(0) +#define meta_end_scope(m, ...) do { (m)->indentation_level--; meta_push_line(m, __VA_ARGS__); } while(0) #define meta_begin_matlab_class_cracker(_1, _2, FN, ...) FN #define meta_begin_matlab_class_1(m, name) meta_begin_scope(m, s8("classdef " name)) @@ -774,10 +804,19 @@ meta_push_matlab_property(MetaprogramContext *m, s8 name, i64 length) stream_append_s8(&m->stream, s8(")\n")); } +function void +meta_push_matlab_enum_with_value(MetaprogramContext *m, s8 name, i32 value) +{ + meta_indent(m); + stream_append_s8s(&m->stream, name, s8(" (")); + stream_append_i64(&m->stream, value); + stream_append_s8(&m->stream, s8(")\n")); +} + function b32 meta_end_and_write_matlab(MetaprogramContext *m, char *path) { - while (m->indentation_level > 0) meta_end_scope((m), s8("end")); + while (m->indentation_level > 0) meta_end_scope(m, s8("end")); b32 result = meta_write_and_reset(m, path); return result; } @@ -788,9 +827,10 @@ build_matlab_bindings(Arena arena) b32 result = 1; os_make_directory(OUTPUT("matlab")); + Arena scratch = sub_arena(&arena, MB(1), 16); + char *out = OUTPUT("matlab/OGLBeamformerLiveFeedbackFlags.m"); - /* NOTE(rnp): if one file is outdated all files are outdated */ - if (needs_rebuild(out, "beamformer_parameters.h")) { + if (needs_rebuild(out)) { /* TODO(rnp): recreate/clear directory incase these file names change */ MetaprogramContext m = {.stream = arena_stream(arena)}; @@ -800,17 +840,66 @@ build_matlab_bindings(Arena arena) BEAMFORMER_LIVE_IMAGING_DIRTY_FLAG_LIST result &= meta_end_and_write_matlab(&m, out); + meta_begin_matlab_class(&m, "OGLBeamformerDataKind", "int32"); + meta_begin_scope(&m, s8("enumeration")); + BEAMFORMER_DATA_KIND_LIST + result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerDataKind.m")); + #undef X + + #define X(kind, ...) meta_push_matlab_enum_with_value(&m, s8(#kind), BeamformerFilterKind_## kind); + meta_begin_matlab_class(&m, "OGLBeamformerFilterKind", "int32"); + meta_begin_scope(&m, s8("enumeration")); + BEAMFORMER_FILTER_KIND_LIST(,) + result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerFilterKind.m")); + #undef X + + #define X(kind, ...) meta_push_matlab_enum_with_value(&m, s8(#kind), BeamformerShaderKind_## kind); meta_begin_matlab_class(&m, "OGLBeamformerShaderStage", "int32"); meta_begin_scope(&m, s8("enumeration")); COMPUTE_SHADERS result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerShaderStage.m")); + #undef X - meta_begin_matlab_class(&m, "OGLBeamformerDataKind", "int32"); - meta_begin_scope(&m, s8("enumeration")); - BEAMFORMER_DATA_KIND_LIST - result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/OGLBeamformerDataKind.m")); + os_make_directory(OUTPUT("matlab/+OGLBeamformerFilter")); + #define X(kind, ...) {OUTPUT("matlab/+OGLBeamformerFilter/" #kind ".m"), s8(#kind), s8(#__VA_ARGS__)}, + read_only local_persist struct {char *out; s8 class, args;} filter_table[] = { + BEAMFORMER_FILTER_KIND_LIST(,) + }; #undef X + s8_list members = {0}; + for EachElement(filter_table, filter) { + typeof(*filter_table) *f = filter_table + filter; + members.count = 0; + s8_list_from_s8(&members, &scratch, f->args); + meta_begin_scope(&m, s8("classdef "), f->class, s8(" < OGLBeamformerFilter.BaseFilter")); + + meta_begin_scope(&m, s8("properties")); + for (iz it = 0; it < members.count; it++) + meta_push_matlab_property(&m, members.data[it], 1); + meta_end_scope(&m, s8("end")); + + meta_begin_scope(&m, s8("methods")); + meta_begin_line(&m, s8("function obj = "), f->class, s8("(")); + for (iz it = 0; it < members.count; it++) + meta_push(&m, it > 0 ? s8(", ") : s8(""), members.data[it]); + meta_end_line(&m, s8(")")); + + m.indentation_level++; + for (iz it = 0; it < members.count; it++) + meta_push_line(&m, s8("obj."), members.data[it], s8(" = "), members.data[it], s8(";")); + result &= meta_end_and_write_matlab(&m, f->out); + } + + meta_begin_matlab_class(&m, "BaseFilter"); + meta_begin_scope(&m, s8("methods")); + meta_begin_scope(&m, s8("function out = Flatten(obj)")); + meta_push_line(&m, s8("fields = struct2cell(struct(obj));")); + meta_push_line(&m, s8("out = zeros(1, numel(fields));")); + meta_begin_scope(&m, s8("for i = 1:numel(fields)")); + meta_push_line(&m, s8("out(i) = fields{i};")); + result &= meta_end_and_write_matlab(&m, OUTPUT("matlab/+OGLBeamformerFilter/BaseFilter.m")); + #define X(name, __t, __s, elements, ...) meta_push_line(&m, s8(#name "(1," #elements ")")); meta_begin_matlab_class(&m, "OGLBeamformerParameters"); meta_begin_scope(&m, s8("properties")); diff --git a/helpers/ogl_beamformer_lib.c b/helpers/ogl_beamformer_lib.c @@ -317,7 +317,7 @@ beamformer_push_pipeline(i32 *shaders, u32 shader_count, BeamformerDataKind data } function b32 -beamformer_create_filter(BeamformerFilterKind kind, BeamformerFilterParameters params, u8 filter_slot, u8 parameter_block) +beamformer_create_filter_base(BeamformerFilterKind kind, BeamformerFilterParameters params, u8 filter_slot, u8 parameter_block) { b32 result = 0; if (check_shared_memory()) { @@ -337,16 +337,23 @@ beamformer_create_filter(BeamformerFilterKind kind, BeamformerFilterParameters p } b32 -beamformer_create_kaiser_low_pass_filter(f32 beta, f32 cutoff_frequency, f32 sampling_frequency, - i16 length, u8 filter_slot, u8 parameter_block) -{ - BeamformerFilterParameters params = { - .beta = beta, - .cutoff_frequency = cutoff_frequency, - .sampling_frequency = sampling_frequency, - .length = length, - }; - b32 result = beamformer_create_filter(BeamformerFilterKind_Kaiser, params, filter_slot, parameter_block); +beamformer_create_filter(BeamformerFilterKind kind, f32 *filter_parameters, u32 filter_parameter_count, + f32 sampling_frequency, b32 complex, u8 filter_slot, u8 parameter_block) +{ + b32 result = 0; + if (lib_error_check(kind >= 0 && kind < BeamformerFilterKind_Count, BF_LIB_ERR_KIND_INVALID_FILTER_KIND)) { + BeamformerFilterParameters fp = {.sampling_frequency = sampling_frequency, .complex = complex != 0}; + #define X(kind, ...) sizeof(fp.kind), + read_only local_persist u32 kind_sizes[] = {BEAMFORMER_FILTER_KIND_LIST(,)}; + #undef X + if (lib_error_check(kind_sizes[kind] == sizeof(f32) * filter_parameter_count, + BF_LIB_ERR_KIND_INVALID_FILTER_PARAM_COUNT)) + { + /* NOTE(rnp): any filter kind struct works as base offset of union */ + mem_copy(&fp.Kaiser, filter_parameters, kind_sizes[kind]); + result = beamformer_create_filter_base(kind, fp, filter_slot, parameter_block); + } + } return result; } diff --git a/helpers/ogl_beamformer_lib_base.h b/helpers/ogl_beamformer_lib_base.h @@ -23,7 +23,9 @@ X(EXPORT_SPACE_OVERFLOW, 12, "not enough space for data export") \ X(SHARED_MEMORY, 13, "failed to open shared memory region") \ X(SYNC_VARIABLE, 14, "failed to acquire lock within timeout period") \ - X(INVALID_TIMEOUT, 15, "invalid timeout value") + X(INVALID_TIMEOUT, 15, "invalid timeout value") \ + X(INVALID_FILTER_KIND, 16, "invalid filter kind") \ + X(INVALID_FILTER_PARAM_COUNT, 17, "invalid parameters count passed for filter") #define X(type, num, string) BF_LIB_ERR_KIND_ ##type = num, typedef enum {BEAMFORMER_LIB_ERRORS} BeamformerLibErrorKind; @@ -108,9 +110,10 @@ LIB_FN uint32_t beamformer_push_focal_vectors_at(float *vectors, uint32_t c * M: * M = (A - 8) / (2.285 (ω_s - ω_p)) */ -LIB_FN uint32_t beamformer_create_kaiser_low_pass_filter(float beta, float cutoff_frequency, - float sampling_frequency, int16_t length, - uint8_t filter_slot, uint8_t parameter_block); + +LIB_FN uint32_t beamformer_create_filter(BeamformerFilterKind kind, float *filter_parameters, + uint32_t filter_parameter_count, float sampling_frequency, + uint32_t complex, uint8_t filter_slot, uint8_t parameter_block); ////////////////////////// // Live Imaging Controls diff --git a/math.c b/math.c @@ -121,26 +121,6 @@ make_hadamard_transpose(Arena *a, i32 dim) return result; } -/* NOTE(rnp): adapted from "Discrete Time Signal Processing" (Oppenheim) */ -function f32 * -kaiser_low_pass_filter(Arena *arena, f32 cutoff_frequency, f32 sampling_frequency, f32 beta, i32 length) -{ - f32 *result = push_array(arena, f32, length); - f32 wc = 2 * PI * cutoff_frequency / sampling_frequency; - f32 a = (f32)length / 2.0f; - f32 pi_i0_b = PI * (f32)cephes_i0(beta); - - for (i32 n = 0; n < length; n++) { - f32 t = (f32)n - a; - f32 impulse = !f32_cmp(t, 0) ? sin_f32(wc * t) / t : wc; - t = t / a; - f32 window = (f32)cephes_i0(beta * sqrt_f32(1 - t * t)) / pi_i0_b; - result[n] = impulse * window; - } - - return result; -} - function b32 iv2_equal(iv2 a, iv2 b) { @@ -217,6 +197,13 @@ v2_floor(v2 a) } function f32 +v2_magnitude_squared(v2 a) +{ + f32 result = a.x * a.x + a.y * a.y; + return result; +} + +function f32 v2_magnitude(v2 a) { f32 result = sqrt_f32(a.x * a.x + a.y * a.y); @@ -593,6 +580,92 @@ obb_raycast(m4 obb_orientation, v3 obb_size, v3 obb_center, ray r) return result; } +function f32 +complex_filter_first_moment(v2 *filter, i32 length, f32 sampling_frequency) +{ + f32 n = 0, d = 0; + for (i32 i = 0; i < length; i++) { + f32 t = v2_magnitude_squared(filter[i]); + n += (f32)i * t; + d += t; + } + f32 result = n / d / sampling_frequency; + return result; +} + +function f32 +real_filter_first_moment(f32 *filter, i32 length, f32 sampling_frequency) +{ + f32 n = 0, d = 0; + for (i32 i = 0; i < length; i++) { + f32 t = filter[i] * filter[i]; + n += (f32)i * t; + d += t; + } + f32 result = n / d / sampling_frequency; + return result; +} + +function f32 +tukey_window(f32 t, f32 tapering) +{ + f32 r = tapering; + f32 result = 1; + if (t < r / 2) result = 0.5f * (1 + cos_f32(2 * PI * (t - r / 2) / r)); + if (t >= 1 - r / 2) result = 0.5f * (1 + cos_f32(2 * PI * (t - 1 + r / 2) / r)); + return result; +} + +/* NOTE(rnp): adapted from "Discrete Time Signal Processing" (Oppenheim) */ +function f32 * +kaiser_low_pass_filter(Arena *arena, f32 cutoff_frequency, f32 sampling_frequency, f32 beta, i32 length) +{ + f32 *result = push_array(arena, f32, length); + f32 wc = 2 * PI * cutoff_frequency / sampling_frequency; + f32 a = (f32)length / 2.0f; + f32 pi_i0_b = PI * (f32)cephes_i0(beta); + + for (i32 n = 0; n < length; n++) { + f32 t = (f32)n - a; + f32 impulse = !f32_cmp(t, 0) ? sin_f32(wc * t) / t : wc; + t = t / a; + f32 window = (f32)cephes_i0(beta * sqrt_f32(1 - t * t)) / pi_i0_b; + result[n] = impulse * window; + } + + return result; +} + +function f32 * +rf_chirp(Arena *arena, f32 min_frequency, f32 max_frequency, f32 sampling_frequency, + i32 length, b32 reverse) +{ + f32 *result = push_array(arena, f32, length); + for (i32 i = 0; i < length; i++) { + i32 index = reverse? length - 1 - i : i; + f32 fc = min_frequency + (f32)i * (max_frequency - min_frequency) / (2 * (f32)length); + f32 arg = 2 * PI * fc * (f32)i / sampling_frequency; + result[index] = sin_f32(arg) * tukey_window((f32)i / (f32)length, 0.2f); + } + return result; +} + +function v2 * +baseband_chirp(Arena *arena, f32 min_frequency, f32 max_frequency, f32 sampling_frequency, + i32 length, b32 reverse, f32 scale) +{ + v2 *result = push_array(arena, v2, length); + f32 conjugate = reverse ? -1 : 1; + for (i32 i = 0; i < length; i++) { + i32 index = reverse? length - 1 - i : i; + f32 fc = min_frequency + (f32)i * (max_frequency - min_frequency) / (2 * (f32)length); + f32 arg = 2 * PI * fc * (f32)i / sampling_frequency; + v2 sample = {{scale * cos_f32(arg), conjugate * scale * sin_f32(arg)}}; + result[index] = v2_scale(sample, tukey_window((f32)i / (f32)length, 0.2f)); + } + return result; +} + function v4 hsv_to_rgb(v4 hsv) { diff --git a/shaders/demod.glsl b/shaders/demod.glsl @@ -1,70 +0,0 @@ -/* See LICENSE for license details. */ -#if defined(INPUT_DATA_TYPE_FLOAT) - #define DATA_TYPE vec2 - #define RESULT_TYPE_CAST(v) (v) - #define SAMPLE_TYPE_CAST(v) (v) -#else - #define DATA_TYPE uint - #define RESULT_TYPE_CAST(v) packSnorm2x16(v) - #define SAMPLE_TYPE_CAST(v) unpackSnorm2x16(v) -#endif - -layout(std430, binding = 1) readonly restrict buffer buffer_1 { - DATA_TYPE in_data[]; -}; - -layout(std430, binding = 2) writeonly restrict buffer buffer_2 { - DATA_TYPE out_data[]; -}; - -layout(r32f, binding = 0) readonly restrict uniform image1D filter_coefficients; -layout(r16i, binding = 1) readonly restrict uniform iimage1D channel_mapping; - -vec2 rotate_iq(vec2 iq, uint index) -{ - float arg = radians(360) * demodulation_frequency * index / sampling_frequency; - mat2 phasor = mat2( cos(arg), sin(arg), - -sin(arg), cos(arg)); - vec2 result = phasor * iq; - return result; -} - -vec2 sample_rf(uint index) -{ - vec2 result = SAMPLE_TYPE_CAST(in_data[index]); - return result; -} - -void main() -{ - uint in_sample = gl_GlobalInvocationID.x * decimation_rate; - uint out_sample = gl_GlobalInvocationID.x; - uint channel = gl_GlobalInvocationID.y; - uint transmit = gl_GlobalInvocationID.z; - - uint in_channel = map_channels ? imageLoad(channel_mapping, int(channel)).x : channel; - uint in_offset = input_channel_stride * in_channel + input_transmit_stride * transmit; - uint out_offset = output_channel_stride * channel + - output_transmit_stride * transmit + - output_sample_stride * out_sample; - - int target; - if (map_channels) { - target = int(output_channel_stride / output_sample_stride); - } else { - target = int(output_transmit_stride); - } - - if (out_sample < target) { - vec2 result = vec2(0); - int index = int(in_sample) - imageSize(filter_coefficients).x; - int start = index < 0 ? -index : 0; - index += start; - target *= int(decimation_rate); - for (int i = start; i < imageSize(filter_coefficients).x && index < target; i++, index++) { - vec2 iq = sqrt(2.0f) * rotate_iq(sample_rf(in_offset + index) * vec2(1, -1), index); - result += iq * imageLoad(filter_coefficients, imageSize(filter_coefficients).x - i - 1).x; - } - out_data[out_offset] = RESULT_TYPE_CAST(result); - } -} diff --git a/shaders/filter.glsl b/shaders/filter.glsl @@ -0,0 +1,98 @@ +/* See LICENSE for license details. */ +#if defined(INPUT_DATA_TYPE_FLOAT) + #define DATA_TYPE vec2 + #define RESULT_TYPE_CAST(v) (v) + #define SAMPLE_TYPE_CAST(v) (v) +#else + #define DATA_TYPE uint + #define RESULT_TYPE_CAST(v) packSnorm2x16(v) + #define SAMPLE_TYPE_CAST(v) unpackSnorm2x16(v) +#endif + +layout(std430, binding = 1) readonly restrict buffer buffer_1 { + DATA_TYPE in_data[]; +}; + +layout(std430, binding = 2) writeonly restrict buffer buffer_2 { + DATA_TYPE out_data[]; +}; + +layout(r16i, binding = 1) readonly restrict uniform iimage1D channel_mapping; + +#if COMPLEX_FILTER + layout(rg32f, binding = 0) readonly restrict uniform image1D filter_coefficients; + #define apply_filter(iq, h) complex_mul((iq), (h).xy) +#else + layout(r32f, binding = 0) readonly restrict uniform image1D filter_coefficients; + #define apply_filter(iq, h) ((iq) * (h).x) +#endif + +vec2 complex_mul(vec2 a, vec2 b) +{ + mat2 m = mat2(b.x, b.y, -b.y, b.x); + vec2 result = m * a; + return result; +} + +vec2 rotate_iq(vec2 iq, int index) +{ + float arg = radians(360) * demodulation_frequency * index / sampling_frequency; + /* TODO(rnp): this can be optimized based on the sampling mode. for 4x sampling + * (NS200BW) coefficients cycle through (cos) {1, 0, -1, 0} (sin) {0, -1, 0, 1} + * so we don't actually need to use the special function unit. There should be an + * equivalent for BS100BW and BS50BW as well */ + mat2 phasor = mat2(cos(arg), -sin(arg), + sin(arg), cos(arg)); + vec2 result = phasor * iq; + return result; +} + +vec2 sample_rf(uint index) +{ + vec2 result = SAMPLE_TYPE_CAST(in_data[index]); + return result; +} + +void main() +{ + uint in_sample = gl_GlobalInvocationID.x * decimation_rate; + uint out_sample = gl_GlobalInvocationID.x; + uint channel = gl_GlobalInvocationID.y; + uint transmit = gl_GlobalInvocationID.z; + + uint in_channel = map_channels ? imageLoad(channel_mapping, int(channel)).x : channel; + uint in_offset = input_channel_stride * in_channel + input_transmit_stride * transmit; + uint out_offset = output_channel_stride * channel + + output_transmit_stride * transmit + + output_sample_stride * out_sample; + + int target; + if (map_channels) { + target = int(output_channel_stride / output_sample_stride); + } else { + target = int(output_transmit_stride); + } + + if (out_sample < target) { + target *= int(decimation_rate); + + vec2 result = vec2(0); + int a_length = target; + int b_length = imageSize(filter_coefficients).x; + int index = int(in_sample); + + const float scale = bool(COMPLEX_FILTER) ? 1 : sqrt(2); + + for (int j = max(0, index - b_length); j < min(index, a_length); j++) { + vec2 iq = sample_rf(in_offset + j); + vec4 h = imageLoad(filter_coefficients, index - j); + #if defined(DEMODULATE) + result += scale * apply_filter(rotate_iq(iq * vec2(1, -1), -j), h); + #else + result += apply_filter(iq, h); + #endif + } + + out_data[out_offset] = RESULT_TYPE_CAST(result); + } +} diff --git a/static.c b/static.c @@ -425,14 +425,14 @@ setup_beamformer(Arena *memory, BeamformerCtx **o_ctx, BeamformerInput **o_input BEAMFORMER_DECODE_UBO_PARAM_LIST "};\n\n" ), - [BeamformerShaderKind_Demodulate] = s8_comp("layout(std140, binding = 0) uniform parameters {\n" - BEAMFORMER_DEMOD_UBO_PARAM_LIST + [BeamformerShaderKind_Filter] = s8_comp("layout(std140, binding = 0) uniform parameters {\n" + BEAMFORMER_FILTER_UBO_PARAM_LIST "};\n\n" ), #undef X }; - #define X(e, sn, f, pretty_name) do if (s8(f).len > 0) { \ + #define X(e, f, ...) do if (s8(f).len > 0) { \ ShaderReloadContext *src = push_struct(memory, typeof(*src)); \ src->beamformer_context = ctx; \ src->header = compute_headers[BeamformerShaderKind_##e]; \ diff --git a/ui.c b/ui.c @@ -2641,7 +2641,7 @@ draw_compute_stats_bar_view(BeamformerUI *ui, Arena arena, ComputeShaderStats *s } } - #define X(e, n, s, pn) [BeamformerShaderKind_##e] = s8_comp(pn ": "), + #define X(e, s, pn, ...) [BeamformerShaderKind_##e] = s8_comp(pn ": "), read_only local_persist s8 labels[BeamformerShaderKind_ComputeCount] = {COMPUTE_SHADERS_INTERNAL}; #undef X @@ -2769,7 +2769,7 @@ draw_compute_stats_view(BeamformerUI *ui, Arena arena, Variable *view, Rect r, v Table *table = table_new(&arena, 2, TextAlignment_Left, TextAlignment_Left, TextAlignment_Left); switch (csv->kind) { case ComputeStatsViewKind_Average:{ - #define X(e, n, s, pn) [BeamformerShaderKind_##e] = s8_comp(pn ":"), + #define X(e, n, pn, ...) [BeamformerShaderKind_##e] = s8_comp(pn ":"), read_only local_persist s8 labels[BeamformerShaderKind_ComputeCount] = {COMPUTE_SHADERS_INTERNAL}; #undef X da_reserve(&arena, table, stages); diff --git a/util.c b/util.c @@ -611,13 +611,13 @@ parse_f64(s8 s) { f64 integral = 0, fractional = 0, sign = 1; - if (s.len && *s.data == '-') { + if (s.len > 0 && *s.data == '-') { sign = -1; s.data++; s.len--; } - while (s.len && *s.data != '.') { + while (s.len > 0 && *s.data != '.') { integral *= 10; integral += *s.data - '0'; s.data++; @@ -626,7 +626,7 @@ parse_f64(s8 s) if (*s.data == '.') { s.data++; s.len--; } - while (s.len) { + while (s.len > 0) { ASSERT(s.data[s.len - 1] != '.'); fractional /= 10; fractional += (f64)(s.data[--s.len] - '0') / 10.0; diff --git a/util.h b/util.h @@ -139,6 +139,7 @@ typedef char c8; typedef uint8_t u8; typedef int16_t i16; typedef uint16_t u16; +typedef uint16_t b16; typedef int32_t i32; typedef uint32_t u32; typedef int64_t i64;