Commit: d401677f05f7c413d9ce3578b84e662573427b3a
Parent: f6d1edb4001a7f81f71f1c6b7b5a8b2436e06cc0
Author: Randy Palamar
Date: Sat, 23 Aug 2025 12:34:46 -0600
core: basic support for matched filtering
Diffstat:
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;