ogl_beamforming

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

Commit: efd1b742e36a88a49d48cef56fe8dae05dfc0ef6
Parent: feca7d86eb20ea5a9b8b8e6c113ffc4374e449ee
Author: Randy Palamar
Date:   Tue, 11 Nov 2025 13:59:12 -0700

meta: add conditional generation

this brings the @Emit directive closer to being able to do all
generation directly without supporting code in the build tool

parameter struct generation is now moved to metacode

Diffstat:
Mbeamformer.c | 28+++++++++++++---------------
Mbeamformer.meta | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbeamformer_parameters.h | 59-----------------------------------------------------------
Mbuild.c | 741+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mgenerated/beamformer.meta.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhelpers/ogl_beamformer_lib.c | 12++++++------
Mmath.c | 10----------
Mtests/throughput.c | 34+++++++++++++++++-----------------
Mui.c | 38++++++++++++++++++--------------------
Mutil.c | 58++++++++++++++++++++++++++++++++++++++++------------------
Mutil.h | 22++++++++++++++++++++++
11 files changed, 922 insertions(+), 315 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -290,7 +290,7 @@ alloc_shader_storage(BeamformerCtx *ctx, u32 decoded_data_size, Arena arena) /* NOTE(rnp): these are stubs when CUDA isn't supported */ cuda_register_buffers(cc->ping_pong_ssbos, countof(cc->ping_pong_ssbos), cc->rf_buffer.ssbo); u32 decoded_data_dimension[3] = {pb->parameters.sample_count, pb->parameters.channel_count, pb->parameters.acquisition_count}; - cuda_init(pb->parameters.raw_data_dimensions, decoded_data_dimension); + cuda_init(pb->parameters.raw_data_dimensions.E, decoded_data_dimension); } function void @@ -406,13 +406,11 @@ compute_cursor_finished(struct compute_cursor *cursor) function m4 das_voxel_transform_matrix(BeamformerParameters *bp) { - v3 min = v3_from_f32_array(bp->output_min_coordinate); - v3 max = v3_from_f32_array(bp->output_max_coordinate); - v3 extent = v3_abs(v3_sub(max, min)); - v3 points = v3_from_iv3(make_valid_output_points(bp->output_points)); + v3 extent = v3_abs(v3_sub(bp->output_max_coordinate, bp->output_min_coordinate)); + v3 points = v3_from_iv3(make_valid_output_points(bp->output_points.E)); m4 T1 = m4_translation(v3_scale(v3_sub(points, (v3){{1.0f, 1.0f, 1.0f}}), -0.5f)); - m4 T2 = m4_translation(v3_add(min, v3_scale(extent, 0.5f))); + m4 T2 = m4_translation(v3_add(bp->output_min_coordinate, v3_scale(extent, 0.5f))); m4 S = m4_scale(v3_div(extent, points)); m4 R; @@ -649,9 +647,9 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb) BeamformerShaderDASBakeParameters *db = &sd->bake.DAS; BeamformerDASUBO *du = &cp->das_ubo_data; - du->voxel_transform = das_voxel_transform_matrix(&pb->parameters); - mem_copy(du->xdc_transform.E, pb->parameters.xdc_transform, sizeof(du->xdc_transform)); - mem_copy(du->xdc_element_pitch.E, pb->parameters.xdc_element_pitch, sizeof(du->xdc_element_pitch)); + du->voxel_transform = das_voxel_transform_matrix(&pb->parameters); + du->xdc_transform = pb->parameters.xdc_transform; + du->xdc_element_pitch = pb->parameters.xdc_element_pitch; db->sampling_frequency = sampling_frequency; db->demodulation_frequency = pb->parameters.demodulation_frequency; db->speed_of_sound = pb->parameters.speed_of_sound; @@ -662,8 +660,8 @@ plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb) db->channel_count = pb->parameters.channel_count; db->acquisition_count = pb->parameters.acquisition_count; db->interpolation_mode = pb->parameters.interpolation_mode; - db->transmit_angle = pb->parameters.focal_vector[0]; - db->focus_depth = pb->parameters.focal_vector[1]; + db->transmit_angle = pb->parameters.focal_vector.E[0]; + db->focus_depth = pb->parameters.focal_vector.E[1]; db->transmit_receive_orientation = pb->parameters.transmit_receive_orientation; if (pb->parameters.single_focus) sd->bake.flags |= BeamformerShaderDASFlags_SingleFocus; @@ -853,11 +851,11 @@ beamformer_commit_parameter_block(BeamformerCtx *ctx, BeamformerComputePlan *cp, if (cp->hadamard_order != (i32)cp->acquisition_count) update_hadamard_texture(cp, (i32)cp->acquisition_count, arena); - cp->min_coordinate = v3_from_f32_array(pb->parameters.output_min_coordinate); - cp->max_coordinate = v3_from_f32_array(pb->parameters.output_max_coordinate); + cp->min_coordinate = pb->parameters.output_min_coordinate; + cp->max_coordinate = pb->parameters.output_max_coordinate; - cp->output_points = make_valid_output_points(pb->parameters.output_points); - cp->average_frames = pb->parameters.output_points[3]; + cp->output_points = make_valid_output_points(pb->parameters.output_points.E); + cp->average_frames = pb->parameters.output_points.E[3]; GLenum gl_kind = cp->iq_pipeline ? GL_RG32F : GL_R32F; if (cp->average_frames > 1 && !beamformer_frame_compatible(ctx->averaged_frames + 0, cp->output_points, gl_kind)) { diff --git a/beamformer.meta b/beamformer.meta @@ -86,6 +86,54 @@ [Cubic] } +@Table([name type]) ParametersHead +{ + [xdc_transform M4] + [xdc_element_pitch V2] + [raw_data_dimensions UV2] + [focal_vector V2] + [transmit_receive_orientation U32] + [sample_count U32] + [channel_count U32] + [acquisition_count U32] + [das_shader_id U32] + [time_offset F32] + [single_focus U8] + [single_orientation U8] + [decode_mode U8] + [sampling_mode U8] +} + +@Table([name type]) ParametersUI +{ + [output_min_coordinate V3] + [output_max_coordinate V3] + [output_points SV4] + [sampling_frequency F32] + [demodulation_frequency F32] + [speed_of_sound F32] + [f_number F32] + [off_axis_pos F32] + [interpolation_mode U32] + [coherency_weighting U32] + [beamform_plane U32] + [decimation_rate U32] +} + +// TODO(rnp): definable constants in meta code (or at least references to enum values) +@Table([name type elements]) ParametersSimple +{ + [channel_mapping S16 256] + [sparse_elements S16 256] + [transmit_receive_orientations U8 256] + [steering_angles F32 256] + [focal_depths F32 256] + [compute_stages S32 16] + [compute_stage_parameters S32 16] + [compute_stages_count U32 1] + [data_kind S32 1] +} + @Expand(AcquisitionKind) @Enumeration(AcquisitionKind `$(name)`) @Expand(DataKind) @Enumeration(DataKind `$(name)`) @Expand(FilterKind) @Enumeration(FilterKind `$(name)`) @@ -94,6 +142,25 @@ @Emit { `typedef struct {` + @Expand(ParametersHead) ` $(%type)$(|)$(name);` + @Expand(ParametersUI) ` $(%type)$(|)$(name);` + `} BeamformerParameters;` + `` + `typedef struct {` + @Expand(ParametersHead) ` $(%type)$(|)$(name);` + `} BeamformerParametersHead;` + `` + `typedef struct {` + @Expand(ParametersUI) ` $(%type)$(|)$(name);` + `} BeamformerUIParameters;` + `` + `typedef struct {` + @Expand(ParametersHead) ` $(%type)$(|)$(name);` + @Expand(ParametersUI) ` $(%type)$(|)$(name);` + @Expand(ParametersSimple) ` $(%type)$(|)$(name)$(elements > 1 -> "[" elements "]");` + `} BeamformerSimpleParameters;` + `` + `typedef struct {` ` BeamformerEmissionKind kind;` ` union {` ` struct {` @@ -234,3 +301,72 @@ { @Shader(render_3d.frag.glsl) Render3D } + +///////////////// +// NOTE: Library + +@Emit(CLibrary) +{ + `typedef struct {` + @Expand(ParametersHead) ` $(%type)$(|)$(name)$(#type > 1 -> "[" #type "]");` + @Expand(ParametersUI) ` $(%type)$(|)$(name)$(#type > 1 -> "[" #type "]");` + @Expand(ParametersSimple) ` $(%type)$(|)$(name)$(elements > 1 -> "[" elements "]");` + `} BeamformerSimpleParameters;` + `` + `typedef struct {` + @Expand(ParametersUI) ` $(%type)$(|)$(name)$(#type > 1 -> "[" #type "]");` + `} BeamformerUIParameters;` + `` + `typedef struct {` + @Expand(ParametersHead) ` $(%type)$(|)$(name)$(#type > 1 -> "[" #type "]");` + `} BeamformerParametersHead;` + `` + `typedef struct {` + @Expand(ParametersHead) ` $(%type)$(|)$(name)$(#type > 1 -> "[" #type "]");` + @Expand(ParametersUI) ` $(%type)$(|)$(name)$(#type > 1 -> "[" #type "]");` + `} BeamformerParameters;` + `` +} + +//////////////// +// NOTE: MATLAB + +@Emit(MATLAB) Parameters +{ + `classdef OGLBeamformerParameters` + ` properties` + @Expand(ParametersHead) ` $(name)(1,$(#type))$(|)$(%type)` + ` % UI Parameters` + @Expand(ParametersUI) ` $(name)(1,$(#type))$(|)$(%type)` + ` end` + `end` +} + +@Emit(MATLAB) ParametersHead +{ + `classdef OGLBeamformerParametersHead` + ` properties` + @Expand(ParametersHead) ` $(name)(1,$(#type))$(|)$(%type)` + ` end` + `end` +} + +@Emit(MATLAB) ParametersUI +{ + `classdef OGLBeamformerParametersUI` + ` properties` + @Expand(ParametersUI) ` $(name)(1,$(#type))$(|)$(%type)` + ` end` + `end` +} + +@Emit(MATLAB) SimpleParameters +{ + `classdef OGLBeamformerSimpleParameters` + ` properties` + @Expand(ParametersHead) ` $(name)(1,$(#type)) $(%type)` + @Expand(ParametersUI) ` $(name)(1,$(#type)) $(%type)` + @Expand(ParametersSimple) ` $(name)(1,$(elements)) $(%type)` + ` end` + `end` +} diff --git a/beamformer_parameters.h b/beamformer_parameters.h @@ -1,5 +1,4 @@ /* See LICENSE for license details. */ -#include <stdint.h> /* TODO(rnp): * [ ]: shader kinds have ballooned; shader stats table needs to be compressed @@ -42,64 +41,6 @@ typedef enum { typedef enum {BEAMFORMER_CONSTANTS_LIST} BeamformerConstants; #undef X -/* X(name, type, size, matlab_type, elements, comment) */ -#define BEAMFORMER_PARAMS_HEAD \ - X(xdc_transform, float, [16], single, 16, "IMPORTANT: column major order") \ - X(xdc_element_pitch, float, [2], single, 2, "[m] Transducer Element Pitch {row, col}") \ - X(raw_data_dimensions, uint32_t, [2], uint32, 2, "Raw Data Dimensions") \ - X(focal_vector, float, [2], single, 2, "[degree, m] focal point {angle, depth}") \ - X(transmit_receive_orientation, uint32_t, , uint32, 1, "") \ - X(sample_count, uint32_t, , uint32, 1, "") \ - X(channel_count, uint32_t, , uint32, 1, "") \ - X(acquisition_count, uint32_t, , uint32, 1, "") \ - X(das_shader_id, uint32_t, , uint32, 1, "") \ - X(time_offset, float, , single, 1, "pulse length correction time [s]") \ - X(single_focus, uint8_t, , uint8, 1, "") \ - X(single_orientation, uint8_t, , uint8, 1, "") \ - X(decode_mode, uint8_t, , uint8, 1, "") \ - X(sampling_mode, uint8_t, , uint8, 1, "") - -#define BEAMFORMER_UI_PARAMS \ - X(output_min_coordinate, float, [3], single, 3, "[m] Back-Top-Left corner of output region") \ - X(output_max_coordinate, float, [3], single, 3, "[m] Front-Bottom-Right corner of output region") \ - X(output_points, int32_t, [4], int32, 4, "Width * Height * Depth * (Frame Average Count)") \ - X(sampling_frequency, float, , single, 1, "[Hz]") \ - X(demodulation_frequency, float, , single, 1, "[Hz]") \ - X(speed_of_sound, float, , single, 1, "[m/s]") \ - X(f_number, float, , single, 1, "F# (set to 0 to disable)") \ - X(off_axis_pos, float, , single, 1, "[m] Position on screen normal to beamform in TPW/VLS/HERCULES") \ - X(interpolation_mode, uint32_t, , uint32, 1, "Nearest, Linear, or Cubic Interpolation of RF Samples") \ - X(coherency_weighting, uint32_t, , uint32, 1, "Apply coherency weighting to output data") \ - X(beamform_plane, uint32_t, , uint32, 1, "Plane to Beamform in TPW/VLS/HERCULES") \ - X(decimation_rate, uint32_t, , uint32, 1, "Number of times to decimate") - -#define BEAMFORMER_SIMPLE_PARAMS \ - X(channel_mapping, int16_t, [BeamformerMaxChannelCount], int16, BeamformerMaxChannelCount) \ - X(sparse_elements, int16_t, [BeamformerMaxChannelCount], int16, BeamformerMaxChannelCount) \ - X(transmit_receive_orientations, uint8_t, [BeamformerMaxChannelCount], uint8, BeamformerMaxChannelCount) \ - X(steering_angles, float, [BeamformerMaxChannelCount], single, BeamformerMaxChannelCount) \ - X(focal_depths, float, [BeamformerMaxChannelCount], single, BeamformerMaxChannelCount) \ - X(compute_stages, int32_t, [BeamformerMaxComputeShaderStages], int32, BeamformerMaxComputeShaderStages) \ - X(compute_stage_parameters, int16_t, [BeamformerMaxComputeShaderStages], int16, BeamformerMaxComputeShaderStages) \ - X(compute_stages_count, uint32_t, , uint32, 1) \ - X(data_kind, int32_t, , int32, 1) - -#define X(name, type, size, ...) type name size; -typedef struct {BEAMFORMER_PARAMS_HEAD} BeamformerParametersHead; -typedef struct {BEAMFORMER_UI_PARAMS} BeamformerUIParameters; - -typedef struct { - BEAMFORMER_PARAMS_HEAD - BEAMFORMER_UI_PARAMS -} BeamformerParameters; - -typedef struct { - BEAMFORMER_PARAMS_HEAD - BEAMFORMER_UI_PARAMS - BEAMFORMER_SIMPLE_PARAMS -} BeamformerSimpleParameters; -#undef X - #define BEAMFORMER_LIVE_IMAGING_DIRTY_FLAG_LIST \ X(ImagePlaneOffsets, 0) \ X(TransmitPower, 1) \ diff --git a/build.c b/build.c @@ -925,14 +925,43 @@ meta_end_and_write_matlab(MetaprogramContext *m, char *path) X(SubShader) \ X(Table) +typedef enum { + #define X(k, ...) MetaEntryKind_## k, + META_ENTRY_KIND_LIST + #undef X + MetaEntryKind_Count, +} MetaEntryKind; + +#define X(k, ...) #k, +read_only global char *meta_entry_kind_strings[] = {META_ENTRY_KIND_LIST}; +#undef X + +#define META_EMIT_LANG_LIST \ + X(C) \ + X(CLibrary) \ + X(MATLAB) + +typedef enum { + #define X(k, ...) MetaEmitLang_## k, + META_EMIT_LANG_LIST + #undef X + MetaEmitLang_Count, +} MetaEmitLang; + #define META_KIND_LIST \ - X(F32, f32, float, single, 4) \ - X(S32, i32, int32_t, int32, 4) \ - X(S16, i16, int16_t, int16, 2) \ - X(S8, i8, int8_t, int8, 1) \ - X(U32, u32, uint32_t, uint32, 4) \ - X(U16, u16, uint16_t, uint16, 2) \ - X(U8, u8, uint8_t, uint8, 1) + X(M4, m4, float, single, 64, 16) \ + X(SV4, iv4, int32_t, int32, 16, 4) \ + X(UV4, uv4, uint32_t, uint32, 16, 4) \ + X(UV2, uv2, uint32_t, uint32, 8, 2) \ + X(V3, v3, float, single, 12, 3) \ + X(V2, v2, float, single, 8, 2) \ + X(F32, f32, float, single, 4, 1) \ + X(S32, i32, int32_t, int32, 4, 1) \ + X(S16, i16, int16_t, int16, 2, 1) \ + X(S8, i8, int8_t, int8, 1, 1) \ + X(U32, u32, uint32_t, uint32, 4, 1) \ + X(U16, u16, uint16_t, uint16, 2, 1) \ + X(U8, u8, uint8_t, uint8, 1, 1) typedef enum { #define X(k, ...) MetaKind_## k, @@ -947,6 +976,12 @@ read_only global u8 meta_kind_byte_sizes[] = { #undef X }; +read_only global u8 meta_kind_elements[] = { + #define X(_k, _c, _b, _m, _by, elements, ...) elements, + META_KIND_LIST + #undef X +}; + read_only global s8 meta_kind_meta_types[] = { #define X(k, ...) s8_comp(#k), META_KIND_LIST @@ -971,17 +1006,6 @@ read_only global s8 meta_kind_c_types[] = { #undef X }; -typedef enum { - #define X(k, ...) MetaEntryKind_## k, - META_ENTRY_KIND_LIST - #undef X - MetaEntryKind_Count, -} MetaEntryKind; - -#define X(k, ...) #k, -read_only global char *meta_entry_kind_strings[] = {META_ENTRY_KIND_LIST}; -#undef X - typedef struct { u32 line, column; } MetaLocation; #define META_ENTRY_ARGUMENT_KIND_LIST \ @@ -1132,6 +1156,20 @@ meta_entry_print(MetaEntry *e, i32 indent, i32 caret) fprintf(stderr, "\n"); } +function iz +meta_lookup_string_slow(s8 *strings, iz string_count, s8 s) +{ + // TODO(rnp): obviously this is slow + iz result = -1; + for (iz i = 0; i < string_count; i++) { + if (s8_equal(s, strings[i])) { + result = i; + break; + } + } + return result; +} + function MetaEntryKind meta_entry_kind_from_string(s8 s) { @@ -1139,12 +1177,8 @@ meta_entry_kind_from_string(s8 s) read_only local_persist s8 kinds[] = {META_ENTRY_KIND_LIST}; #undef X MetaEntryKind result = MetaEntryKind_Invalid; - for EachNonZeroEnumValue(MetaEntryKind, kind) { - if (s8_equal(kinds[kind], s)) { - result = kind; - break; - } - } + iz id = meta_lookup_string_slow(kinds + 1, countof(kinds) - 1, s); + if (id > 0) result = (MetaEntryKind)(id + 1); return result; } @@ -1162,7 +1196,8 @@ meta_parser_trim(MetaParser *p) }break; case '\n':{ p->p.location.line++; p->p.location.column = 0; comment = 0; }break; case '/':{ - comment = ((s + 1) != end && s[1] == '/'); + comment = ((s + 1) != end && s[1] == '/'); + if (comment) s++; } /* FALLTHROUGH */ default:{done = !comment;}break; } @@ -1536,14 +1571,48 @@ DA_STRUCT(MetaMUnion, MetaMUnion); typedef enum { MetaExpansionPartKind_Alignment, + MetaExpansionPartKind_Conditional, MetaExpansionPartKind_EvalKind, + MetaExpansionPartKind_EvalKindCount, MetaExpansionPartKind_Reference, MetaExpansionPartKind_String, } MetaExpansionPartKind; +typedef enum { + MetaExpansionConditionalArgumentKind_Invalid, + MetaExpansionConditionalArgumentKind_Number, + MetaExpansionConditionalArgumentKind_Evaluation, + MetaExpansionConditionalArgumentKind_Reference, +} MetaExpansionConditionalArgumentKind; + +typedef struct { + MetaExpansionConditionalArgumentKind kind; + union { + s8 *strings; + i64 number; + }; +} MetaExpansionConditionalArgument; + +typedef enum { + MetaExpansionOperation_Invalid, + MetaExpansionOperation_LessThan, + MetaExpansionOperation_GreaterThan, +} MetaExpansionOperation; + +typedef struct { + MetaExpansionConditionalArgument lhs; + MetaExpansionConditionalArgument rhs; + MetaExpansionOperation op; + u32 instruction_skip; +} MetaExpansionConditional; + typedef struct { - s8 *strings; MetaExpansionPartKind kind; + union { + s8 string; + s8 *strings; + MetaExpansionConditional conditional; + }; } MetaExpansionPart; DA_STRUCT(MetaExpansionPart, MetaExpansionPart); @@ -1567,7 +1636,14 @@ typedef struct { MetaEmitOperationKind kind; MetaLocation location; } MetaEmitOperation; -DA_STRUCT(MetaEmitOperation, MetaEmitOperation); + +typedef struct { + MetaEmitOperation *data; + iz count; + iz capacity; + + s8 filename; +} MetaEmitOperationList; typedef struct { MetaEmitOperationList *data; @@ -1592,7 +1668,7 @@ typedef struct { s8_list munion_namespaces; MetaMUnionList munions; - MetaEmitOperationListSet emit_sets; + MetaEmitOperationListSet emit_sets[MetaEmitLang_Count]; MetaShaderBakeParametersList shader_bake_parameters; MetaIDList shader_enumerations; @@ -1602,21 +1678,6 @@ typedef struct { s8_list shader_names; } MetaContext; - -function iz -meta_lookup_string_slow(s8 *strings, iz string_count, s8 s) -{ - // TODO(rnp): obviously this is slow - iz result = -1; - for (iz i = 0; i < string_count; i++) { - if (s8_equal(s, strings[i])) { - result = i; - break; - } - } - return result; -} - function iz meta_lookup_id_slow(MetaIDList *v, u32 id) { @@ -1893,8 +1954,11 @@ meta_push_expansion_part(MetaContext *ctx, Arena *arena, MetaExpansionPartList * MetaExpansionPart *result = da_push(arena, parts); result->kind = kind; switch (kind) { - case MetaExpansionPartKind_Alignment:{}break; + case MetaExpansionPartKind_Alignment: + case MetaExpansionPartKind_Conditional: + {}break; case MetaExpansionPartKind_EvalKind: + case MetaExpansionPartKind_EvalKindCount: case MetaExpansionPartKind_Reference: { iz index = meta_lookup_string_slow(t->fields, t->field_count, string); @@ -1906,14 +1970,154 @@ meta_push_expansion_part(MetaContext *ctx, Arena *arena, MetaExpansionPartList * (i32)table_name.len, table_name.data, (i32)string.len, string.data); } }break; - case MetaExpansionPartKind_String:{ - result->strings = push_struct(arena, s8); - result->strings[0] = string; + case MetaExpansionPartKind_String:{ result->string = string; }break; + InvalidDefaultCase; + } + return result; +} + +#define META_EXPANSION_TOKEN_LIST \ + X('|', Alignment) \ + X('%', TypeEval) \ + X('#', TypeEvalElements) \ + X('"', Quote) \ + X('-', Dash) \ + X('>', GreaterThan) \ + X('<', LessThan) \ + +typedef enum { + MetaExpansionToken_EOF, + MetaExpansionToken_Identifier, + MetaExpansionToken_Number, + MetaExpansionToken_String, + #define X(__1, kind, ...) MetaExpansionToken_## kind, + META_EXPANSION_TOKEN_LIST + #undef X + MetaExpansionToken_Count, +} MetaExpansionToken; + +read_only global s8 meta_expansion_token_strings[] = { + s8_comp("EOF"), + s8_comp("Indentifier"), + s8_comp("Number"), + s8_comp("String"), + #define X(s, kind, ...) s8_comp(#s), + META_EXPANSION_TOKEN_LIST + #undef X +}; + +typedef struct { + s8 s; + union { + i64 number; + s8 string; + }; + s8 save; + MetaLocation loc; +} MetaExpansionParser; + +#define meta_expansion_save(v) (v)->save = (v)->s +#define meta_expansion_restore(v) swap((v)->s, (v)->save) +#define meta_expansion_commit(v) meta_expansion_restore(v) + +#define meta_expansion_expected(loc, e, g) \ + meta_compiler_error(loc, "invalid expansion string: expected %.*s after %.*s\n", \ + (i32)meta_expansion_token_strings[e].len, meta_expansion_token_strings[e].data, \ + (i32)meta_expansion_token_strings[g].len, meta_expansion_token_strings[g].data) + +function s8 +meta_expansion_extract_string(MetaExpansionParser *p) +{ + s8 result = {.data = p->s.data}; + for (; result.len < p->s.len; result.len++) { + b32 done = 0; + switch (p->s.data[result.len]) { + #define X(t, ...) case t: + META_EXPANSION_TOKEN_LIST + #undef X + case ' ': + {done = 1;}break; + default:{}break; + } + if (done) break; + } + p->s.data += result.len; + p->s.len -= result.len; + return result; +} + +function MetaExpansionToken +meta_expansion_token(MetaExpansionParser *p) +{ + MetaExpansionToken result = MetaExpansionToken_EOF; + meta_expansion_save(p); + if (p->s.len > 0) { + b32 chop = 1; + switch (p->s.data[0]) { + #define X(t, kind, ...) case t:{ result = MetaExpansionToken_## kind; }break; + META_EXPANSION_TOKEN_LIST + #undef X + default:{ + chop = 0; + if (BETWEEN(p->s.data[0], '0', '9')) result = MetaExpansionToken_Number; + else result = MetaExpansionToken_Identifier; + }break; + } + if (chop) { + s8_chop(&p->s, 1); + p->s = s8_trim(p->s); + } + + switch (result) { + case MetaExpansionToken_Number:{ + IntegerConversion integer = integer_from_s8(p->s); + if (integer.result != IntegerConversionResult_Success) { + /* TODO(rnp): point at start */ + meta_compiler_error(p->loc, "invalid integer in expansion string\n"); + } + p->number = integer.S64; + p->s = integer.unparsed; + }break; + case MetaExpansionToken_Identifier:{ p->string = meta_expansion_extract_string(p); }break; + default:{}break; + } + p->s = s8_trim(p->s); + } + return result; +} + +function MetaExpansionPart * +meta_expansion_start_conditional(MetaContext *ctx, Arena *arena, MetaExpansionPartList *ops, + MetaExpansionParser *p, MetaExpansionToken token, b32 negate) +{ + MetaExpansionPart *result = meta_push_expansion_part(ctx, arena, ops, MetaExpansionPartKind_Conditional, + s8(""), 0, p->loc); + switch (token) { + case MetaExpansionToken_Number:{ + result->conditional.lhs.kind = MetaExpansionConditionalArgumentKind_Number; + result->conditional.lhs.number = negate ? -p->number : p->number; }break; + default:{}break; } return result; } +function void +meta_expansion_end_conditional(MetaExpansionPart *ep, MetaExpansionParser *p, MetaExpansionToken token, b32 negate) +{ + if (ep->conditional.rhs.kind != MetaExpansionConditionalArgumentKind_Invalid) { + meta_compiler_error(p->loc, "invalid expansion conditional: duplicate right hand expression: '%.*s'\n", + (i32)p->save.len, p->save.data); + } + switch (token) { + case MetaExpansionToken_Number:{ + ep->conditional.rhs.kind = MetaExpansionConditionalArgumentKind_Number; + ep->conditional.rhs.number = negate ? -p->number : p->number; + }break; + default:{}break; + } +} + function MetaExpansionPartList meta_generate_expansion_set(MetaContext *ctx, Arena *arena, s8 expansion_string, MetaTable *t, MetaLocation loc) { @@ -1923,14 +2127,118 @@ meta_generate_expansion_set(MetaContext *ctx, Arena *arena, s8 expansion_string, meta_expansion_string_split(remainder, &left, &inner, &remainder, loc); if (left.len) meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_String, left, t, loc); if (inner.len) { - s8 test = inner; - s8 opcode = s8_chop(&test, 1); - if (test.len == 0 && opcode.data[0] == '|') { - meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_Alignment, inner, t, loc); - } else if (test.len && opcode.data[0] == '%') { - meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_EvalKind, test, t, loc); - } else { - meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_Reference, inner, t, loc); + MetaExpansionParser p[1] = {{.s = inner, .loc = loc}}; + + MetaExpansionPart *test_part = 0; + b32 count_test_parts = 0; + + for (MetaExpansionToken token = meta_expansion_token(p); + token != MetaExpansionToken_EOF; + token = meta_expansion_token(p)) + { + if (count_test_parts) test_part->conditional.instruction_skip++; + switch (token) { + case MetaExpansionToken_Alignment:{ + meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_Alignment, p->s, t, loc); + }break; + + case MetaExpansionToken_Identifier:{ + meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_Reference, p->string, t, loc); + }break; + + case MetaExpansionToken_TypeEval: + case MetaExpansionToken_TypeEvalElements: + { + if (meta_expansion_token(p) != MetaExpansionToken_Identifier) { + loc.column += p->save.data - expansion_string.data; + meta_expansion_expected(loc, MetaExpansionToken_Identifier, token); + } + MetaExpansionPartKind kind = token == MetaExpansionToken_TypeEval ? + MetaExpansionPartKind_EvalKind : + MetaExpansionPartKind_EvalKindCount; + meta_push_expansion_part(ctx, arena, &result, kind, p->string, t, loc); + }break; + + case MetaExpansionToken_Quote:{ + u8 *point = p->s.data; + s8 string = meta_expansion_extract_string(p); + token = meta_expansion_token(p); + if (token != MetaExpansionToken_Quote) { + loc.column += point - expansion_string.data; + /* TODO(rnp): point at start */ + meta_compiler_error(loc, "unterminated string in expansion\n"); + } + meta_push_expansion_part(ctx, arena, &result, MetaExpansionPartKind_String, string, t, loc); + }break; + + case MetaExpansionToken_Dash:{ + token = meta_expansion_token(p); + switch (token) { + case MetaExpansionToken_GreaterThan:{ + if (!test_part) goto error; + if (test_part->conditional.lhs.kind == MetaExpansionConditionalArgumentKind_Invalid || + test_part->conditional.rhs.kind == MetaExpansionConditionalArgumentKind_Invalid) + { + b32 lhs = test_part->conditional.lhs.kind == MetaExpansionConditionalArgumentKind_Invalid; + b32 rhs = test_part->conditional.rhs.kind == MetaExpansionConditionalArgumentKind_Invalid; + if (lhs && rhs) + meta_compiler_error(loc, "expansion string test terminated without arguments\n"); + meta_compiler_error(loc, "expansion string test terminated without %s argument\n", + lhs? "left" : "right"); + } + count_test_parts = 1; + }break; + case MetaExpansionToken_Number:{ + if (test_part) meta_expansion_end_conditional(test_part, p, token, 1); + else test_part = meta_expansion_start_conditional(ctx, arena, &result, p, token, 1); + }break; + default:{ goto error; }break; + } + }break; + + case MetaExpansionToken_Number:{ + if (test_part) meta_expansion_end_conditional(test_part, p, token, 0); + else test_part = meta_expansion_start_conditional(ctx, arena, &result, p, token, 0); + }break; + + case MetaExpansionToken_GreaterThan: + case MetaExpansionToken_LessThan: + { + if (test_part && test_part->conditional.op != MetaExpansionOperation_Invalid) goto error; + if (!test_part) { + if (result.count == 0) { + meta_compiler_error(p->loc, "invalid expansion conditional: missing left hand side\n"); + } + + s8 *strings = result.data[result.count - 1].strings; + MetaExpansionPartKind last_kind = result.data[result.count - 1].kind; + if (last_kind != MetaExpansionPartKind_EvalKindCount && + last_kind != MetaExpansionPartKind_Reference) + { + meta_compiler_error(p->loc, "invalid expansion conditional: left hand side not numeric\n"); + } + result.count--; + test_part = meta_expansion_start_conditional(ctx, arena, &result, p, token, 0); + if (last_kind == MetaExpansionPartKind_EvalKindCount) { + test_part->conditional.lhs.kind = MetaExpansionConditionalArgumentKind_Evaluation; + } else { + test_part->conditional.lhs.kind = MetaExpansionConditionalArgumentKind_Reference; + } + test_part->conditional.lhs.strings = strings; + } + test_part->conditional.op = token == MetaExpansionToken_LessThan ? + MetaExpansionOperation_LessThan : + MetaExpansionOperation_GreaterThan; + }break; + + error: + default: + { + meta_compiler_error(loc, "invalid nested %.*s in expansion string\n", + (i32)meta_expansion_token_strings[token].len, + meta_expansion_token_strings[token].data); + }break; + } } } } while (remainder.len); @@ -2005,7 +2313,7 @@ meta_embed(MetaContext *ctx, Arena scratch, MetaEntry *e, iz entry_count) meta_entry_argument_expected(e, s8("filename")); s8 filename = meta_entry_argument_expect(e, 0, MetaEntryArgumentKind_String).string; - MetaEmitOperationList *ops = da_push(ctx->arena, &ctx->emit_sets); + MetaEmitOperationList *ops = da_push(ctx->arena, ctx->emit_sets + MetaEmitLang_C); if (e->name.len == 0) meta_entry_error(e, "name must be provided for output array"); MetaEmitOperation *op; @@ -2022,12 +2330,52 @@ meta_embed(MetaContext *ctx, Arena scratch, MetaEntry *e, iz entry_count) op->string = s8("};"); } +function MetaKind +meta_map_kind(s8 kind, s8 table_name, MetaLocation location) +{ + iz id = meta_lookup_string_slow(meta_kind_meta_types, MetaKind_Count, kind); + if (id < 0) { + meta_compiler_error(location, "Invalid Kind in '%.*s' table expansion: %.*s\n", + (i32)table_name.len, table_name.data, (i32)kind.len, kind.data); + } + MetaKind result = (MetaKind)id; + return result; +} + +function MetaEmitLang +meta_map_emit_lang(s8 lang, MetaEntry *e) +{ + #define X(k, ...) s8_comp(#k), + read_only local_persist s8 meta_lang_strings[] = {META_EMIT_LANG_LIST}; + #undef X + + iz id = meta_lookup_string_slow(meta_lang_strings, MetaEmitLang_Count, lang); + if (id < 0) { + #define X(k, ...) #k ", " + meta_entry_error(e, "Unknown Emit Language: '%.*s'\nPossible Values: " + META_EMIT_LANG_LIST "\n", (i32)lang.len, lang.data); + #undef X + } + MetaEmitLang result = (MetaEmitLang)id; + return result; +} + function iz meta_pack_emit(MetaContext *ctx, Arena scratch, MetaEntry *e, iz entry_count) { assert(e->kind == MetaEntryKind_Emit); - MetaEmitOperationList *ops = da_push(ctx->arena, &ctx->emit_sets); + MetaEmitLang lang = MetaEmitLang_C; + if (e->argument_count) { + meta_entry_argument_expected(e, s8("emit_language")); + s8 name = meta_entry_argument_expect(e, 0, MetaEntryArgumentKind_String).string; + lang = meta_map_emit_lang(name, e); + } + + MetaEmitOperationList *ops = da_push(ctx->arena, ctx->emit_sets + lang); + /* TODO(rnp): probably we should check this is unique */ + ops->filename = e->name; + MetaEntryScope scope = meta_entry_extract_scope(e, entry_count); for (MetaEntry *row = scope.start; row != scope.one_past_last; row++) { switch (row->kind) { @@ -2129,8 +2477,8 @@ function CommandList meta_extract_emit_file_dependencies(MetaContext *ctx, Arena *arena) { CommandList result = {0}; - for (iz set = 0; set < ctx->emit_sets.count; set++) { - MetaEmitOperationList *ops = ctx->emit_sets.data + set; + for (iz set = 0; set < ctx->emit_sets[MetaEmitLang_C].count; set++) { + MetaEmitOperationList *ops = ctx->emit_sets[MetaEmitLang_C].data + set; for (iz opcode = 0; opcode < ops->count; opcode++) { MetaEmitOperation *op = ops->data + opcode; switch (op->kind) { @@ -2145,18 +2493,6 @@ meta_extract_emit_file_dependencies(MetaContext *ctx, Arena *arena) return result; } -function MetaKind -meta_map_kind(s8 kind, s8 table_name, MetaLocation location) -{ - iz id = meta_lookup_string_slow(meta_kind_meta_types, MetaKind_Count, kind); - if (id < 0) { - meta_compiler_error(location, "Invalid Kind in '%.*s' table expansion: %.*s\n", - (i32)table_name.len, table_name.data, (i32)kind.len, kind.data); - } - MetaKind result = (MetaKind)id; - return result; -} - function void metagen_push_byte_array(MetaprogramContext *m, s8 bytes) { @@ -2199,90 +2535,159 @@ metagen_push_table(MetaprogramContext *m, Arena scratch, s8 row_start, s8 row_en } } -function void -metagen_run_emit(MetaprogramContext *m, MetaContext *ctx, s8 *evaluation_table) +function i64 +meta_expansion_part_conditional_argument(MetaExpansionConditionalArgument a, u32 entry, + s8 table_name, MetaLocation loc) { - for (iz set = 0; set < ctx->emit_sets.count; set++) { - MetaEmitOperationList *ops = ctx->emit_sets.data + set; - for (iz opcode = 0; opcode < ops->count; opcode++) { - MetaEmitOperation *op = ops->data + opcode; - switch (op->kind) { - case MetaEmitOperationKind_String:{ meta_push_line(m, op->string); }break; - case MetaEmitOperationKind_FileBytes:{ - Arena scratch = m->scratch; - s8 filename = push_s8_from_parts(&scratch, s8(OS_PATH_SEPARATOR), ctx->directory, op->string); - s8 file = os_read_whole_file(&scratch, (c8 *)filename.data); - m->indentation_level++; - metagen_push_byte_array(m, file); - m->indentation_level--; - }break; - case MetaEmitOperationKind_Expand:{ - Arena scratch = m->scratch; + i64 result = 0; + switch (a.kind) { + case MetaExpansionConditionalArgumentKind_Number:{ + result = a.number; + }break; + + case MetaExpansionConditionalArgumentKind_Evaluation: + { + s8 string = a.strings[entry]; + MetaKind kind = meta_map_kind(string, table_name, loc); + result = meta_kind_elements[kind]; + }break; + + case MetaExpansionConditionalArgumentKind_Reference:{ + s8 string = a.strings[entry]; + IntegerConversion integer = integer_from_s8(string); + if (integer.result != IntegerConversionResult_Success) { + meta_compiler_error(loc, "Invalid integer in '%.*s' table expansion: %.*s\n", + (i32)table_name.len, table_name.data, (i32)string.len, string.data); + } + result = integer.S64; + }break; - MetaEmitOperationExpansion *eop = &op->expansion_operation; - MetaTable *t = ctx->tables.data + eop->table_id; - s8 table_name = ctx->table_names.data[t->table_name_id]; + InvalidDefaultCase; + } - u32 alignment_count = 1; - u32 evaluation_count = 0; - for (u32 part = 0; part < eop->part_count; part++) { - if (eop->parts[part].kind == MetaExpansionPartKind_Alignment) - alignment_count++; - if (eop->parts[part].kind == MetaExpansionPartKind_EvalKind) - evaluation_count++; + return result; +} + +function b32 +meta_expansion_part_conditional(MetaExpansionPart *p, u32 entry, s8 table_name, MetaLocation loc) +{ + assert(p->kind == MetaExpansionPartKind_Conditional); + b32 result = 0; + i64 lhs = meta_expansion_part_conditional_argument(p->conditional.lhs, entry, table_name, loc); + i64 rhs = meta_expansion_part_conditional_argument(p->conditional.rhs, entry, table_name, loc); + switch (p->conditional.op) { + case MetaExpansionOperation_LessThan:{ result = lhs < rhs; }break; + case MetaExpansionOperation_GreaterThan:{ result = lhs > rhs; }break; + InvalidDefaultCase; + } + return result; +} + +function void +metagen_run_emit(MetaprogramContext *m, MetaContext *ctx, MetaEmitOperationList *ops, + s8 *evaluation_table) +{ + for (iz opcode = 0; opcode < ops->count; opcode++) { + MetaEmitOperation *op = ops->data + opcode; + switch (op->kind) { + case MetaEmitOperationKind_String:{ meta_push_line(m, op->string); }break; + case MetaEmitOperationKind_FileBytes:{ + Arena scratch = m->scratch; + s8 filename = push_s8_from_parts(&scratch, s8(OS_PATH_SEPARATOR), ctx->directory, op->string); + s8 file = os_read_whole_file(&scratch, (c8 *)filename.data); + m->indentation_level++; + metagen_push_byte_array(m, file); + m->indentation_level--; + }break; + case MetaEmitOperationKind_Expand:{ + Arena scratch = m->scratch; + + MetaEmitOperationExpansion *eop = &op->expansion_operation; + MetaTable *t = ctx->tables.data + eop->table_id; + s8 table_name = ctx->table_names.data[t->table_name_id]; + + u32 alignment_count = 1; + u32 evaluation_count = 0; + for (u32 part = 0; part < eop->part_count; part++) { + if (eop->parts[part].kind == MetaExpansionPartKind_Alignment) + alignment_count++; + if (eop->parts[part].kind == MetaExpansionPartKind_EvalKind || + eop->parts[part].kind == MetaExpansionPartKind_EvalKindCount) + evaluation_count++; + } + + MetaKind **evaluation_columns = push_array(&scratch, MetaKind *, evaluation_count); + for (u32 column = 0; column < evaluation_count; column++) + evaluation_columns[column] = push_array(&scratch, MetaKind, t->entry_count); + + for (u32 part = 0; part < eop->part_count; part++) { + u32 eval_column = 0; + MetaExpansionPart *p = eop->parts + part; + if (p->kind == MetaExpansionPartKind_EvalKind) { + for (u32 entry = 0; entry < t->entry_count; entry++) { + evaluation_columns[eval_column][entry] = meta_map_kind(p->strings[entry], + table_name, op->location); + } + eval_column++; } + } - MetaKind **evaluation_columns = push_array(&scratch, MetaKind *, evaluation_count); - for (u32 column = 0; column < evaluation_count; column++) - evaluation_columns[column] = push_array(&scratch, MetaKind, t->entry_count); + s8 **columns = push_array(&scratch, s8 *, alignment_count); + for (u32 column = 0; column < alignment_count; column++) + columns[column] = push_array(&scratch, s8, t->entry_count); + Stream sb = arena_stream(scratch); + for (u32 entry = 0; entry < t->entry_count; entry++) { + u32 column = 0; + u32 eval_column = 0; for (u32 part = 0; part < eop->part_count; part++) { - u32 eval_column = 0; MetaExpansionPart *p = eop->parts + part; - if (p->kind == MetaExpansionPartKind_EvalKind) { - for (u32 entry = 0; entry < t->entry_count; entry++) { - evaluation_columns[eval_column][entry] = meta_map_kind(p->strings[entry], - table_name, op->location); - } - eval_column++; + switch (p->kind) { + case MetaExpansionPartKind_Alignment:{ + columns[column][entry] = arena_stream_commit_and_reset(&scratch, &sb); + column++; + }break; + + case MetaExpansionPartKind_Conditional:{ + if (!meta_expansion_part_conditional(p, entry, table_name, op->location)) + part += p->conditional.instruction_skip; + }break; + + case MetaExpansionPartKind_EvalKind:{ + s8 kind = evaluation_table[evaluation_columns[eval_column][entry]]; + stream_append_s8(&sb, kind); + }break; + + case MetaExpansionPartKind_EvalKindCount:{ + stream_append_u64(&sb, meta_kind_elements[evaluation_columns[eval_column][entry]]); + }break; + + case MetaExpansionPartKind_Reference: + case MetaExpansionPartKind_String: + { + s8 string = p->kind == MetaExpansionPartKind_Reference ? p->strings[entry] : p->string; + stream_append_s8(&sb, string); + }break; } } - s8 **columns = push_array(&scratch, s8 *, alignment_count); - for (u32 column = 0; column < alignment_count; column++) - columns[column] = push_array(&scratch, s8, t->entry_count); - - Stream sb = arena_stream(scratch); - for (u32 entry = 0; entry < t->entry_count; entry++) { - u32 column = 0; - u32 eval_column = 0; - for (u32 part = 0; part < eop->part_count; part++) { - MetaExpansionPart *p = eop->parts + part; - switch (p->kind) { - case MetaExpansionPartKind_Alignment:{ - columns[column][entry] = arena_stream_commit_and_reset(&scratch, &sb); - column++; - }break; - case MetaExpansionPartKind_EvalKind:{ - s8 kind = evaluation_table[evaluation_columns[eval_column][entry]]; - stream_append_s8(&sb, kind); - }break; - case MetaExpansionPartKind_Reference: - case MetaExpansionPartKind_String: - { - u32 index = p->kind == MetaExpansionPartKind_Reference ? entry : 0; - stream_append_s8(&sb, p->strings[index]); - }break; - } - } - columns[column][entry] = arena_stream_commit_and_reset(&scratch, &sb); - } - metagen_push_table(m, scratch, s8(""), s8(""), columns, t->entry_count, alignment_count); - }break; - InvalidDefaultCase; + columns[column][entry] = arena_stream_commit_and_reset(&scratch, &sb); } + metagen_push_table(m, scratch, s8(""), s8(""), columns, t->entry_count, alignment_count); + }break; + InvalidDefaultCase; } - meta_end_line(m); + } + meta_end_line(m); +} + +function void +metagen_run_emit_set(MetaprogramContext *m, MetaContext *ctx, MetaEmitOperationListSet *emit_set, + s8 *evaluation_table) +{ + for (iz set = 0; set < emit_set->count; set++) { + MetaEmitOperationList *ops = emit_set->data + set; + metagen_run_emit(m, ctx, ops, evaluation_table); } } @@ -2603,7 +3008,7 @@ metagen_emit_c_code(MetaContext *ctx, Arena arena) metagen_push_table(m, m->scratch, s8(""), s8(";"), (s8 *[]){types, names}, countof(names), 2); } meta_end_scope(m, s8("} BeamformerShaderBakeParameters;\n")); - metagen_run_emit(m, ctx, meta_kind_c_types); + metagen_run_emit_set(m, ctx, ctx->emit_sets + MetaEmitLang_C, meta_kind_c_types); ///////////////////////////////// // NOTE(rnp): shader info tables @@ -2798,7 +3203,9 @@ metagen_matlab_union(MetaprogramContext *m, MetaContext *ctx, MetaMUnion *mu, s8 { u32 name_index = table_matches[member].name_index; for (u32 prop = 0; prop < t->entry_count; prop++) { - meta_push_line(m, t->entries[name_index][prop], s8("(1,1) "), meta_kind_matlab_types[kinds[prop]]); + meta_begin_line(m, t->entries[name_index][prop], s8("(1,")); + meta_push_u64(m, meta_kind_elements[kinds[prop]]); + meta_end_line(m, s8(") "), meta_kind_matlab_types[kinds[prop]]); } } meta_end_scope(m, s8("end")); } meta_end_scope(m, s8("end")); @@ -2838,31 +3245,6 @@ metagen_emit_matlab_code(MetaContext *ctx, Arena arena) result &= meta_end_and_write_matlab(m, OUTPUT("matlab/OGLBeamformerLiveFeedbackFlags.m")); #undef X - #define X(name, __t, __s, kind, elements, ...) meta_push_matlab_property(m, s8(#name), (u64)elements, s8(#kind)); - meta_begin_matlab_class(m, "OGLBeamformerParameters"); - meta_begin_scope(m, s8("properties")); - BEAMFORMER_PARAMS_HEAD - BEAMFORMER_UI_PARAMS - result &= meta_end_and_write_matlab(m, OUTPUT("matlab/OGLBeamformerParameters.m")); - - meta_begin_matlab_class(m, "OGLBeamformerParametersHead"); - meta_begin_scope(m, s8("properties")); - BEAMFORMER_PARAMS_HEAD - result &= meta_end_and_write_matlab(m, OUTPUT("matlab/OGLBeamformerParametersHead.m")); - - meta_begin_matlab_class(m, "OGLBeamformerParametersUI"); - meta_begin_scope(m, s8("properties")); - BEAMFORMER_UI_PARAMS - result &= meta_end_and_write_matlab(m, OUTPUT("matlab/OGLBeamformerParametersUI.m")); - - meta_begin_matlab_class(m, "OGLBeamformerSimpleParameters"); - meta_begin_scope(m, s8("properties")); - BEAMFORMER_PARAMS_HEAD - BEAMFORMER_UI_PARAMS - BEAMFORMER_SIMPLE_PARAMS - result &= meta_end_and_write_matlab(m, OUTPUT("matlab/OGLBeamformerSimpleParameters.m")); - #undef X - #define X(name, __t, __s, elements, ...) meta_push_matlab_property(m, s8(#name), elements, s8("")); meta_begin_matlab_class(m, "OGLBeamformerLiveImagingParameters"); meta_begin_scope(m, s8("properties")); @@ -2905,6 +3287,22 @@ metagen_emit_matlab_code(MetaContext *ctx, Arena arena) result &= meta_end_and_write_matlab(m, (c8 *)output.data); } + //////////////////// + // NOTE: emit files + { + MetaEmitOperationListSet *emit_set = ctx->emit_sets + MetaEmitLang_MATLAB; + for (u32 list = 0; list < emit_set->count; list++) { + MetaEmitOperationList *ops = emit_set->data + list; + Arena scratch = m->scratch; + s8 output = push_s8_from_parts(&m->scratch, s8(""), + s8(OUTPUT("matlab") OS_PATH_SEPARATOR "OGLBeamformer"), + ops->filename, s8(".m")); + meta_push_line(m, s8("% GENERATED CODE")); + metagen_run_emit(m, ctx, ops, meta_kind_matlab_types); + result &= meta_write_and_reset(m, (c8 *)output.data); + m->scratch = scratch; + } + } ////////////////// // NOTE: MUnions @@ -2939,6 +3337,7 @@ metagen_emit_helper_library_header(MetaContext *ctx, Arena arena) meta_push_line(m, s8("/* See LICENSE for license details. */\n")); meta_push_line(m, s8("// GENERATED CODE\n")); + meta_push_line(m, s8("#include <stdint.h>\n")); { iz index = meta_lookup_string_slow(ctx->enumeration_kinds.data, ctx->enumeration_kinds.count, @@ -2971,6 +3370,8 @@ metagen_emit_helper_library_header(MetaContext *ctx, Arena arena) } } + metagen_run_emit_set(m, ctx, ctx->emit_sets + MetaEmitLang_CLibrary, meta_kind_base_c_types); + meta_push(m, parameters_header, base_header); result &= meta_write_and_reset(m, out); @@ -3097,7 +3498,7 @@ metagen_file_direct(Arena arena, char *filename) if (needs_rebuild_(out, deps.data, deps.count)) { build_log_generate(out); meta_push(m, c_file_header); - metagen_run_emit(m, ctx, meta_kind_c_types); + metagen_run_emit_set(m, ctx, ctx->emit_sets + MetaEmitLang_C, meta_kind_c_types); result &= meta_write_and_reset(m, out); } diff --git a/generated/beamformer.meta.c b/generated/beamformer.meta.c @@ -157,6 +157,105 @@ typedef struct { } BeamformerShaderBakeParameters; typedef struct { + m4 xdc_transform; + v2 xdc_element_pitch; + uv2 raw_data_dimensions; + v2 focal_vector; + u32 transmit_receive_orientation; + u32 sample_count; + u32 channel_count; + u32 acquisition_count; + u32 das_shader_id; + f32 time_offset; + u8 single_focus; + u8 single_orientation; + u8 decode_mode; + u8 sampling_mode; + v3 output_min_coordinate; + v3 output_max_coordinate; + iv4 output_points; + f32 sampling_frequency; + f32 demodulation_frequency; + f32 speed_of_sound; + f32 f_number; + f32 off_axis_pos; + u32 interpolation_mode; + u32 coherency_weighting; + u32 beamform_plane; + u32 decimation_rate; +} BeamformerParameters; + +typedef struct { + m4 xdc_transform; + v2 xdc_element_pitch; + uv2 raw_data_dimensions; + v2 focal_vector; + u32 transmit_receive_orientation; + u32 sample_count; + u32 channel_count; + u32 acquisition_count; + u32 das_shader_id; + f32 time_offset; + u8 single_focus; + u8 single_orientation; + u8 decode_mode; + u8 sampling_mode; +} BeamformerParametersHead; + +typedef struct { + v3 output_min_coordinate; + v3 output_max_coordinate; + iv4 output_points; + f32 sampling_frequency; + f32 demodulation_frequency; + f32 speed_of_sound; + f32 f_number; + f32 off_axis_pos; + u32 interpolation_mode; + u32 coherency_weighting; + u32 beamform_plane; + u32 decimation_rate; +} BeamformerUIParameters; + +typedef struct { + m4 xdc_transform; + v2 xdc_element_pitch; + uv2 raw_data_dimensions; + v2 focal_vector; + u32 transmit_receive_orientation; + u32 sample_count; + u32 channel_count; + u32 acquisition_count; + u32 das_shader_id; + f32 time_offset; + u8 single_focus; + u8 single_orientation; + u8 decode_mode; + u8 sampling_mode; + v3 output_min_coordinate; + v3 output_max_coordinate; + iv4 output_points; + f32 sampling_frequency; + f32 demodulation_frequency; + f32 speed_of_sound; + f32 f_number; + f32 off_axis_pos; + u32 interpolation_mode; + u32 coherency_weighting; + u32 beamform_plane; + u32 decimation_rate; + i16 channel_mapping[256]; + i16 sparse_elements[256]; + u8 transmit_receive_orientations[256]; + f32 steering_angles[256]; + f32 focal_depths[256]; + i32 compute_stages[16]; + i32 compute_stage_parameters[16]; + u32 compute_stages_count; + i32 data_kind; +} BeamformerSimpleParameters; + +typedef struct { BeamformerEmissionKind kind; union { struct { diff --git a/helpers/ogl_beamformer_lib.c b/helpers/ogl_beamformer_lib.c @@ -404,7 +404,7 @@ beamformer_push_data_base(void *data, u32 data_size, i32 timeout_ms, u32 block) BeamformerDataKind data_kind = b->pipeline.data_kind; u32 size = bp->acquisition_count * bp->sample_count * bp->channel_count * beamformer_data_kind_byte_size[data_kind]; - u32 raw_size = bp->raw_data_dimensions[0] * bp->raw_data_dimensions[1] * beamformer_data_kind_byte_size[data_kind]; + u32 raw_size = bp->raw_data_dimensions.x * bp->raw_data_dimensions.y * beamformer_data_kind_byte_size[data_kind]; if (lib_error_check(size <= arena_capacity(&scratch, u8), BF_LIB_ERR_KIND_BUFFER_OVERFLOW) && lib_error_check(size <= data_size && data_size == raw_size, BF_LIB_ERR_KIND_DATA_SIZE_MISMATCH)) @@ -413,7 +413,7 @@ beamformer_push_data_base(void *data, u32 data_size, i32 timeout_ms, u32 block) if (lib_try_lock(BeamformerSharedMemoryLockKind_ScratchSpace, 0)) { u32 channel_count = bp->channel_count; u32 out_channel_stride = beamformer_data_kind_element_count[data_kind] * bp->sample_count * bp->acquisition_count; - u32 in_channel_stride = beamformer_data_kind_element_count[data_kind] * bp->raw_data_dimensions[0]; + u32 in_channel_stride = beamformer_data_kind_element_count[data_kind] * bp->raw_data_dimensions.x; for (u32 channel = 0; channel < channel_count; channel++) { u16 data_channel = (u16)b->channel_mapping[channel]; @@ -581,9 +581,9 @@ beamformer_beamform_data(BeamformerSimpleParameters *bp, void *data, uint32_t da { b32 result = validate_simple_parameters(bp); if (result) { - bp->output_points[0] = MAX(1, bp->output_points[0]); - bp->output_points[1] = MAX(1, bp->output_points[1]); - bp->output_points[2] = MAX(1, bp->output_points[2]); + bp->output_points.E[0] = MAX(1, bp->output_points.E[0]); + bp->output_points.E[1] = MAX(1, bp->output_points.E[1]); + bp->output_points.E[2] = MAX(1, bp->output_points.E[2]); beamformer_push_simple_parameters(bp); @@ -593,7 +593,7 @@ beamformer_beamform_data(BeamformerSimpleParameters *bp, void *data, uint32_t da complex |= shader == BeamformerShaderKind_Demodulate || shader == BeamformerShaderKind_CudaHilbert; } - iz output_size = bp->output_points[0] * bp->output_points[1] * bp->output_points[2] * (i32)sizeof(f32); + iz output_size = bp->output_points.x * bp->output_points.y * bp->output_points.z * (i32)sizeof(f32); if (complex) output_size *= 2; Arena scratch = beamformer_shared_memory_scratch_arena(g_beamformer_library_context.bp); diff --git a/math.c b/math.c @@ -253,16 +253,6 @@ v3_from_iv3(iv3 v) } function v3 -v3_from_f32_array(f32 v[3]) -{ - v3 result; - result.E[0] = v[0]; - result.E[1] = v[1]; - result.E[2] = v[2]; - return result; -} - -function v3 v3_abs(v3 a) { v3 result; diff --git a/tests/throughput.c b/tests/throughput.c @@ -14,7 +14,7 @@ #include <stdlib.h> #include <zstd.h> -global i32 g_output_points[4] = {512, 1, 1024, 1}; +global iv3 g_output_points = {{512, 1, 1024}}; global v2 g_axial_extent = {{ 10e-3f, 165e-3f}}; global v2 g_lateral_extent = {{-60e-3f, 60e-3f}}; global f32 g_f_number = 0.5f; @@ -188,9 +188,9 @@ read_zemp_bp_v1(u8 *path) function void beamformer_parameters_from_zemp_bp_v1(zemp_bp_v1 *zbp, BeamformerParameters *out) { - mem_copy(out->xdc_transform, zbp->xdc_transform, sizeof(out->xdc_transform)); - mem_copy(out->xdc_element_pitch, zbp->xdc_element_pitch, sizeof(out->xdc_element_pitch)); - mem_copy(out->raw_data_dimensions, zbp->raw_data_dim, sizeof(out->raw_data_dimensions)); + mem_copy(out->xdc_transform.E, zbp->xdc_transform, sizeof(out->xdc_transform)); + mem_copy(out->xdc_element_pitch.E, zbp->xdc_element_pitch, sizeof(out->xdc_element_pitch)); + mem_copy(out->raw_data_dimensions.E, zbp->raw_data_dim, sizeof(out->raw_data_dimensions)); out->sample_count = zbp->decoded_data_dim[0]; out->channel_count = zbp->decoded_data_dim[1]; @@ -281,7 +281,7 @@ decompress_data_at_work_index(Stream *path_base, u32 index) function b32 send_frame(i16 *restrict i16_data, BeamformerParameters *restrict bp) { - u32 data_size = bp->raw_data_dimensions[0] * bp->raw_data_dimensions[1] * sizeof(i16); + u32 data_size = bp->raw_data_dimensions.E[0] * bp->raw_data_dimensions.E[1] * sizeof(i16); b32 result = beamformer_push_data_with_compute(i16_data, data_size, BeamformerViewPlaneTag_XZ, 0); if (!result && !g_should_exit) printf("lib error: %s\n", beamformer_get_last_error_string()); @@ -307,16 +307,16 @@ execute_study(s8 study, Arena arena, Stream path, Options *options) BeamformerParameters bp = {0}; beamformer_parameters_from_zemp_bp_v1(zbp, &bp); - mem_copy(bp.output_points, g_output_points, sizeof(bp.output_points)); - bp.output_points[3] = 1; + bp.output_points.xyz = g_output_points; + bp.output_points.w = 1; - bp.output_min_coordinate[0] = g_lateral_extent.x; - bp.output_min_coordinate[1] = 0; - bp.output_min_coordinate[2] = g_axial_extent.x; + bp.output_min_coordinate.E[0] = g_lateral_extent.x; + bp.output_min_coordinate.E[1] = 0; + bp.output_min_coordinate.E[2] = g_axial_extent.x; - bp.output_max_coordinate[0] = g_lateral_extent.y; - bp.output_max_coordinate[1] = 0; - bp.output_max_coordinate[2] = g_axial_extent.y; + bp.output_max_coordinate.E[0] = g_lateral_extent.y; + bp.output_max_coordinate.E[1] = 0; + bp.output_max_coordinate.E[2] = g_axial_extent.y; bp.f_number = g_f_number; bp.beamform_plane = 0; @@ -373,8 +373,8 @@ execute_study(s8 study, Arena arena, Stream path, Options *options) bp.single_orientation = 1; bp.transmit_receive_orientation = packed_tx_rx; - bp.focal_vector[0] = zbp->transmit_angles[0]; - bp.focal_vector[1] = zbp->focal_depths[0]; + bp.focal_vector.E[0] = zbp->transmit_angles[0]; + bp.focal_vector.E[1] = zbp->focal_depths[0]; } else { alignas(64) v2 focal_vectors[BeamformerMaxChannelCount]; for (u32 i = 0; i < countof(focal_vectors); i++) @@ -412,7 +412,7 @@ execute_study(s8 study, Arena arena, Stream path, Options *options) u32 frame = 0; f32 times[32] = {0}; - f32 data_size = (f32)(bp.raw_data_dimensions[0] * bp.raw_data_dimensions[1] * sizeof(*data)); + f32 data_size = (f32)(bp.raw_data_dimensions.E[0] * bp.raw_data_dimensions.E[1] * sizeof(*data)); f64 start = os_get_time(); for (;!g_should_exit;) { if (send_frame(data, &bp)) { @@ -440,7 +440,7 @@ execute_study(s8 study, Arena arena, Stream path, Options *options) beamformer_set_live_parameters(&lip); } else { for (u32 i = 0; i < zbp->raw_data_dim[2]; i++) - send_frame(data + i * bp.raw_data_dimensions[0] * bp.raw_data_dimensions[1], &bp); + send_frame(data + i * bp.raw_data_dimensions.E[0] * bp.raw_data_dimensions.E[1], &bp); } free(zbp); diff --git a/ui.c b/ui.c @@ -1184,11 +1184,11 @@ add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx) VariableGroupKind_Vector, ui->font); { add_beamformer_variable(ui, group, &ui->arena, s8("Min:"), s8("[mm]"), - bp->output_min_coordinate + 0, v2_inf, 1e3f, 0.5e-3f, + bp->output_min_coordinate.E + 0, v2_inf, 1e3f, 0.5e-3f, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); add_beamformer_variable(ui, group, &ui->arena, s8("Max:"), s8("[mm]"), - bp->output_max_coordinate + 0, v2_inf, 1e3f, 0.5e-3f, + bp->output_max_coordinate.E + 0, v2_inf, 1e3f, 0.5e-3f, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); } group = end_variable_group(group); @@ -1197,11 +1197,11 @@ add_beamformer_parameters_view(Variable *parent, BeamformerCtx *ctx) VariableGroupKind_Vector, ui->font); { add_beamformer_variable(ui, group, &ui->arena, s8("Min:"), s8("[mm]"), - bp->output_min_coordinate + 2, v2_inf, 1e3f, 0.5e-3f, + bp->output_min_coordinate.E + 2, v2_inf, 1e3f, 0.5e-3f, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); add_beamformer_variable(ui, group, &ui->arena, s8("Max:"), s8("[mm]"), - bp->output_max_coordinate + 2, v2_inf, 1e3f, 0.5e-3f, + bp->output_max_coordinate.E + 2, v2_inf, 1e3f, 0.5e-3f, V_INPUT|V_TEXT|V_CAUSES_COMPUTE, ui->font); } group = end_variable_group(group); @@ -1285,10 +1285,10 @@ ui_beamformer_frame_view_convert(BeamformerUI *ui, Arena *arena, Variable *view, axial->zoom_starting_coord = F32_INFINITY; b32 copy = kind == BeamformerFrameViewKind_Copy; - lateral->min_value = copy ? &bv->min_coordinate.x : ui->params.output_min_coordinate + 0; - lateral->max_value = copy ? &bv->max_coordinate.x : ui->params.output_max_coordinate + 0; - axial->min_value = copy ? &bv->min_coordinate.z : ui->params.output_min_coordinate + 2; - axial->max_value = copy ? &bv->max_coordinate.z : ui->params.output_max_coordinate + 2; + lateral->min_value = copy ? &bv->min_coordinate.x : &ui->params.output_min_coordinate.x; + lateral->max_value = copy ? &bv->max_coordinate.x : &ui->params.output_max_coordinate.x; + axial->min_value = copy ? &bv->min_coordinate.z : &ui->params.output_min_coordinate.z; + axial->max_value = copy ? &bv->max_coordinate.z : &ui->params.output_max_coordinate.z; #define X(id, text) add_button(ui, menu, arena, s8(text), UI_BID_ ##id, 0, ui->small_font); FRAME_VIEW_BUTTONS @@ -1503,9 +1503,7 @@ beamformer_frame_view_plane_size(BeamformerUI *ui, BeamformerFrameView *view) { v3 result; if (view->kind == BeamformerFrameViewKind_3DXPlane) { - v3 min = v3_from_f32_array(ui->params.output_min_coordinate); - v3 max = v3_from_f32_array(ui->params.output_max_coordinate); - result = v3_sub(max, min); + result = v3_sub(ui->params.output_max_coordinate, ui->params.output_min_coordinate); swap(result.y, result.z); result.x = MAX(1e-3f, result.x); result.y = MAX(1e-3f, result.y); @@ -1538,8 +1536,8 @@ normalized_p_in_rect(Rect r, v2 p, b32 invert_y) function v3 x_plane_position(BeamformerUI *ui) { - f32 y_min = ui->params.output_min_coordinate[2]; - f32 y_max = ui->params.output_max_coordinate[2]; + f32 y_min = ui->params.output_min_coordinate.E[2]; + f32 y_max = ui->params.output_max_coordinate.E[2]; v3 result = {.y = y_min + (y_max - y_min) / 2}; return result; } @@ -1713,15 +1711,15 @@ view_update(BeamformerUI *ui, BeamformerFrameView *view) view->dirty |= view->frame != ui->latest_plane[index]; view->frame = ui->latest_plane[index]; if (view->dirty) { - view->min_coordinate = v3_from_f32_array(ui->params.output_min_coordinate); - view->max_coordinate = v3_from_f32_array(ui->params.output_max_coordinate); + view->min_coordinate = ui->params.output_min_coordinate; + view->max_coordinate = ui->params.output_max_coordinate; } } /* TODO(rnp): x-z or y-z */ /* TODO(rnp): add method of setting a target size in frame view */ iv2 current = view->texture_dim; - iv2 target = {.w = (i32)ui->params.output_points[0], .h = (i32)ui->params.output_points[2]}; + iv2 target = {.w = ui->params.output_points.E[0], .h = ui->params.output_points.E[2]}; if (view->kind != BeamformerFrameViewKind_Copy && view->kind != BeamformerFrameViewKind_3DXPlane && !iv2_equal(current, target) && !iv2_equal(target, (iv2){0})) @@ -3963,10 +3961,10 @@ ui_init(BeamformerCtx *ctx, Arena store) function void validate_ui_parameters(BeamformerUIParameters *p) { - if (p->output_min_coordinate[0] > p->output_max_coordinate[0]) - swap(p->output_min_coordinate[0], p->output_max_coordinate[0]); - if (p->output_min_coordinate[2] > p->output_max_coordinate[2]) - swap(p->output_min_coordinate[2], p->output_max_coordinate[2]); + if (p->output_min_coordinate.x > p->output_max_coordinate.x) + swap(p->output_min_coordinate.x, p->output_max_coordinate.x); + if (p->output_min_coordinate.z > p->output_max_coordinate.z) + swap(p->output_min_coordinate.z, p->output_max_coordinate.z); } function void diff --git a/util.c b/util.c @@ -655,32 +655,54 @@ cut_rect_vertical(Rect rect, f32 at, Rect *top, Rect *bot) } } -function f64 -parse_f64(s8 s) +function IntegerConversion +integer_from_s8(s8 raw) { - f64 integral = 0, fractional = 0, sign = 1; + IntegerConversion result = {0}; - if (s.len > 0 && *s.data == '-') { - sign = -1; - s.data++; - s.len--; + iz i = 0; + i64 scale = 1; + if (raw.len && raw.data[0] == '-') { + scale = -1; + i = 1; } - while (s.len > 0 && *s.data != '.') { - integral *= 10; - integral += *s.data - '0'; - s.data++; - s.len--; + for (; i < raw.len; i++) { + i64 digit = (i64)raw.data[i] - '0'; + if (BETWEEN(digit, 0, 9)) { + if (result.U64 > (U64_MAX - (u64)digit) / 10) { + result.result = IntegerConversionResult_OutOfRange; + result.U64 = U64_MAX; + } else { + result.U64 = 10 * result.U64 + (u64)digit; + } + } else { + break; + } } + result.unparsed = (s8){.len = raw.len - i, .data = raw.data + i}; + result.result = IntegerConversionResult_Success; + result.S64 = (i64)result.U64 * scale; + + return result; +} +function f64 +parse_f64(s8 s) +{ + IntegerConversion integral = integer_from_s8(s); + + s = integral.unparsed; if (*s.data == '.') { s.data++; s.len--; } + while (s.len > 0 && s.data[s.len - 1] == '0') s.len--; - while (s.len > 0) { - ASSERT(s.data[s.len - 1] != '.'); - fractional /= 10; - fractional += (f64)(s.data[--s.len] - '0') / 10.0; - } - f64 result = sign * (integral + fractional); + IntegerConversion fractional = integer_from_s8(s); + + u64 power = (u64)(fractional.unparsed.data - s.data); + f64 frac = (f64)fractional.U64; + while (power > 0) { frac /= 10.0; power--; } + + f64 result = (f64)integral.S64 + frac; return result; } diff --git a/util.h b/util.h @@ -151,6 +151,7 @@ #define I32_MAX (0x7FFFFFFFL) #define U16_MAX (0x0000FFFFUL) #define U32_MAX (0xFFFFFFFFUL) +#define U64_MAX (0xFFFFFFFFFFFFFFFFULL) #define F32_INFINITY (1e+300*1e+300) #define F32_EPSILON (1e-6f) #ifndef PI @@ -195,6 +196,21 @@ typedef struct { iz len; u16 *data; } s16; typedef struct { u32 cp, consumed; } UnicodeDecode; +typedef enum { + IntegerConversionResult_Invalid, + IntegerConversionResult_OutOfRange, + IntegerConversionResult_Success, +} IntegerConversionResult; + +typedef struct { + IntegerConversionResult result; + union { + u64 U64; + i64 S64; + }; + s8 unparsed; +} IntegerConversion; + /* NOTE: raylib stubs */ #ifndef RAYLIB_H typedef struct { f32 x, y; } Vector2; @@ -217,6 +233,12 @@ typedef union { } iv3; typedef union { + struct { i32 x, y, z, w; }; + struct { iv3 xyz; i32 _w; }; + i32 E[4]; +} iv4; + +typedef union { struct { u32 x, y; }; struct { u32 w, h; }; u32 E[2];