ogl_beamforming

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

beamformer_core.c (84732B)


      1 /* See LICENSE for license details. */
      2 /* TODO(rnp):
      3  * [ ]: backtrace dumping on SIGSEGV
      4  * [ ]: cooperative shared memory loading in decode shader
      5  * [ ]: refactor: save filter parameters with rest of parameters, whole slot thing is dumb
      6  * [ ]: upload previously exported data for display. maybe this is a UI thing but doing it
      7  *      programatically would be nice.
      8  * [ ]: Add interface for multi frame upload. RF upload already uses an offset into SM so
      9  *      that part works fine. We just need a way of specify a multi frame upload. (Data must
     10  *      be organized for simple offset access per frame).
     11  * [ ]: refactor: do_compute should build its own "command graph" which tracks
     12  *      dependencies better. It is very important that unnecessary barriers are
     13  *      not placed between compute stages which requires knowledge of the entire
     14  *      graph.
     15  * [ ]: refactor: replace UploadRF with just the scratch_rf_size variable,
     16  *      use below to spin wait in library
     17  * [ ]: utilize umonitor/umwait (intel), monitorx/mwaitx (amd), and wfe/sev (aarch64)
     18  *      for power efficient low latency waiting
     19  * [ ]: BeamformWorkQueue -> BeamformerWorkQueue
     20  * [ ]: refactor: work queue needs a cleanup, we should only have a single one
     21  *      - that queue isn't really considered hot so a lock is probably fine
     22  * [ ]: bug: reinit cuda on hot-reload
     23  *
     24  * [ ]: export for special frames/data
     25  *    - Color Map Data
     26  *    - Recursive Imaging Result
     27  *    - Incoherent sum
     28  *
     29  * [ ]: Tiled Array Handling
     30  *    [ ]: add tile count uv2
     31  *    [ ]: make xdc_transform an array
     32  *    [ ]: modify CPU DAS dispatch code to lookup xdc_transform based on tile index
     33  *    [ ]: modify CPU DAS dispatch code to set rf_element_offset based on tile index
     34  *       - need to check if this works for HERCULES or if the shader just needs to
     35  *         know about the extra tiles
     36  *    [ ]: write simple backpropagation code to optimize 2D tile position and 2D tilt
     37  *         (4 variables per tile).
     38  *       - two tests:
     39  *         1. Maximize value of wire target
     40  *         2. Maximize value of single cyst contrast
     41  *
     42  * [ ]: Recursive Imaging Handling
     43  *    [ ]: add array of image offsets to Compute Array Parameters
     44  *    [ ]: add array of weighting coeffiecents to Compute Array Parameters
     45  *    [ ]: Update 2D sum shader to use these
     46  *
     47  * [ ]: Power Doppler
     48  *    [ ]: also needs array of image offsets
     49  *    [ ]: make modified filter that grabs each sample from a different image offset
     50  *       - NOTE: this can't use existing striding mechanism because one image may
     51  *               be at the start of the ring buffer and another at the end of the
     52  *               ring buffer and the image size may not cleanly divide the ring buffer size.
     53  *       - Probably want this shader to just output the estimated power map directly.
     54  *    [ ]: make a modified render shader that takes two images: one structural and one that
     55  *         is used to index into a color map. (or second render pass that applies color overlay)
     56  */
     57 
     58 #include "base_platform.h"
     59 
     60 #if defined(BEAMFORMER_DEBUG) && !defined(BEAMFORMER_EXPORT) && OS_WINDOWS
     61   #define BEAMFORMER_EXPORT __declspec(dllexport)
     62 #endif
     63 
     64 #include "beamformer_internal.h"
     65 
     66 #define GPU_RESOURCE_HASH_TABLE_COUNT 256
     67 
     68 typedef struct GPUResource GPUResource;
     69 struct GPUResource {
     70 	str8 name;
     71 	u64  size;
     72 	u64  offset;
     73 	u64  alignment;
     74 
     75 	void *data;
     76 
     77 	u64  hash;
     78 
     79 	GPUResource *next;
     80 	GPUResource *hash_next, *hash_prev;
     81 };
     82 typedef struct {GPUResource *first, *last;} GPUResourceHashBucket;
     83 
     84 typedef struct {
     85 	Arena *arena;
     86 	u64    position;
     87 
     88 	GPUResource *resource_list;
     89 	GPUResourceHashBucket hash_table[GPU_RESOURCE_HASH_TABLE_COUNT];
     90 } GPUResourceBuilder;
     91 
     92 read_only BeamformerFrame       beamformer_nil_frame;
     93 read_only BeamformerComputePlan beamformer_nil_compute_plan;
     94 
     95 global BeamformerCtx   *beamformer_context;
     96 global BeamformerInput *beamformer_input;
     97 global f32 dt_for_frame;
     98 
     99 #define beamformer_frame_arena() (beamformer_context->frame_arenas[beamformer_context->frame_index % countof(beamformer_context->frame_arenas)])
    100 #define beamformer_registers() (&beamformer_context->registers->v)
    101 #define beamformer_push_registers(...) beamformer_push_registers_(&(BeamformerRegisters){beamformer_registers_init_literal __VA_ARGS__})
    102 #define BeamformerRegistersScope(...) DeferLoop(beamformer_push_registers(__VA_ARGS__), beamformer_pop_registers())
    103 #define beamformer_command(name, ...) beamformer_push_command(name, &(BeamformerRegisters){beamformer_registers_init_literal __VA_ARGS__})
    104 
    105 function BeamformerRegisters *
    106 beamformer_pop_registers(void)
    107 {
    108 	BeamformerRegisters *result = &beamformer_context->registers->v;
    109 	SLLStackPop(beamformer_context->registers, next);
    110 	if (beamformer_context->registers == 0)
    111 		beamformer_context->registers = &beamformer_context->base_registers;
    112 	return result;
    113 }
    114 
    115 function BeamformerRegisters *
    116 beamformer_push_registers_(BeamformerRegisters *registers)
    117 {
    118 	BeamformerRegistersNode *node   = push_struct(beamformer_frame_arena(), BeamformerRegistersNode);
    119 	BeamformerRegisters     *result = &node->v;
    120 	memory_copy(result, registers, sizeof(node->v));
    121 	SLLStackPush(beamformer_context->registers, node, next);
    122 	return result;
    123 }
    124 
    125 function void
    126 beamformer_command_list_push_new(Arena *arena, BeamformerCommandList *commands, str8 name, BeamformerRegisters *registers)
    127 {
    128 	BeamformerCommandNode *node = push_struct(arena, BeamformerCommandNode);
    129 	node->command.registers = push_struct_no_zero(arena, BeamformerRegisters);
    130 	node->command.name      = push_str8(arena, name);
    131 	memory_copy(node->command.registers, registers, sizeof(*registers));
    132 	DLLInsertLast(0, commands->first, commands->last, node, next, prev);
    133 	commands->count += 1;
    134 }
    135 
    136 function void
    137 beamformer_push_command(str8 name, BeamformerRegisters *registers)
    138 {
    139 	beamformer_command_list_push_new(beamformer_frame_arena(), beamformer_context->command_queues + 0,
    140 	                                 name, registers);
    141 }
    142 
    143 function BeamformerCommandKind
    144 beamformer_command_kind_from_string(str8 s)
    145 {
    146 	BeamformerCommandKind result = BeamformerCommandKind_Nil;
    147 	for EachElement(beamformer_command_infos, it) {
    148 		if (str8_equal(beamformer_command_infos[it].string, s)) {
    149 			result = (BeamformerCommandKind)it;
    150 			break;
    151 		}
    152 	}
    153 	return result;
    154 }
    155 
    156 function BeamformerPanelKind
    157 beamformer_panel_kind_from_string(str8 s)
    158 {
    159 	BeamformerPanelKind result = BeamformerPanelKind_Nil;
    160 	for EachElement(beamformer_panel_infos, it) {
    161 		if (str8_equal(beamformer_panel_infos[it].string, s)) {
    162 			result = (BeamformerPanelKind)it;
    163 			break;
    164 		}
    165 	}
    166 	return result;
    167 }
    168 
    169 function BeamformerFrame *
    170 beamformer_frame_from_index(u64 index)
    171 {
    172 	BeamformerFrame *result = (BeamformerFrame *)&beamformer_nil_frame;
    173 	if (index < countof(beamformer_context->compute_context.backlog.frames)) {
    174 		BeamformerFrame *frame = beamformer_context->compute_context.backlog.frames + index;
    175 		if (frame->timeline_valid_value != 0)
    176 			result = frame;
    177 	}
    178 	return result;
    179 }
    180 
    181 function b32
    182 beamformer_frame_valid(u64 index)
    183 {
    184 	b32 result = beamformer_frame_from_index(index) != &beamformer_nil_frame;
    185 	return result;
    186 }
    187 
    188 function void
    189 beamformer_compute_plan_release(BeamformerComputeContext *cc, u32 block)
    190 {
    191 	assert(block < countof(cc->compute_plans));
    192 	BeamformerComputePlan *cp = cc->compute_plans[block];
    193 	if (cp) {
    194 		gpu_buffer_release(&cp->gpu_temp_arena);
    195 		cc->compute_plans[block] = 0;
    196 		SLLPushFreelist(cp, cc->compute_plan_freelist);
    197 	}
    198 }
    199 
    200 function GPUResource *
    201 gpu_resource_from_hash(GPUResourceBuilder *rb, u64 hash)
    202 {
    203 	GPUResource *result = 0;
    204 
    205 	GPUResourceHashBucket *hb = rb->hash_table + (hash % GPU_RESOURCE_HASH_TABLE_COUNT);
    206 	for (GPUResource *r = hb->first; r; r = r->hash_next) {
    207 		if (hash == r->hash) {
    208 			result = r;
    209 			break;
    210 		}
    211 	}
    212 
    213 	return result;
    214 }
    215 
    216 typedef struct {
    217 	str8  name;
    218 	u64   align;
    219 	u64   size;
    220 	void *data;
    221 } GPUResourcePushInfo;
    222 #define gpu_resource_push(rb, t, count, ...) gpu_resource_push_(rb, (GPUResourcePushInfo){\
    223 	.align = Max(alignof(t), 16), \
    224 	.size  = sizeof(t) * count, \
    225 	__VA_ARGS__})
    226 
    227 function u32
    228 gpu_resource_push_(GPUResourceBuilder *rb, GPUResourcePushInfo info)
    229 {
    230 	assert(info.size > 0 && info.name.length > 0 && IsPowerOfTwo(info.align));
    231 
    232 	u64 hash = u64_hash_from_str8(info.name);
    233 	GPUResource *r = gpu_resource_from_hash(rb, hash);
    234 	if (!r) {
    235 		r = push_struct(rb->arena, GPUResource);
    236 		GPUResourceHashBucket *hb = rb->hash_table + (hash % GPU_RESOURCE_HASH_TABLE_COUNT);
    237 		DLLInsert(0, hb->first, hb->last, r, hash_next, hash_prev);
    238 		SLLStackPush(rb->resource_list, r, next);
    239 
    240 		r->hash      = hash;
    241 		r->name      = info.name;
    242 		r->alignment = Max(16, info.align);
    243 		r->offset    = AlignUpPowerOfTwo(rb->position, r->alignment);
    244 		r->size      = info.size;
    245 		rb->position = r->offset + r->size;
    246 	}
    247 
    248 	u32 result = r->offset;
    249 
    250 	// NOTE(rnp): if this is a new resource and no data is provided it is likely
    251 	// a temporary GPU side buffer. if this is not a new resource and no data
    252 	// is provided then maybe it is shared and someone else will provide it
    253 	if (info.data) r->data = info.data;
    254 
    255 	return result;
    256 }
    257 
    258 function GPUResourceBuilder *
    259 gpu_resource_build_begin(Arena *arena)
    260 {
    261 	GPUResourceBuilder *result = push_struct(arena, GPUResourceBuilder);
    262 	result->arena = arena;
    263 	return result;
    264 }
    265 
    266 function void
    267 gpu_resource_build_end(GPUResourceBuilder *rb, GPUBuffer *buffer)
    268 {
    269 	u64 size = gpu_round_up_to_sync_size(rb->position, 64);
    270 	if (size != (u64)buffer->size) {
    271 		gpu_buffer_allocate(buffer, (GPUBufferAllocateInfo){
    272 			.size  = size,
    273 			.flags = GPUUsageFlag_HostWrite,
    274 			.label = push_str8_f(rb->arena, "GPU Temp Arena [%p]", buffer),
    275 			.timelines_used = (GPUTimeline []){GPUTimeline_Compute},
    276 			.timeline_count = 1,
    277 		});
    278 	}
    279 
    280 	//////////////////////////////////////
    281 	// NOTE(rnp): upload data
    282 	u64 last_wait_value = 0;
    283 	for (GPUResource *r = rb->resource_list; r; r = r->next)
    284 		if (r->data)
    285 			last_wait_value = gpu_buffer_range_upload(buffer, r->data, r->offset, r->size, 0);
    286 
    287 	// TODO(rnp): cleanup this pointless stall
    288 	if (gpu_buffer_needs_sync(buffer))
    289 		gpu_host_wait_timeline(GPUTimeline_Transfer, last_wait_value, -1ULL);
    290 }
    291 
    292 function BeamformerComputePlan *
    293 beamformer_compute_plan_for_block(BeamformerComputeContext *cc, u32 block, Arena *arena)
    294 {
    295 	assert(block < countof(cc->compute_plans));
    296 	BeamformerComputePlan *result = cc->compute_plans[block];
    297 	if (!result) {
    298 		result = SLLPopFreelist(cc->compute_plan_freelist);
    299 		if (!result) result = push_struct_no_zero(arena, BeamformerComputePlan);
    300 		zero_struct(result);
    301 		cc->compute_plans[block] = result;
    302 
    303 		result->ui_voxel_transform = m4_identity();
    304 	}
    305 	return result;
    306 }
    307 
    308 function BeamformerFilter *
    309 beamformer_filter_create(Arena *arena, BeamformerFilterParameters fp)
    310 {
    311 	BeamformerFilter *result = push_struct(arena, BeamformerFilter);
    312 	switch (fp.kind) {
    313 	case BeamformerFilterKind_Kaiser:{
    314 		/* TODO(rnp): this should also support complex */
    315 		/* TODO(rnp): implement this as an IFIR filter instead to reduce computation */
    316 		result->data = kaiser_low_pass_filter(arena, fp.kaiser.cutoff_frequency, fp.sampling_frequency,
    317 		                                      fp.kaiser.beta, (i32)fp.kaiser.length);
    318 		result->length     = (i32)fp.kaiser.length;
    319 		result->time_delay = (f32)result->length / 2.0f / fp.sampling_frequency;
    320 	}break;
    321 
    322 	case BeamformerFilterKind_MatchedChirp:{
    323 		typeof(fp.matched_chirp) *mc = &fp.matched_chirp;
    324 		f32 fs = fp.sampling_frequency;
    325 		result->length = (i32)(mc->duration * fs);
    326 		if (fp.complex) {
    327 			result->data = baseband_chirp(arena, mc->min_frequency, mc->max_frequency, fs, result->length, 1, 0.5f);
    328 			result->time_delay = complex_filter_first_moment(result->data, result->length, fs);
    329 		} else {
    330 			result->data = rf_chirp(arena, mc->min_frequency, mc->max_frequency, fs, result->length, 1);
    331 			result->time_delay = real_filter_first_moment(result->data, result->length, fs);
    332 		}
    333 	}break;
    334 
    335 	InvalidDefaultCase;
    336 	}
    337 
    338 	result->parameters = fp;
    339 	return result;
    340 }
    341 
    342 function iv3
    343 das_valid_points(iv3 points)
    344 {
    345 	iv3 result;
    346 	result.x = Max(points.x, 1);
    347 	result.y = Max(points.y, 1);
    348 	result.z = Max(points.z, 1);
    349 	return result;
    350 }
    351 
    352 function GPUBuffer *
    353 beamformer_gpu_buffer_from_frame(BeamformerFrame *frame)
    354 {
    355 	GPUBuffer *result = beamformer_context->compute_context.backlog.buffer;
    356 	if (!Between(frame->gpu_pointer, result->gpu_pointer, result->gpu_pointer + result->size)) {
    357 		result = 0;
    358 		BeamformerComputePlan *cp = beamformer_context->compute_context.compute_plans[frame->parameter_block];
    359 		if (cp) {
    360 			result = &cp->gpu_temp_arena;
    361 			assert(Between(frame->gpu_pointer, result->gpu_pointer, result->gpu_pointer + result->size));
    362 		}
    363 	}
    364 	return result;
    365 }
    366 
    367 function u64
    368 beamformer_frame_byte_size(iv3 points, BeamformerDataKind kind)
    369 {
    370 	u64 result = points.x * points.y * points.z * beamformer_data_kind_byte_size[kind];
    371 	result = round_up_to(result, 64);
    372 	return result;
    373 }
    374 
    375 function u64
    376 beamformer_incoherent_frame_byte_size(iv3 points, BeamformerDataKind kind)
    377 {
    378 	u64 result = beamformer_frame_byte_size(points, kind) / beamformer_data_kind_element_count[kind];
    379 	return result;
    380 }
    381 
    382 function BeamformerFrame *
    383 beamformer_frame_next(BeamformerComputeContext *cc, iv3 output_points, b32 complex)
    384 {
    385 	BeamformerFrameBacklog *bl = &cc->backlog;
    386 
    387 	BeamformerDataKind kind = complex ? BeamformerDataKind_Float32Complex : BeamformerDataKind_Float32;
    388 	u64 frame_size = beamformer_frame_byte_size(output_points, kind);
    389 
    390 	// TODO(rnp): handle this somewhat gracefully (even it produces garbled output)
    391 	assert(frame_size <= (u64)bl->buffer->size);
    392 
    393 	if (bl->next_offset > (u64)bl->buffer->size - frame_size)
    394 		bl->next_offset = 0;
    395 
    396 	u64 id = bl->counter++;
    397 
    398 	BeamformerFrame *result = bl->frames + (id % countof(bl->frames));
    399 	atomic_store_u64(&result->timeline_valid_value, -1ULL);
    400 	result->id            = id & U32_MAX;
    401 	result->gpu_pointer   = bl->buffer->gpu_pointer + bl->next_offset;
    402 	result->points        = output_points;
    403 	result->data_kind     = kind;
    404 
    405 	bl->next_offset += frame_size;
    406 
    407 	return result;
    408 }
    409 
    410 function void
    411 push_compute_timing_info(ComputeTimingTable *t, ComputeTimingInfo info)
    412 {
    413 	u32 index = atomic_add_u32(&t->write_index, 1) % countof(t->buffer);
    414 	t->buffer[index] = info;
    415 }
    416 
    417 function uv3
    418 layout_for_output(iv3 points)
    419 {
    420 	uv3 result = {{1, 1, 1}};
    421 
    422 	b32 has_x = points.x > 1;
    423 	b32 has_y = points.y > 1;
    424 	b32 has_z = points.z > 1;
    425 
    426 	u32 subgroup_size  = gpu_info()->subgroup_size;
    427 	u32 grid_3d_z_size = Max(1, subgroup_size / (4 * 4));
    428 	u32 grid_2d_y_size = Max(1, subgroup_size / 8);
    429 
    430 	switch (iv3_dimension(points)) {
    431 	case 1:{
    432 		if (has_x) result.x = subgroup_size;
    433 		if (has_y) result.y = subgroup_size;
    434 		if (has_z) result.z = subgroup_size;
    435 	}break;
    436 
    437 	case 2:{
    438 		if (has_x && has_y) {result.x = 8; result.y = grid_2d_y_size;}
    439 		if (has_x && has_z) {result.x = 8; result.z = grid_2d_y_size;}
    440 		if (has_y && has_z) {result.y = 8; result.z = grid_2d_y_size;}
    441 	}break;
    442 
    443 	case 3:{result = (uv3){{4, 4, grid_3d_z_size}};}break;
    444 
    445 	InvalidDefaultCase;
    446 	}
    447 
    448 	return result;
    449 }
    450 
    451 function uv3
    452 dispatch_for_output(uv3 layout, iv3 points)
    453 {
    454 	uv3 result;
    455 	result.x = ceil_div_u32(points.x, layout.x);
    456 	result.y = ceil_div_u32(points.y, layout.y);
    457 	result.z = ceil_div_u32(points.z, layout.z);
    458 	return result;
    459 }
    460 
    461 typedef struct BeamformerComputeGraphNode BeamformerComputeGraphNode;
    462 struct BeamformerComputeGraphNode {
    463 	// NOTE(rnp): will be BeamformerShaderKind_Count for root node
    464 	BeamformerShaderKind kind;
    465 
    466 	// NOTE(rnp): when any of input or output stride is assigned it is assumed that
    467 	// the shader requires a fixed layout for input, output, or both. When two adjacent
    468 	// nodes require incompatible layouts the second pass over the graph will insert
    469 	// Reshape shaders in between.
    470 	BeamformerDataKind   input_data_kind;
    471 	BeamformerDataLayout input_data_layout;
    472 
    473 	BeamformerDataKind   output_data_kind;
    474 	BeamformerDataLayout output_data_layout;
    475 
    476 	uv3 rf_dimensions;
    477 
    478 	b32 requires_fp_input;
    479 
    480 	i32 user_pipeline_index;
    481 
    482 	BeamformerComputeGraphNode *prev;
    483 	BeamformerComputeGraphNode *next;
    484 };
    485 
    486 typedef struct {
    487 	BeamformerComputeGraphNode *first;
    488 	BeamformerComputeGraphNode *last;
    489 	u64                         count;
    490 } BeamformerComputeGraph;
    491 
    492 function b32
    493 compute_plan_push_shader(BeamformerComputePlan *p, BeamformerComputeGraphNode *node, BeamformerShaderParameters *sp)
    494 {
    495 	b32 result = 0;
    496 	if (p->pipeline.shader_count < countof(p->pipeline.shaders)) {
    497 		u32 index = p->pipeline.shader_count++;
    498 		p->pipeline.shaders[index]    = node->kind;
    499 		zero_struct(p->shader_descriptors + index);
    500 		p->pipeline.parameters[index] = sp ? *sp : (BeamformerShaderParameters){0};
    501 
    502 		p->shader_descriptors[index].input_data_kind  = node->input_data_kind;
    503 		p->shader_descriptors[index].output_data_kind = node->output_data_kind;
    504 
    505 		result = 1;
    506 	}
    507 	return result;
    508 }
    509 
    510 function BeamformerComputeGraphNode *
    511 push_compute_graph_node(BeamformerComputeGraph *graph, BeamformerShaderKind kind, Arena *arena)
    512 {
    513 	BeamformerComputeGraphNode *result = push_struct(arena, BeamformerComputeGraphNode);
    514 	if (graph) {
    515 		if (graph->last) result->rf_dimensions = graph->last->rf_dimensions;
    516 		DLLInsertLast(0, graph->first, graph->last, result, next, prev);
    517 		graph->count++;
    518 	}
    519 	result->kind = kind;
    520 	result->user_pipeline_index = -1;
    521 	// NOTE(rnp): initially don't care data parameters
    522 	result->input_data_kind    = BeamformerDataKind_Count;
    523 	result->output_data_kind   = BeamformerDataKind_Count;
    524 	result->input_data_layout  = BeamformerDataLayout_Count;
    525 	result->output_data_layout = BeamformerDataLayout_Count;
    526 	return result;
    527 }
    528 
    529 function uv3
    530 beamformer_data_strides(BeamformerDataLayout layout, uv3 rf_dimensions)
    531 {
    532 	assert(layout >= 0 && layout < BeamformerDataLayout_Count);
    533 	uv3 result = {0};
    534 	BeamformerRFDimension x = beamformer_data_stride_indices[layout][0];
    535 	BeamformerRFDimension y = beamformer_data_stride_indices[layout][1];
    536 	BeamformerRFDimension z = beamformer_data_stride_indices[layout][2];
    537 	result.E[z] = 1;
    538 	result.E[y] = rf_dimensions.E[z];
    539 	result.E[x] = rf_dimensions.E[z] * rf_dimensions.E[y];
    540 	return result;
    541 }
    542 
    543 function void
    544 plan_compute_pipeline(BeamformerComputePlan *cp, BeamformerParameterBlock *pb, Arena *scratch)
    545 {
    546 	b32 run_hilbert = 0;
    547 	b32 demodulate  = 0;
    548 
    549 	for (u32 i = 0; i < pb->pipeline.shader_count; i++) {
    550 		switch (pb->pipeline.shaders[i]) {
    551 		case BeamformerShaderKind_Hilbert:{run_hilbert = 1;}break;
    552 		case BeamformerShaderKind_Demodulate:{demodulate = 1;}break;
    553 		default:{}break;
    554 		}
    555 	}
    556 
    557 	if (demodulate) run_hilbert = 0;
    558 
    559 	f32 sampling_frequency = pb->parameters.sampling_frequency;
    560 	u32 input_sample_count = pb->parameters.sample_count;
    561 	u32 acquisition_count  = pb->parameters.acquisition_count;
    562 	u32 decimation_rate    = Max(pb->parameters.decimation_rate, 1);
    563 
    564 	cp->raw_channel_byte_stride = pb->parameters.sample_count * pb->parameters.acquisition_count
    565 	                              * beamformer_data_kind_byte_size[pb->pipeline.data_kind];
    566 
    567 	BeamformerDataKind input_data_kind = pb->pipeline.data_kind;
    568 	if (demodulate) {
    569 		switch (input_data_kind) {
    570 		case BeamformerDataKind_Int16:{  input_data_kind = BeamformerDataKind_Int16Complex;  }break;
    571 		case BeamformerDataKind_Float16:{input_data_kind = BeamformerDataKind_Float16Complex;}break;
    572 		case BeamformerDataKind_Float32:{input_data_kind = BeamformerDataKind_Float32Complex;}break;
    573 		default:{}break;
    574 		}
    575 		input_sample_count /= (2 * decimation_rate);
    576 		sampling_frequency /= (2 * decimation_rate);
    577 	}
    578 
    579 	cp->iq_pipeline = beamformer_data_kind_complex[input_data_kind] || run_hilbert;
    580 
    581 	BeamformerDataKind das_data_kind = cp->iq_pipeline ? BeamformerDataKind_Float32Complex
    582 	                                                   : BeamformerDataKind_Float32;
    583 
    584 	cp->channel_count = pb->parameters.channel_count;
    585 	u32 chunk_channel_count = Min(cp->channel_count, BeamformerChunkChannelCount);
    586 
    587 	cp->rf_size = input_sample_count * pb->parameters.acquisition_count * chunk_channel_count
    588 	              * beamformer_data_kind_byte_size[das_data_kind];
    589 
    590 	i64 buffer_size = PING_PONG_BUFFER_SLOTS * round_up_to(cp->rf_size, 64);
    591 	if (beamformer_context->compute_context.ping_pong_buffer.size < buffer_size) {
    592 		b32 cuda = cuda_supported();
    593 		GPUBufferAllocateInfo allocate_info = {
    594 			.size   = buffer_size,
    595 			.export = cuda ? &beamformer_context->compute_context.ping_pong_export_handle : 0,
    596 			.label  = str8("PingPongBuffer"),
    597 		};
    598 		gpu_buffer_allocate(&beamformer_context->compute_context.ping_pong_buffer, allocate_info);
    599 
    600 		// TODO(rnp): figure out how to share with CUDA
    601 		// IMPORTANT: on linux the handle is returned to os and should be cleared after import
    602 		// see usage of glImportMemoryFdEXT and surrounding code in ui.c for examples
    603 		if (cuda) {
    604 		}
    605 	}
    606 
    607 	read_only BeamformerDataKind data_kind_to_element_kind[] = {
    608 		[BeamformerDataKind_Int16]          = BeamformerDataKind_Float16,
    609 		[BeamformerDataKind_Float16]        = BeamformerDataKind_Float16,
    610 		[BeamformerDataKind_Float32]        = BeamformerDataKind_Float32,
    611 		[BeamformerDataKind_Int16Complex]   = BeamformerDataKind_Float16,
    612 		[BeamformerDataKind_Float16Complex] = BeamformerDataKind_Float16,
    613 		[BeamformerDataKind_Float32Complex] = BeamformerDataKind_Float32,
    614 	};
    615 
    616 	read_only BeamformerDataKind data_kind_to_fp_kind[] = {
    617 		[BeamformerDataKind_Int16]          = BeamformerDataKind_Float16,
    618 		[BeamformerDataKind_Float16]        = BeamformerDataKind_Float16,
    619 		[BeamformerDataKind_Float32]        = BeamformerDataKind_Float32,
    620 		[BeamformerDataKind_Int16Complex]   = BeamformerDataKind_Float16Complex,
    621 		[BeamformerDataKind_Float16Complex] = BeamformerDataKind_Float16Complex,
    622 		[BeamformerDataKind_Float32Complex] = BeamformerDataKind_Float32Complex,
    623 	};
    624 
    625 	//////////////////////////////////////
    626 	// NOTE(rnp): First Pass: build initial graph and insert hard layout constraints
    627 	BeamformerComputeGraph graph = {0};
    628 	BeamformerComputeGraphNode *root_node = push_compute_graph_node(&graph, BeamformerShaderKind_Count, scratch);
    629 	root_node->input_data_kind    = input_data_kind;
    630 	root_node->input_data_layout  = BeamformerDataLayout_ChannelEventSample;
    631 	root_node->output_data_kind   = input_data_kind;
    632 	root_node->output_data_layout = BeamformerDataLayout_ChannelEventSample;
    633 
    634 	root_node->rf_dimensions.E[BeamformerRFDimension_Channels] = chunk_channel_count;
    635 	root_node->rf_dimensions.E[BeamformerRFDimension_Events]   = pb->parameters.acquisition_count;
    636 	root_node->rf_dimensions.E[BeamformerRFDimension_Samples]  = pb->parameters.sample_count;
    637 
    638 	for EachIndex(pb->pipeline.shader_count, it) {
    639 		// NOTE(rnp): skip unnecessary shaders
    640 		switch (pb->pipeline.shaders[it]) {
    641 		case BeamformerShaderKind_Hilbert:{if (!run_hilbert) continue;}break;
    642 
    643 		case BeamformerShaderKind_Decode:{
    644 			if (pb->parameters.decode_mode == BeamformerDecodeMode_None)
    645 				continue;
    646 		}break;
    647 
    648 		case BeamformerShaderKind_Sum:
    649 		case BeamformerShaderKind_MinMax:
    650 		{
    651 			// NOTE(rnp): currently unsupported
    652 			continue;
    653 		}break;
    654 
    655 		default:{}break;
    656 		}
    657 
    658 		BeamformerComputeGraphNode *node = push_compute_graph_node(&graph, pb->pipeline.shaders[it], scratch);
    659 		node->user_pipeline_index = (i32)it;
    660 		switch (pb->pipeline.shaders[it]) {
    661 		case BeamformerShaderKind_Demodulate:{
    662 			node->rf_dimensions.E[BeamformerRFDimension_Samples] /= (2 * decimation_rate);
    663 		}break;
    664 
    665 		case BeamformerShaderKind_Decode:{
    666 			b32 low_precision   = beamformer_data_kind_element_size[input_data_kind] < 4;
    667 			b32 use_coop_matrix = gpu_info()->cooperative_matrix &&
    668 			                      low_precision &&
    669 			                      (acquisition_count   % 16 == 0) &&
    670 			                      (chunk_channel_count % 16 == 0);
    671 
    672 			node->requires_fp_input = 1;
    673 
    674 			// NOTE(rnp): fixed input layout required for reasonable performance
    675 			node->input_data_layout = BeamformerDataLayout_SampleChannelEvent;
    676 
    677 			if (use_coop_matrix) {
    678 				node->input_data_kind    = BeamformerDataKind_Float16;
    679 				node->output_data_kind   = data_kind_to_element_kind[das_data_kind];
    680 				node->output_data_layout = node->input_data_layout;
    681 			}
    682 		}break;
    683 
    684 		case BeamformerShaderKind_DAS:{
    685 			// NOTE(rnp): if we aren't decoding we can let the previous stage's
    686 			// data kind pass through as long as it is floating point
    687 			node->requires_fp_input = 1;
    688 			if (pb->parameters.decode_mode != BeamformerDecodeMode_None)
    689 				node->input_data_kind = das_data_kind;
    690 
    691 			node->input_data_layout = BeamformerDataLayout_ChannelEventSample;
    692 			if (pb->parameters.acquisition_kind == BeamformerAcquisitionKind_RCA_TPW ||
    693 			    pb->parameters.acquisition_kind == BeamformerAcquisitionKind_RCA_VLS)
    694 			{
    695 				node->input_data_layout = BeamformerDataLayout_EventChannelSample;
    696 			}
    697 
    698 			node->output_data_layout = BeamformerDataLayout_Image;
    699 			node->output_data_kind   = das_data_kind;
    700 
    701 			// NOTE(rnp): insert implicit CoherencyWeighting node
    702 			if (pb->parameters.coherency_weighting)
    703 				node = push_compute_graph_node(&graph, BeamformerShaderKind_CoherencyWeighting, scratch);
    704 		}break;
    705 
    706 		default:{}break;
    707 		}
    708 	}
    709 
    710 	//////////////////////////////////////
    711 	// NOTE(rnp): Second Pass: resolve layout constraints
    712 	for (BeamformerComputeGraphNode *node = root_node->next; node; node = node->next) {
    713 		b32 needs_reshape = 0;
    714 
    715 		// NOTE(rnp): data strides
    716 		{
    717 			b32 input_dont_care       = node->input_data_layout        == BeamformerDataLayout_Count;
    718 			b32 prev_output_dont_care = node->prev->output_data_layout == BeamformerDataLayout_Count;
    719 
    720 			if (prev_output_dont_care && !input_dont_care)
    721 				node->prev->output_data_layout = node->input_data_layout;
    722 
    723 			if (!prev_output_dont_care && input_dont_care)
    724 				node->input_data_layout = node->prev->output_data_layout;
    725 
    726 			if (prev_output_dont_care && input_dont_care)
    727 				node->input_data_layout = node->prev->output_data_layout = node->prev->input_data_layout;
    728 
    729 			needs_reshape |= node->input_data_layout != node->prev->output_data_layout;
    730 		}
    731 
    732 		// NOTE(rnp): data kinds
    733 		{
    734 			b32 input_dont_care       = node->input_data_kind        == BeamformerDataKind_Count;
    735 			b32 prev_output_dont_care = node->prev->output_data_kind == BeamformerDataKind_Count;
    736 
    737 			if (prev_output_dont_care && !input_dont_care)
    738 				node->prev->output_data_kind = node->input_data_kind;
    739 
    740 			if (!prev_output_dont_care && input_dont_care)
    741 				node->input_data_kind = node->prev->output_data_kind;
    742 
    743 			if (prev_output_dont_care && input_dont_care)
    744 				node->input_data_kind = node->prev->output_data_kind = node->prev->input_data_kind;
    745 
    746 			if (node->requires_fp_input) {
    747 				node->input_data_kind = data_kind_to_fp_kind[node->input_data_kind];
    748 				if (prev_output_dont_care)
    749 					node->prev->output_data_kind = node->input_data_kind;
    750 			}
    751 
    752 			needs_reshape |= node->input_data_kind != node->prev->output_data_kind;
    753 		}
    754 
    755 		// NOTE(rnp): insert reshape if needed
    756 		if (needs_reshape) {
    757 			BeamformerComputeGraphNode *new = push_compute_graph_node(0, BeamformerShaderKind_Reshape, scratch);
    758 			BeamformerComputeGraphNode *last  = node->prev;
    759 			DLLInsertLast(0, node, last, new, next, prev);
    760 			graph.count++;
    761 			new->rf_dimensions      = new->prev->rf_dimensions;
    762 			new->input_data_kind    = new->prev->output_data_kind;
    763 			new->input_data_layout  = new->prev->output_data_layout;
    764 			new->output_data_kind   = new->next->input_data_kind;
    765 			new->output_data_layout = new->next->input_data_layout;
    766 		}
    767 	}
    768 
    769 	// NOTE(rnp): ensure last node descriptor gets proper values for output data kind
    770 	if (graph.last->output_data_kind == BeamformerDataKind_Count)
    771 		graph.last->output_data_kind = graph.last->input_data_kind;
    772 
    773 	f32 time_offset   = pb->parameters.time_offset;
    774 	u32 subgroup_size = gpu_info()->subgroup_size;
    775 
    776 	cp->first_image_shader_index = 0;
    777 	cp->pipeline.shader_count = 0;
    778 
    779 	GPUResourceBuilder *resource_builder = gpu_resource_build_begin(scratch);
    780 	for (BeamformerComputeGraphNode *node = root_node->next; node; node = node->next) {
    781 		assert(node->prev->output_data_kind == node->input_data_kind);
    782 		assert(node->prev->output_data_layout == node->input_data_layout);
    783 
    784 		BeamformerShaderParameters *sp = 0;
    785 		if (node->user_pipeline_index >= 0)
    786 			sp = pb->pipeline.parameters + node->user_pipeline_index;
    787 
    788 		if (compute_plan_push_shader(cp, node, sp)) {
    789 			BeamformerShaderDescriptor *sd = cp->shader_descriptors + cp->pipeline.shader_count - 1;
    790 
    791 			uv3 output_stride = beamformer_data_strides(node->output_data_layout, node->rf_dimensions);
    792 			uv3 input_stride  = beamformer_data_strides(node->input_data_layout,  node->prev->rf_dimensions);
    793 
    794 			switch (node->kind) {
    795 			case BeamformerShaderKind_Decode:{
    796 				BeamformerDecodeBakeParameters *db = &sd->bake.Decode;
    797 
    798 				u32 decode_sample_count = node->rf_dimensions.E[BeamformerRFDimension_Samples];
    799 				db->DecodeMode        = pb->parameters.decode_mode;
    800 				db->TransmitCount     = node->rf_dimensions.E[BeamformerRFDimension_Events];
    801 				db->ChunkChannelCount = node->rf_dimensions.E[BeamformerRFDimension_Channels];
    802 
    803 				// NOTE(rnp): ignored when using coop matrices
    804 				db->OutputSampleStride   = output_stride.E[BeamformerRFDimension_Samples];
    805 				db->OutputChannelStride  = output_stride.E[BeamformerRFDimension_Channels];
    806 				db->OutputTransmitStride = output_stride.E[BeamformerRFDimension_Events];
    807 
    808 				db->ToProcess = 1;
    809 
    810 				b32 use_coop_matrix = gpu_info()->cooperative_matrix &&
    811 				                      node->input_data_kind == BeamformerDataKind_Float16 &&
    812 				                      (db->TransmitCount % 16 == 0) &&
    813 				                      (chunk_channel_count % 16 == 0);
    814 				if (use_coop_matrix) {
    815 					// TODO(rnp): shared memory for larger sizes
    816 					sd->layout = (uv3){{subgroup_size, 1, 1}};
    817 
    818 					if (demodulate)
    819 						decode_sample_count *= 2;
    820 
    821 					sd->compile_flags |= BeamformerDecodeCompileFlags_CooperativeMatrix;
    822 					db->CooperativeMatrixM = 16;
    823 					db->CooperativeMatrixN = 16;
    824 					db->CooperativeMatrixK = 16;
    825 
    826 					sd->dispatch.x = db->TransmitCount   / db->CooperativeMatrixN;
    827 					sd->dispatch.y = chunk_channel_count / db->CooperativeMatrixM;
    828 					sd->dispatch.z = decode_sample_count;
    829 				} else if (db->TransmitCount > 40) {
    830 					sd->compile_flags |= BeamformerDecodeCompileFlags_UseSharedMemory;
    831 
    832 					if (db->TransmitCount == 48)
    833 						db->ToProcess = db->TransmitCount / 16;
    834 
    835 					b32 use_16x  = db->TransmitCount == 48 || db->TransmitCount == 80 ||
    836 					               db->TransmitCount == 96 || db->TransmitCount == 160;
    837 					sd->layout.x = use_16x ? 16 : 32;
    838 					sd->layout.y = 4;
    839 					sd->layout.z = 1;
    840 
    841 					sd->dispatch.x = ceil_div_u32(db->TransmitCount / db->ToProcess, sd->layout.x);
    842 					sd->dispatch.y = ceil_div_u32(db->ChunkChannelCount,             sd->layout.y);
    843 					sd->dispatch.z = ceil_div_u32(decode_sample_count,               sd->layout.z);
    844 				} else {
    845 					/* NOTE(rnp): register caching. using more threads will cause the compiler to do
    846 					 * contortions to avoid spilling registers. using less gives higher performance */
    847 					sd->layout = (uv3){{subgroup_size / 2, 1, 1}};
    848 
    849 					sd->dispatch.x = ceil_div_u32(decode_sample_count,   sd->layout.x);
    850 					sd->dispatch.y = ceil_div_u32(db->ChunkChannelCount, sd->layout.y);
    851 					sd->dispatch.z = 1;
    852 				}
    853 
    854 				sd->uses_heap = 1;
    855 				u32 order = db->TransmitCount;
    856 				db->Hadamard = gpu_resource_push(resource_builder, f16, order * order,
    857 				                                 .data = make_hadamard_transpose(scratch, order, use_coop_matrix),
    858 				                                 .name = str8("hadamard"));
    859 			}break;
    860 
    861 			case BeamformerShaderKind_Demodulate:
    862 			case BeamformerShaderKind_Filter:
    863 			{
    864 				b32 demod = node->kind == BeamformerShaderKind_Demodulate;
    865 				BeamformerFilter *f = beamformer_filter_create(scratch, cp->filter_parameters[sp->filter_slot]);
    866 
    867 				sd->compile_flags |= BeamformerFilterCompileFlags_Demodulate * demod;
    868 				sd->compile_flags |= BeamformerFilterCompileFlags_ComplexFilter * f->parameters.complex;
    869 
    870 				time_offset += f->time_delay;
    871 
    872 				BeamformerFilterBakeParameters *fb = &sd->bake.Filter;
    873 
    874 				sd->uses_heap = 1;
    875 				fb->FilterLength = (u32)f->length;
    876 				fb->FilterCoefficients = gpu_resource_push(resource_builder, f32, f->length * (f->parameters.complex ? 2 : 1),
    877 				                                           .data = f->data,
    878 				                                           .name = push_str8_f(scratch, "filter_%u", sp->filter_slot));
    879 
    880 				fb->SampleCount    = node->rf_dimensions.E[BeamformerRFDimension_Samples];
    881 				fb->DecimationRate = demod ? decimation_rate : 1;
    882 
    883 				b32 deinterleave =  beamformer_data_kind_complex[node->input_data_kind] &&
    884 				                   !beamformer_data_kind_complex[node->output_data_kind];
    885 				if (deinterleave)
    886 					fb->BatchSampleCount = node->rf_dimensions.x * node->rf_dimensions.y * node->rf_dimensions.z;
    887 
    888 				fb->OutputSampleStride   = output_stride.E[BeamformerRFDimension_Samples];
    889 				fb->OutputChannelStride  = output_stride.E[BeamformerRFDimension_Channels];
    890 				fb->OutputTransmitStride = output_stride.E[BeamformerRFDimension_Events];
    891 
    892 				fb->InputSampleStride    = input_stride.E[BeamformerRFDimension_Samples];
    893 				fb->InputChannelStride   = input_stride.E[BeamformerRFDimension_Channels];
    894 				fb->InputTransmitStride  = input_stride.E[BeamformerRFDimension_Events];
    895 
    896 				/* NOTE(rnp): when we are demodulating we pretend that the sampler was alternating
    897 				 * between sampling the I portion and the Q portion of an IQ signal. Therefore there
    898 				 * is an implicit decimation factor of 2 which must always be included. All code here
    899 				 * assumes that the signal was sampled in such a way that supports this operation.
    900 				 * To recover IQ[n] from the sampled data (RF[n]) we do the following:
    901 				 *   I[n]  = RF[n]
    902 				 *   Q[n]  = RF[n + 1]
    903 				 *   IQ[n] = I[n] - j*Q[n]
    904 				 */
    905 				if (demod) {
    906 					fb->DemodulationFrequency = pb->parameters.demodulation_frequency;
    907 					fb->SamplingFrequency     = pb->parameters.sampling_frequency / 2;
    908 				}
    909 
    910 				sd->layout     = (uv3){{subgroup_size, 1, 1}};
    911 				sd->dispatch.x = ceil_div_u32(node->rf_dimensions.E[BeamformerRFDimension_Samples],  sd->layout.x);
    912 				sd->dispatch.y = ceil_div_u32(node->rf_dimensions.E[BeamformerRFDimension_Channels], sd->layout.y);
    913 				sd->dispatch.z = ceil_div_u32(node->rf_dimensions.E[BeamformerRFDimension_Events],   sd->layout.z);
    914 			}break;
    915 
    916 			case BeamformerShaderKind_DAS:{
    917 				cp->first_image_shader_index = cp->pipeline.shader_count;
    918 
    919 				BeamformerDASBakeParameters *db = &sd->bake.DAS;
    920 				db->SamplingFrequency     = sampling_frequency;
    921 				db->DemodulationFrequency = pb->parameters.demodulation_frequency;
    922 				db->SpeedOfSound          = pb->parameters.speed_of_sound;
    923 				db->TimeOffset            = time_offset;
    924 				db->FNumber               = pb->parameters.f_number;
    925 				db->AcquisitionKind       = pb->parameters.acquisition_kind;
    926 				db->SampleCount           = node->rf_dimensions.E[BeamformerRFDimension_Samples];
    927 				db->AcquisitionCount      = node->rf_dimensions.E[BeamformerRFDimension_Events];
    928 				db->ChunkChannelCount     = node->rf_dimensions.E[BeamformerRFDimension_Channels];
    929 				db->ChannelByteStride     = input_stride.E[BeamformerRFDimension_Channels]
    930 				                            * beamformer_data_kind_byte_size[node->input_data_kind];
    931 				db->AcquisitionByteStride = input_stride.E[BeamformerRFDimension_Events]
    932 				                            * beamformer_data_kind_byte_size[node->input_data_kind];
    933 
    934 				// TODO(rnp): pass these in
    935 				db->ReceiveChannelCount   = pb->parameters.channel_count;
    936 				db->TransmitChannelCount  = pb->parameters.channel_count;
    937 				db->ReceiveTileCount      = 1;
    938 				db->TransmitTileCount     = 1;
    939 
    940 				db->InterpolationMode     = pb->parameters.interpolation_mode;
    941 				db->ReadiGroupCount       = pb->parameters.readi_group_count;
    942 				db->OutputSizeX           = cp->output_points.x;
    943 				db->OutputSizeY           = cp->output_points.y;
    944 				db->OutputSizeZ           = cp->output_points.z;
    945 				db->TransmitReceiveOrientation = pb->parameters.transmit_receive_orientation;
    946 
    947 				cp->xdc_element_pitch = pb->parameters.xdc_element_pitch;
    948 
    949 				u32 id = pb->parameters.acquisition_kind;
    950 
    951 				u32 acquisition_kind_flags = beamformer_acquisition_kind_flags[id];
    952 				b32 sparse             = (acquisition_kind_flags & BeamformerAcquisitionKindFlags_Sparse) != 0;
    953 				b32 single_focus       = pb->parameters.single_focus != 0;
    954 				b32 single_orientation = pb->parameters.single_orientation != 0;
    955 
    956 				sd->compile_flags |= BeamformerDASCompileFlags_CoherencyWeighting * (pb->parameters.coherency_weighting != 0);
    957 				sd->compile_flags |= BeamformerDASCompileFlags_Sparse             * sparse;
    958 
    959 				sd->layout   = layout_for_output(cp->output_points);
    960 				sd->dispatch = dispatch_for_output(sd->layout, cp->output_points);
    961 
    962 				sd->uses_heap = 1;
    963 				db->TransducerTransforms = gpu_resource_push(resource_builder, m4, db->TransmitTileCount * db->ReceiveTileCount,
    964 				                                             .data = &pb->parameters.xdc_transform,
    965 				                                             .name = str8("transducer_transforms"));
    966 
    967 				if (!single_focus) {
    968 					db->FocalVectors = gpu_resource_push(resource_builder, v2, db->AcquisitionCount,
    969 					                                     .data = pb->focal_vectors,
    970 					                                     .name = str8("focal_vectors"));
    971 				}
    972 
    973 				if (!single_orientation) {
    974 					db->TransmitReceiveOrientations = gpu_resource_push(resource_builder, u8, db->AcquisitionCount,
    975 					                                                    .data = pb->transmit_receive_orientations,
    976 					                                                    .name = str8("transmit_receive_orientations"));
    977 				}
    978 
    979 				if (sparse) {
    980 					db->SparseElements = gpu_resource_push(resource_builder, i16, db->AcquisitionCount,
    981 					                                       .data = pb->sparse_elements,
    982 					                                       .name = str8("sparse_elements"));
    983 				}
    984 
    985 				if (pb->parameters.coherency_weighting) {
    986 					db->IncoherentFrame = gpu_resource_push(resource_builder, u32, 0,
    987 					                                        .size = beamformer_incoherent_frame_byte_size(cp->output_points, das_data_kind),
    988 					                                        .name = str8("incoherent_buffer"));
    989 				}
    990 
    991 				cp->readi_group = pb->parameters.readi_group;
    992 				if (db->ReadiGroupCount > 1) {
    993 					u32 order = db->ReadiGroupCount;
    994 					db->Hadamard = gpu_resource_push(resource_builder, f16, order * order,
    995 					                                 .data = make_hadamard_transpose(scratch, order, 0),
    996 					                                 .name = str8("readi_hadamard"));
    997 				}
    998 			}break;
    999 
   1000 			case BeamformerShaderKind_CoherencyWeighting:{
   1001 				// NOTE(rnp): beamformed data is stored in linear order; making the layout 2D or 3D
   1002 				// here is just slower
   1003 				sd->layout   = (uv3){{subgroup_size, 1, 1}};
   1004 				sd->dispatch = dispatch_for_output(sd->layout, cp->output_points);
   1005 
   1006 				BeamformerCoherencyWeightingBakeParameters *cw = &sd->bake.CoherencyWeighting;
   1007 				cw->Scale         = 1.f;
   1008 				cw->OutputVoxels  = cp->output_points.x * cp->output_points.y * cp->output_points.z;
   1009 				cw->IncoherentSum = gpu_resource_push(resource_builder, u32, 0,
   1010 				                                      .size = beamformer_incoherent_frame_byte_size(cp->output_points, das_data_kind),
   1011 				                                      .name = str8("incoherent_buffer"));
   1012 				sd->uses_heap = 1;
   1013 			}break;
   1014 
   1015 			case BeamformerShaderKind_Reshape:{
   1016 				BeamformerReshapeBakeParameters *rb = &sd->bake.Reshape;
   1017 				b32 deinterleave =  beamformer_data_kind_complex[node->input_data_kind] &&
   1018 				                   !beamformer_data_kind_complex[node->output_data_kind];
   1019 				b32 interleave   = !beamformer_data_kind_complex[node->input_data_kind] &&
   1020 				                    beamformer_data_kind_complex[node->output_data_kind];
   1021 				assert(interleave == 0 || (interleave != deinterleave));
   1022 				sd->compile_flags |= BeamformerReshapeCompileFlags_Deinterleave * deinterleave;
   1023 				sd->compile_flags |= BeamformerReshapeCompileFlags_Interleave   * interleave;
   1024 
   1025 				rb->InputStrideX   = input_stride.x;
   1026 				rb->InputStrideY   = input_stride.y;
   1027 				rb->InputStrideZ   = input_stride.z;
   1028 				rb->OutputStrideX  = output_stride.x;
   1029 				rb->OutputStrideY  = output_stride.y;
   1030 				rb->OutputStrideZ  = output_stride.z;
   1031 
   1032 				// NOTE(rnp): order doesn't really matter here but it must match the dispatch layout
   1033 				rb->SizeX          = node->rf_dimensions.x;
   1034 				rb->SizeY          = node->rf_dimensions.y;
   1035 				rb->SizeZ          = node->rf_dimensions.z;
   1036 
   1037 				sd->layout.x = 1;
   1038 				sd->layout.z = Min(subgroup_size, rb->SizeZ);
   1039 				sd->layout.y = subgroup_size / sd->layout.z;
   1040 
   1041 				sd->dispatch.x = ceil_div_u32(rb->SizeX, sd->layout.x);
   1042 				sd->dispatch.y = ceil_div_u32(rb->SizeY, sd->layout.y);
   1043 				sd->dispatch.z = ceil_div_u32(rb->SizeZ, sd->layout.z);
   1044 			}break;
   1045 
   1046 			default:{}break;
   1047 
   1048 			#if 0
   1049 			case BeamformerShaderKind_Sum:{
   1050 				sd->bake.data_kind = BeamformerDataKind_Float32;
   1051 				if (cp->iq_pipeline)
   1052 					sd->bake.data_kind = BeamformerDataKind_Float32Complex;
   1053 
   1054 				sd->layout   = layout_for_output(cp->output_points);
   1055 				sd->dispatch = dispatch_for_output(sd->layout, cp->output_points);
   1056 
   1057 				commit = 1;
   1058 			}break;
   1059 			#endif
   1060 
   1061 			}
   1062 		}
   1063 	}
   1064 
   1065 	cp->pipeline.data_kind = input_data_kind;
   1066 
   1067 	if (cp->first_image_shader_index == 0)
   1068 		cp->first_image_shader_index = cp->pipeline.shader_count;
   1069 
   1070 	gpu_resource_build_end(resource_builder, &cp->gpu_temp_arena);
   1071 }
   1072 
   1073 function void
   1074 stream_append_shader_header(Stream *s, i32 reloadable_index, u64 gpu_heap_pointer, BeamformerShaderDescriptor *sd, uv3 layout)
   1075 {
   1076 	stream_append_str8(s, str8("#version 460 core\n\n"
   1077 	"#extension GL_EXT_buffer_reference : require\n"
   1078 	"#extension GL_EXT_shader_16bit_storage : require\n"
   1079 	"#extension GL_EXT_shader_explicit_arithmetic_types : require\n\n"
   1080 	"#define f32     float32_t\n"
   1081 	"#define f16     float16_t\n"
   1082 	"#define s32     int32_t\n"
   1083 	"#define u64     uint64_t\n"
   1084 	"#define u32     uint32_t\n"
   1085 	"#define s16     int16_t\n"
   1086 	"#define u16     uint16_t\n"
   1087 	"#define u8      uint8_t\n"
   1088 	"#define s32vec2 i32vec2\n"
   1089 	"#define s16vec2 i16vec2\n"
   1090 	"\n"));
   1091 
   1092 	i32  header_vector_length = beamformer_shader_header_vector_lengths[reloadable_index];
   1093 	i32 *header_vector        = (i32 *)beamformer_shader_header_vectors[reloadable_index];
   1094 	for (i32 index = 0; index < header_vector_length; index++)
   1095 		stream_append_str8(s, beamformer_shader_global_header_strings[header_vector[index]]);
   1096 
   1097 	if (layout.x != 0) {
   1098 		stream_append_str8(s, str8("layout(local_size_x = "));
   1099 		stream_append_u64(s,  layout.x);
   1100 		stream_append_str8(s, str8(", local_size_y = "));
   1101 		stream_append_u64(s,  layout.y);
   1102 		stream_append_str8(s, str8(", local_size_z = "));
   1103 		stream_append_u64(s,  layout.z);
   1104 		stream_append_str8(s, str8(") in;\n\n"));
   1105 	}
   1106 
   1107 	{
   1108 		u32 max_length = 0;
   1109 		for EachElement(beamformer_data_kind_str8, it)
   1110 			max_length = Max(max_length, (u32)beamformer_data_kind_str8[it].length);
   1111 
   1112 		for EachElement(beamformer_data_kind_str8, it) {
   1113 			stream_append_str8s(s, str8("#define DataKind_"), beamformer_data_kind_str8[it]);
   1114 			stream_pad(s, ' ', max_length - beamformer_data_kind_str8[it].length + 1);
   1115 			stream_append_u64(s, it);
   1116 			stream_append_byte(s, '\n');
   1117 		}
   1118 		stream_append_byte(s, '\n');
   1119 	}
   1120 
   1121 	if (gpu_heap_pointer) {
   1122 		stream_append_str8(s, str8("#define HeapBase u64(0x"));
   1123 		stream_append_hex_u64(s, gpu_heap_pointer);
   1124 		stream_append_str8(s, str8("ul)\n"));
   1125 	}
   1126 
   1127 	if (sd) {
   1128 		BeamformerDataKind data_kinds[] = {sd->input_data_kind, sd->output_data_kind};
   1129 		str8 line_prefixes[] = {str8_comp("Input"), str8_comp("Output")};
   1130 		for EachElement(data_kinds, it) {
   1131 			if (data_kinds[it] != BeamformerDataKind_Count) {
   1132 				stream_append_str8s(s, str8("#define "), line_prefixes[it], str8("DataType "),
   1133 				                    beamformer_data_kind_glsl_type[data_kinds[it]],
   1134 				                    str8("\n#define "), line_prefixes[it], str8("DataKind DataKind_"),
   1135 				                    beamformer_data_kind_str8[data_kinds[it]],
   1136 				                    str8("\n#define "), line_prefixes[it], str8("DataKindByteSize "));
   1137 				stream_append_u64(s, beamformer_data_kind_byte_size[data_kinds[it]]);
   1138 				stream_append_byte(s, '\n');
   1139 			}
   1140 		}
   1141 		stream_append_byte(s, '\n');
   1142 
   1143 		stream_append_str8(s, str8("#define CompileFlags (0x"));
   1144 		stream_append_hex_u64_width(s, sd->compile_flags, 8);
   1145 		stream_append_str8(s, str8(")\n"));
   1146 
   1147 		i32 struct_id = beamformer_base_shader_to_bake_struct_id[reloadable_index];
   1148 		if (struct_id != -1) {
   1149 			const str8             *names = meta_struct_member_names_by_id[struct_id];
   1150 			const MetaStructInfo   *si    = meta_struct_info_by_id + struct_id;
   1151 			const MetaStructMember *sm    = meta_struct_members_by_id[struct_id];
   1152 			for (u32 index = 0; index < si->member_count; index++) {
   1153 				str8 type = meta_kind_glsl_types[sm[index].type_id];
   1154 				stream_append_str8(s, str8("layout(constant_id = "));
   1155 				stream_append_u64(s, index);
   1156 				stream_append_str8s(s, str8(") const "), type, str8(" "), names[index], str8(" = "), type, str8("(1);\n"));
   1157 			}
   1158 		}
   1159 	}
   1160 
   1161 	if (!renderdoc_attached())
   1162 		stream_append_str8(s, str8("\n\n#line 1\n"));
   1163 }
   1164 
   1165 function void
   1166 beamformer_reload_pipeline(VulkanHandle *pipeline, BeamformerShaderReloadInfo *sris, u32 count, Arena *scratch)
   1167 {
   1168 	assume(count <= 2);
   1169 	str8 paths[2];
   1170 	VulkanPipelineCreateInfo infos[2];
   1171 
   1172 	if (!BakeShaders) {
   1173 		for (u32 i = 0; i < count; i++)
   1174 			paths[i] = push_str8_from_parts(scratch, os_path_separator(), str8("shaders"), sris[i].filename_or_data);
   1175 	}
   1176 
   1177 	u32 push_constants_size = 0;
   1178 	for (u32 i = 0; i < count; i++) {
   1179 		Stream shader_stream = arena_stream(scratch);
   1180 		i32 reloadable_index = beamformer_shader_reloadable_index_by_shader[sris[i].shader];
   1181 		if (i == 0) push_constants_size = beamformer_shader_push_constant_sizes[reloadable_index];
   1182 		else        assert(push_constants_size == beamformer_shader_push_constant_sizes[reloadable_index]);
   1183 
   1184 		stream_append_shader_header(&shader_stream, reloadable_index, sris[i].gpu_heap_pointer,
   1185 		                            sris[i].shader_descriptor, sris[i].layout);
   1186 
   1187 		str8 shader_text;
   1188 		if (BakeShaders) {
   1189 			stream_append_str8(&shader_stream, sris[i].filename_or_data);
   1190 			shader_text = arena_stream_commit_zero(scratch, &shader_stream);
   1191 		} else {
   1192 			str8 stream_data = arena_stream_commit(scratch, &shader_stream);
   1193 			str8 shader_data = os_read_entire_file(scratch, (c8 *)paths[i].data);
   1194 			// NOTE(rnp): kinda sucky but need to make sure these are a contiguous string
   1195 			shader_text = push_str8_from_parts(scratch, str8(""), stream_data, shader_data);
   1196 		}
   1197 
   1198 		infos[i].kind = sris[i].shader_kind;
   1199 		infos[i].text = shader_text;
   1200 		infos[i].name = beamformer_shader_names[sris[i].shader];
   1201 		infos[i].specialization_data      = sris[i].shader_descriptor ? &sris[i].shader_descriptor->bake : 0;
   1202 		infos[i].specialization_struct_id = beamformer_base_shader_to_bake_struct_id[reloadable_index];
   1203 
   1204 		//str8 line = str8("---------------\n");
   1205 		//str8 nl   = str8("\n");
   1206 		//os_console_log(line.data, line.length);
   1207 		//os_console_log(infos[i].name.data, infos[i].name.length);
   1208 		//os_console_log(nl.data, nl.length);
   1209 		//os_console_log(line.data, line.length);
   1210 		//os_console_log(infos[i].text.data, infos[i].text.length);
   1211 		//os_console_log(line.data, line.length);
   1212 	}
   1213 
   1214 	vk_pipeline_release(*pipeline);
   1215 	*pipeline = vk_pipeline(infos, count, push_constants_size);
   1216 }
   1217 
   1218 function void
   1219 beamformer_reload_render_pipeline(VulkanHandle *pipeline, BeamformerShaderKind shader, Arena *scratch)
   1220 {
   1221 	i32 index = beamformer_shader_reloadable_index_by_shader[shader];
   1222 	BeamformerShaderReloadInfo infos[2] = {
   1223 		{
   1224 			.shader      = shader,
   1225 			.shader_kind = beamformer_shader_primitive_is_vertex[index] ? VulkanShaderKind_Vertex : VulkanShaderKind_Mesh,
   1226 			.filename_or_data = BakeShaders ? beamformer_shader_data[index][0]
   1227 			                                : beamformer_reloadable_shader_files[index][0],
   1228 		},
   1229 		{
   1230 			.shader           = shader,
   1231 			.shader_kind      = VulkanShaderKind_Fragment,
   1232 			.filename_or_data = BakeShaders ? beamformer_shader_data[index][1]
   1233 			                                : beamformer_reloadable_shader_files[index][1],
   1234 		},
   1235 	};
   1236 	beamformer_reload_pipeline(pipeline, infos, countof(infos), scratch);
   1237 }
   1238 
   1239 function void
   1240 beamformer_commit_parameter_block(BeamformerCtx *ctx, BeamformerComputePlan *cp, u32 block, Arena *scratch)
   1241 {
   1242 	BeamformerParameterBlock *pb;
   1243 	DeferLoop(pb = beamformer_parameter_block_lock(ctx->shared_memory, block, -1),
   1244 	          beamformer_parameter_block_unlock(ctx->shared_memory, block))
   1245 	for EachBit(pb->region_update_flags, region)
   1246 	{
   1247 		pb->region_update_flags &= ~(1ul << region);
   1248 		switch (region) {
   1249 		case BeamformerParameterDirtyFlag_NotifyUI:{
   1250 			atomic_store_u32(&ctx->ui_dirty_parameter_blocks, 1u << block);
   1251 			cp->ui_voxel_transform = m4_identity();
   1252 		}break;
   1253 
   1254 		case BeamformerParameterDirtyFlag_Parameters:{
   1255 			cp->output_points  = das_valid_points(pb->parameters.output_points.xyz);
   1256 			cp->average_frames = pb->parameters.output_points.E[3];
   1257 
   1258 			plan_compute_pipeline(cp, pb, scratch);
   1259 
   1260 			b32 heap_pointer_updated = cp->last_gpu_heap_pointer != cp->gpu_temp_arena.gpu_pointer;
   1261 			cp->last_gpu_heap_pointer = cp->gpu_temp_arena.gpu_pointer;
   1262 
   1263 			for (u32 shader_slot = 0; shader_slot < cp->pipeline.shader_count; shader_slot++) {
   1264 				u128 hash = u128_hash_from_data(cp->shader_descriptors + shader_slot, sizeof(BeamformerShaderDescriptor));
   1265 				if (!u128_equal(hash, cp->shader_hashes[shader_slot]) ||
   1266 				    (cp->shader_descriptors[shader_slot].uses_heap && heap_pointer_updated))
   1267 				{
   1268 					cp->dirty_programs |= 1 << shader_slot;
   1269 				}
   1270 				cp->shader_hashes[shader_slot] = hash;
   1271 			}
   1272 
   1273 			cp->acquisition_count = pb->parameters.acquisition_count;
   1274 			cp->acquisition_kind  = pb->parameters.acquisition_kind;
   1275 			cp->contrast_mode     = pb->parameters.contrast_mode;
   1276 		}break;
   1277 		}
   1278 	}
   1279 }
   1280 
   1281 function void
   1282 do_compute_shader(GPUCommandList cmd, BeamformerComputePlan *cp, BeamformerFrame *frame,
   1283                   u64 input_pointer, u64 output_pointer, u32 shader_slot, u32 channel_offset)
   1284 {
   1285 	BeamformerComputeContext *cc = &beamformer_context->compute_context;
   1286 
   1287 	uv3 dispatch = cp->shader_descriptors[shader_slot].dispatch;
   1288 
   1289 	gpu_command_bind_pipeline(cmd, cp->vulkan_pipelines[shader_slot]);
   1290 
   1291 	switch (cp->pipeline.shaders[shader_slot]) {
   1292 
   1293 	case BeamformerShaderKind_Decode:{
   1294 		BeamformerDecodePushConstants pc = {
   1295 			.rf_buffer     = input_pointer,
   1296 			.output_buffer = output_pointer,
   1297 		};
   1298 
   1299 		gpu_command_push_constants(cmd, 0, sizeof(pc), &pc);
   1300 		gpu_command_dispatch_compute(cmd, dispatch);
   1301 
   1302 		cc->ping_pong_input_index = !cc->ping_pong_input_index;
   1303 	}break;
   1304 
   1305 	case BeamformerShaderKind_Hilbert:{
   1306 		//cuda_hilbert(input_index, output_index);
   1307 		//cc->ping_pong_input_index = !cc->ping_pong_input_index;
   1308 	}break;
   1309 
   1310 	case BeamformerShaderKind_Filter:
   1311 	case BeamformerShaderKind_Demodulate:
   1312 	{
   1313 		BeamformerFilterPushConstants pc = {
   1314 			.input_buffer  = input_pointer,
   1315 			.output_buffer = output_pointer,
   1316 		};
   1317 
   1318 		gpu_command_push_constants(cmd, 0, sizeof(pc), &pc);
   1319 		gpu_command_dispatch_compute(cmd, dispatch);
   1320 
   1321 		cc->ping_pong_input_index = !cc->ping_pong_input_index;
   1322 	}break;
   1323 
   1324 	case BeamformerShaderKind_DAS:{
   1325 		BeamformerDASPushConstants pc = {
   1326 			.xdc_element_pitch = cp->xdc_element_pitch,
   1327 			.rf_data           = input_pointer,
   1328 			.output_frame      = frame->gpu_pointer,
   1329 			.channel_offset    = channel_offset,
   1330 			.focus_depth       = frame->focus_depth,
   1331 			.steering_angle    = frame->steering_angle,
   1332 			.tx_rx_orientation = frame->transmit_receive_orientation,
   1333 			.readi_group       = cp->readi_group,
   1334 		};
   1335 		memory_copy(pc.voxel_transform.E, frame->voxel_transform.E, sizeof(pc.voxel_transform));
   1336 
   1337 		gpu_command_push_constants(cmd, 0, sizeof(pc), &pc);
   1338 		gpu_command_dispatch_compute(cmd, dispatch);
   1339 	}break;
   1340 
   1341 	case BeamformerShaderKind_CoherencyWeighting:{
   1342 		BeamformerCoherencyWeightingPushConstants pc = {.coherent_sum = frame->gpu_pointer};
   1343 		gpu_command_push_constants(cmd, 0, sizeof(pc), &pc);
   1344 		gpu_command_dispatch_compute(cmd, dispatch);
   1345 	}break;
   1346 
   1347 	case BeamformerShaderKind_Reshape:{
   1348 		BeamformerDataKind input_data_kind = cp->shader_descriptors[shader_slot].input_data_kind;
   1349 		BeamformerReshapeBakeParameters *rb = &cp->shader_descriptors[shader_slot].bake.Reshape;
   1350 		BeamformerReshapePushConstants pc = {
   1351 			.left_input_buffer  = input_pointer,
   1352 			.right_input_buffer = input_pointer + rb->SizeX * rb->SizeY * rb->SizeZ
   1353 			                                      * beamformer_data_kind_byte_size[input_data_kind],
   1354 			.output_buffer      = output_pointer,
   1355 		};
   1356 
   1357 		gpu_command_push_constants(cmd, 0, sizeof(pc), &pc);
   1358 		gpu_command_dispatch_compute(cmd, dispatch);
   1359 
   1360 		cc->ping_pong_input_index = !cc->ping_pong_input_index;
   1361 	}break;
   1362 
   1363 	// NOTE(rnp): invalid stages should be filtered in planning phase
   1364 	InvalidDefaultCase;
   1365 	}
   1366 
   1367 	#if 0
   1368 	switch (shader) {
   1369 	case BeamformerShaderKind_MinMax:{
   1370 		for (u32 i = 1; i < frame->image.mip_map_levels; i++) {
   1371 			glBindImageTexture(0, frame->texture, i - 1, GL_TRUE, 0, GL_READ_ONLY,  GL_RG32F);
   1372 			glBindImageTexture(1, frame->texture, i - 0, GL_TRUE, 0, GL_WRITE_ONLY, GL_RG32F);
   1373 			glProgramUniform1i(program, MIN_MAX_MIPS_LEVEL_UNIFORM_LOC, i);
   1374 
   1375 			u32 width  = (u32)frame->dim.x >> i;
   1376 			u32 height = (u32)frame->dim.y >> i;
   1377 			u32 depth  = (u32)frame->dim.z >> i;
   1378 			glDispatchCompute(ORONE(width / 32), ORONE(height), ORONE(depth / 32));
   1379 			glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
   1380 		}
   1381 	}break;
   1382 	case BeamformerShaderKind_Sum:{
   1383 		u32 aframe_index = ctx->averaged_frame_index % countof(ctx->averaged_frames);
   1384 		BeamformerFrame *aframe = ctx->averaged_frames + aframe_index;
   1385 		aframe->id              = ctx->averaged_frame_index;
   1386 		atomic_store_u32(&aframe->ready_to_present, 0);
   1387 		/* TODO(rnp): hack we need a better way of specifying which frames to sum;
   1388 		 * this is fine for rolling averaging but what if we want to do something else */
   1389 		assert(frame >= ctx->beamform_frames);
   1390 		assert(frame < ctx->beamform_frames + countof(ctx->beamform_frames));
   1391 		u32 base_index   = (u32)(frame - ctx->beamform_frames);
   1392 		u32 to_average   = (u32)cp->average_frames;
   1393 		u32 frame_count  = 0;
   1394 		u32 *in_textures = push_array(&arena, u32, BeamformerMaxBacklogFrames);
   1395 		ComputeFrameIterator cfi = compute_frame_iterator(ctx, 1 + base_index - to_average, to_average);
   1396 		for (BeamformerFrame *it = frame_next(&cfi); it; it = frame_next(&cfi))
   1397 			in_textures[frame_count++] = it->texture;
   1398 
   1399 		assert(to_average == frame_count);
   1400 
   1401 		glProgramUniform1f(program, SUM_PRESCALE_UNIFORM_LOC, 1 / (f32)frame_count);
   1402 		/* NOTE: zero output before summing */
   1403 		glClearTexImage(aframe->texture, 0, GL_RED, GL_FLOAT, 0);
   1404 		glMemoryBarrier(GL_TEXTURE_UPDATE_BARRIER_BIT);
   1405 
   1406 		glBindImageTexture(0, out_texture, 0, GL_TRUE, 0, GL_READ_WRITE, GL_RG32F);
   1407 		for (u32 i = 0; i < in_texture_count; i++) {
   1408 			glBindImageTexture(1, in_textures[i], 0, GL_TRUE, 0, GL_READ_ONLY, GL_RG32F);
   1409 			glDispatchCompute(dispatch.x, dispatch.y, dispatch.z);
   1410 			glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
   1411 		}
   1412 
   1413 		memory_copy(aframe->voxel_transform.E,  frame->voxel_transform.E, sizeof(frame->voxel_transform));
   1414 		aframe->compound_count   = frame->compound_count;
   1415 		aframe->acquisition_kind = frame->acquisition_kind;
   1416 	}break;
   1417 	}
   1418 	#endif
   1419 }
   1420 
   1421 function void
   1422 complete_queue(BeamformerCtx *ctx, BeamformWorkQueue *q, Arena *arena)
   1423 {
   1424 	BeamformerComputeContext * cs = &ctx->compute_context;
   1425 	BeamformerSharedMemory *   sm = ctx->shared_memory;
   1426 
   1427 	for (BeamformWork *work = beamform_work_queue_pop(q);
   1428 	     work;
   1429 	     beamform_work_queue_pop_commit(q), work = beamform_work_queue_pop(q))
   1430 	{
   1431 		switch (work->kind) {
   1432 
   1433 		case BeamformerWorkKind_ExportBuffer:{
   1434 			/* TODO(rnp): better way of handling DispatchCompute barrier */
   1435 			post_sync_barrier(ctx->shared_memory, BeamformerSharedMemoryLockKind_DispatchCompute);
   1436 			beamformer_shared_memory_take_lock(ctx->shared_memory, (i32)work->lock, (u32)-1);
   1437 			BeamformerExportContext *ec = &work->export_context;
   1438 			switch (ec->kind) {
   1439 			case BeamformerExportKind_BeamformedData:{
   1440 				BeamformerFrameBacklog *bl = &ctx->compute_context.backlog;
   1441 				u32 req_count = Clamp(ec->count, 1, bl->counter);
   1442 				u32 frame_idx = bl->counter - req_count;
   1443 				u8 *sm_output = beamformer_shared_memory_data_pointer(sm, ctx->shared_memory_size);
   1444 				u64 exported_size = 0;
   1445 				for (u32 export_count = 0; export_count < req_count; export_count++, frame_idx++) {
   1446 					BeamformerFrame *f = bl->frames + frame_idx % countof(bl->frames);
   1447 					u64 frame_size = beamformer_frame_byte_size(f->points, f->data_kind);
   1448 					assert((frame_size & 63) == 0);
   1449 					// NOTE(tkh) we don't want to assume that all req_count frames are the same size,
   1450 					// so we either need to count the total size of all requested frames first or
   1451 					// just fill up as much as possible.
   1452 					if (exported_size + frame_size <= ec->size) {
   1453 						u64 offset = f->gpu_pointer - bl->buffer->gpu_pointer;
   1454 						gpu_host_wait_timeline(GPUTimeline_Compute, f->timeline_valid_value, -1ULL);
   1455 						gpu_buffer_range_download(sm_output + exported_size, bl->buffer, offset, frame_size, 1);
   1456 						exported_size += frame_size;
   1457 					}
   1458 				}
   1459 			}break;
   1460 
   1461 			case BeamformerExportKind_Stats:{
   1462 				ComputeTimingTable *table = ctx->compute_timing_table;
   1463 				/* NOTE(rnp): do a little spin to let this finish updating */
   1464 				spin_wait(table->write_index != atomic_load_u32(&table->read_index));
   1465 				ComputeShaderStats *stats = ctx->compute_shader_stats;
   1466 				if (sizeof(stats->table) <= ec->size)
   1467 					memory_copy(beamformer_shared_memory_data_pointer(sm, ctx->shared_memory_size),
   1468 					         &stats->table, sizeof(stats->table));
   1469 			}break;
   1470 			InvalidDefaultCase;
   1471 			}
   1472 			beamformer_shared_memory_release_lock(ctx->shared_memory, work->lock);
   1473 			post_sync_barrier(ctx->shared_memory, BeamformerSharedMemoryLockKind_ExportSync);
   1474 		}break;
   1475 
   1476 		case BeamformerWorkKind_CreateFilter:{
   1477 			BeamformerCreateFilterContext *fctx = &work->create_filter_context;
   1478 			u32 block = fctx->parameter_block;
   1479 			u32 slot  = fctx->filter_slot;
   1480 			BeamformerComputePlan *cp = beamformer_compute_plan_for_block(cs, block, arena);
   1481 			cp->filter_parameters[slot] = fctx->parameters;
   1482 		}break;
   1483 
   1484 		case BeamformerWorkKind_WaitThenCompute:
   1485 		case BeamformerWorkKind_Compute:
   1486 		{
   1487 			push_compute_timing_info(ctx->compute_timing_table,
   1488 			                         (ComputeTimingInfo){.kind = ComputeTimingInfoKind_ComputeFrameBegin});
   1489 
   1490 			BeamformerComputePlan *cp = beamformer_compute_plan_for_block(cs, work->compute_context.parameter_set, arena);
   1491 			if unlikely(beamformer_parameter_block_dirty(sm, work->compute_context.parameter_set)) {
   1492 				u32 set = work->compute_context.parameter_set;
   1493 				Temp scratch = temp_begin(arena);
   1494 				beamformer_commit_parameter_block(ctx, cp, set, arena);
   1495 				temp_end(scratch);
   1496 			}
   1497 
   1498 			post_sync_barrier(ctx->shared_memory, BeamformerSharedMemoryLockKind_DispatchCompute);
   1499 
   1500 			u32 dirty_programs = atomic_swap_u32(&cp->dirty_programs, 0);
   1501 			static_assert(BeamformerMaxComputeShaderStages <= 32, "");
   1502 			if unlikely(dirty_programs) {
   1503 				for EachBit(dirty_programs, slot) {
   1504 					assert(slot < BeamformerMaxComputeShaderStages);
   1505 					Temp scratch;
   1506 					DeferLoop(scratch = temp_begin(arena), temp_end(scratch))
   1507 					{
   1508 						u64 gpu_heap_pointer = cp->gpu_temp_arena.gpu_pointer;
   1509 						i32 index = beamformer_shader_reloadable_index_by_shader[cp->pipeline.shaders[slot]];
   1510 						BeamformerShaderReloadInfo info = {
   1511 							.shader      = cp->pipeline.shaders[slot],
   1512 							.shader_kind = VulkanShaderKind_Compute,
   1513 							.shader_descriptor = cp->shader_descriptors + slot,
   1514 							.gpu_heap_pointer  = cp->shader_descriptors[slot].uses_heap ? gpu_heap_pointer : 0,
   1515 							.filename_or_data  = BakeShaders ? beamformer_shader_data[index][0]
   1516 							                                 : beamformer_reloadable_shader_files[index][0],
   1517 							.layout            = cp->shader_descriptors[slot].layout,
   1518 						};
   1519 						beamformer_reload_pipeline(cp->vulkan_pipelines + slot, &info, 1, arena);
   1520 					}
   1521 				}
   1522 			}
   1523 
   1524 			atomic_store_u32(&cs->processing_compute, 1);
   1525 
   1526 			start_renderdoc_capture();
   1527 
   1528 			i32 das_index = -1;
   1529 			i32 coherency_weighting = -1;
   1530 			for (u32 i = 0; i < cp->pipeline.shader_count; i++) {
   1531 				if (cp->pipeline.shaders[i] == BeamformerShaderKind_CoherencyWeighting)
   1532 					coherency_weighting = (i32)i;
   1533 
   1534 				if (cp->pipeline.shaders[i] == BeamformerShaderKind_DAS)
   1535 					das_index = (i32)i;
   1536 			}
   1537 
   1538 			BeamformerFrame *frame  = beamformer_frame_next(cs, cp->output_points, cp->iq_pipeline);
   1539 			frame->acquisition_kind = cp->acquisition_kind;
   1540 			frame->contrast_mode    = cp->contrast_mode;
   1541 			frame->compound_count   = cp->acquisition_count;
   1542 			frame->parameter_block  = work->compute_context.parameter_set;
   1543 			frame->view_plane_tag   = work->compute_context.view_plane;
   1544 			frame->focus_depth      = work->compute_context.focus_depth;
   1545 			frame->steering_angle   = work->compute_context.steering_angle;
   1546 			frame->transmit_receive_orientation = work->compute_context.tx_rx_orientation;
   1547 
   1548 			b32 transform_changed = !m4_equal(cp->last_voxel_transform, work->compute_context.voxel_transform);
   1549 			memory_copy(&cp->last_voxel_transform, &work->compute_context.voxel_transform, sizeof(cp->last_voxel_transform));
   1550 			memory_copy(&frame->voxel_transform, &cp->last_voxel_transform, sizeof(frame->voxel_transform));
   1551 
   1552 			if (transform_changed) {
   1553 				atomic_store_u32(&beamformer_context->ui_dirty_parameter_blocks, 1u << work->compute_context.parameter_set);
   1554 				cp->ui_voxel_transform = m4_identity();
   1555 			} else if (u64_hash_from_str8(str8_struct(&cp->last_voxel_transform)) == cp->ui_last_voxel_transform_expected_hash) {
   1556 				frame->voxel_transform = m4_mul(cp->ui_voxel_transform, frame->voxel_transform);
   1557 			}
   1558 
   1559 			GPUCommandList cmd = gpu_command_list_begin(GPUTimeline_Compute);
   1560 			gpu_command_timestamp(cmd);
   1561 
   1562 			if (das_index >= 0) {
   1563 				GPUBuffer *backlog = cs->backlog.buffer;
   1564 				u64 frame_size = beamformer_frame_byte_size(frame->points, frame->data_kind);
   1565 				u64 offset     = frame->gpu_pointer - backlog->gpu_pointer;
   1566 				gpu_command_clear_buffer(cmd, backlog, offset, frame_size, 0);
   1567 			}
   1568 
   1569 			if (coherency_weighting >= 0) {
   1570 				BeamformerCoherencyWeightingBakeParameters *cw = &cp->shader_descriptors[coherency_weighting].bake.CoherencyWeighting;
   1571 				GPUBuffer *gpu_arena = &cp->gpu_temp_arena;
   1572 				u64 coherent_size = beamformer_incoherent_frame_byte_size(frame->points, frame->data_kind);
   1573 				gpu_command_clear_buffer(cmd, gpu_arena, cw->IncoherentSum, coherent_size, 0);
   1574 			}
   1575 
   1576 			BeamformerRFBuffer *rf = &cs->rf_buffer;
   1577 			u64 compute_index = rf->compute_index;
   1578 			u64 slot = compute_index % countof(rf->upload_complete_values);
   1579 
   1580 			// NOTE(rnp): the library/ui will ensure that any time a new dataset is uploaded
   1581 			// the next time beamforming is issued it will be tagged as WaitThenCompute.
   1582 			if (work->kind != BeamformerWorkKind_WaitThenCompute)
   1583 				slot = (compute_index - 1) % countof(rf->upload_complete_values);
   1584 
   1585 			// NOTE(rnp): nvidia needs a memory barrier between pipeline stages
   1586 			// for correct output. It doesn't seem to effect performance on nvidia cards.
   1587 			// It does have a very minor performance decrease on AMD, but technically its
   1588 			// against vulkan spec to skip them so for simplicity we will default to for now
   1589 			b32 pipeline_memory_barrier = 1; //gpu_info()->vendor == GPUVendor_NVIDIA;
   1590 
   1591 			for (u32 channel_offset = 0;
   1592 			     channel_offset < cp->channel_count;
   1593 			     channel_offset += BeamformerChunkChannelCount)
   1594 			{
   1595 				u64 rf_pointer = rf->buffer.gpu_pointer + slot * rf->active_rf_size;
   1596 				rf_pointer += cp->raw_channel_byte_stride * channel_offset;
   1597 
   1598 				// NOTE(rnp): handle the case where the channel count is not a multiple of
   1599 				// BeamformerChunkChannelCount. We do not want to have to modify every shader
   1600 				// that might run to care about this so here we run a copy/clear to pad the
   1601 				// data for the last iteration.
   1602 				b32 special_handling = (cp->channel_count - channel_offset) < BeamformerChunkChannelCount;
   1603 				if unlikely(special_handling) {
   1604 					u32 channel_pad_count = BeamformerChunkChannelCount - (cp->channel_count - channel_offset);
   1605 					u32 input_index = cs->ping_pong_input_index;
   1606 					u64 pp_size     = cs->ping_pong_buffer.size / PING_PONG_BUFFER_SLOTS;
   1607 					u64 copy_size   = cp->raw_channel_byte_stride * (cp->channel_count - channel_offset);
   1608 					u64 copy_offset = rf_pointer - rf->buffer.gpu_pointer;
   1609 					u64 clear_size  = cp->raw_channel_byte_stride * channel_pad_count;
   1610 
   1611 					gpu_command_copy_buffer(cmd, &cs->ping_pong_buffer, input_index * pp_size, &rf->buffer, copy_offset, copy_size);
   1612 					gpu_command_clear_buffer(cmd, &cs->ping_pong_buffer, input_index * pp_size + copy_size, clear_size, 0);
   1613 					// TODO(rnp): technically this should be a transfer to compute barrier
   1614 					// but a full memory barrier will also do the trick
   1615 					gpu_command_pipeline_barrier(cmd, 1);
   1616 				}
   1617 
   1618 				for (u32 i = 0; i < cp->first_image_shader_index; i++) {
   1619 					u32 output_index      = !cs->ping_pong_input_index;
   1620 					u32 input_index       =  cs->ping_pong_input_index;
   1621 
   1622 					u64 pp_size           = cs->ping_pong_buffer.size / PING_PONG_BUFFER_SLOTS;
   1623 					u64 pp_input_pointer  = cs->ping_pong_buffer.gpu_pointer + input_index  * pp_size;
   1624 					u64 pp_output_pointer = cs->ping_pong_buffer.gpu_pointer + output_index * pp_size;
   1625 
   1626 					u64 output_pointer = pp_output_pointer;
   1627 					u64 input_pointer  = (i == 0 && !special_handling) ? rf_pointer : pp_input_pointer;
   1628 
   1629 					if (i != 0) gpu_command_pipeline_barrier(cmd, pipeline_memory_barrier);
   1630 					do_compute_shader(cmd, cp, frame, input_pointer, output_pointer, i, channel_offset);
   1631 					gpu_command_timestamp(cmd);
   1632 				}
   1633 			}
   1634 
   1635 			for (u32 i = cp->first_image_shader_index; i < cp->pipeline.shader_count; i++) {
   1636 				gpu_command_pipeline_barrier(cmd, pipeline_memory_barrier);
   1637 				do_compute_shader(cmd, cp, frame, 0, 0, i, 0);
   1638 				gpu_command_timestamp(cmd);
   1639 			}
   1640 
   1641 			u32 wait_info_count = 0;
   1642 			GPUSemaphoreSignalInfo wait_info = {.semaphore = rf->upload_semaphore};
   1643 			if (work->kind == BeamformerWorkKind_WaitThenCompute) {
   1644 				spin_wait(atomic_load_u64(&rf->insertion_index) <= compute_index);
   1645 				wait_info.value = atomic_load_u64(rf->upload_complete_values + slot);
   1646 				wait_info_count = 1;
   1647 			}
   1648 
   1649 			u64 end_timeline_value = gpu_command_list_end(cmd, &wait_info, wait_info_count, 0, 0);
   1650 			atomic_store_u64(rf->compute_complete_values + slot, end_timeline_value);
   1651 
   1652 			if (work->kind == BeamformerWorkKind_WaitThenCompute)
   1653 				atomic_add_u64(&rf->compute_index, 1);
   1654 
   1655 			atomic_store_u64(&frame->timeline_valid_value, end_timeline_value);
   1656 
   1657 			Temp scratch;
   1658 			DeferLoop(scratch = temp_begin(arena), temp_end(scratch))
   1659 			{
   1660 				/* NOTE(rnp): this blocks until work completes */
   1661 				u64  count       = 0;
   1662 				u64 *timestamps  = gpu_read_timestamps(GPUTimeline_Compute, &count, arena);
   1663 
   1664 				i32 steps        = ((i32)cp->channel_count / BeamformerChunkChannelCount) - 1;
   1665 				i32 step         = 0;
   1666 				u32 shader_index = 0;
   1667 				u64 last_time    = count > 0 ? timestamps[0] : 0;
   1668 
   1669 				for (u64 i = 1; i < count; i++) {
   1670 					push_compute_timing_info(ctx->compute_timing_table, (ComputeTimingInfo){
   1671 						.kind        = ComputeTimingInfoKind_Shader,
   1672 						.shader      = cp->pipeline.shaders[shader_index],
   1673 						.shader_slot = shader_index,
   1674 						.timer_count = timestamps[i] - last_time,
   1675 					});
   1676 					last_time = timestamps[i];
   1677 
   1678 					shader_index++;
   1679 					if (shader_index == cp->first_image_shader_index && step < steps) {
   1680 						shader_index = 0;
   1681 						step++;
   1682 					}
   1683 				}
   1684 			}
   1685 
   1686 			cs->processing_progress = 1;
   1687 
   1688 			//if (has_sum) {
   1689 			if (0) {
   1690 				#if 0
   1691 				u32 aframe_index = ((ctx->averaged_frame_index++) % countof(ctx->averaged_frames));
   1692 				ctx->averaged_frames[aframe_index].view_plane_tag  = frame->view_plane_tag;
   1693 				ctx->averaged_frames[aframe_index].ready_to_present = 1;
   1694 				atomic_store_u64((u64 *)&ctx->latest_frame, (u64)(ctx->averaged_frames + aframe_index));
   1695 				#endif
   1696 			} else {
   1697 				atomic_store_u64((u64 *)&ctx->latest_frame, (u64)frame);
   1698 			}
   1699 
   1700 			atomic_store_u32(&cs->processing_compute, 0);
   1701 
   1702 			push_compute_timing_info(ctx->compute_timing_table,
   1703 			                         (ComputeTimingInfo){.kind = ComputeTimingInfoKind_ComputeFrameEnd});
   1704 
   1705 			end_renderdoc_capture();
   1706 		}break;
   1707 		InvalidDefaultCase;
   1708 		}
   1709 	}
   1710 }
   1711 
   1712 function void
   1713 coalesce_timing_table(ComputeTimingTable *t, ComputeShaderStats *stats)
   1714 {
   1715 	/* TODO(rnp): we do not currently do anything to handle the potential for a half written
   1716 	 * info item. this could result in garbage entries but they shouldn't really matter */
   1717 
   1718 	u32 target = atomic_load_u32(&t->write_index);
   1719 	u32 stats_index = stats->latest_frame_index;
   1720 
   1721 	b32 has_rf = 0;
   1722 	f32 gpu_clocks_to_nano = 1.0e-9f * gpu_info()->timestamp_period_ns;
   1723 
   1724 	// NOTE(rnp): not equal (the index may wrap)
   1725 	while (t->read_index != target) {
   1726 		ComputeTimingInfo info = t->buffer[t->read_index % countof(t->buffer)];
   1727 		switch (info.kind) {
   1728 
   1729 		case ComputeTimingInfoKind_ComputeFrameBegin:{
   1730 			assert(t->compute_frame_active == 0);
   1731 			t->compute_frame_active = 1;
   1732 			/* NOTE(rnp): allow multiple instances of same shader to accumulate */
   1733 			t->in_flight_shader_count = 0;
   1734 			memory_clear(t->in_flight_shader_ids, 0, sizeof(t->in_flight_shader_ids));
   1735 			memory_clear(stats->table.times[stats_index], 0, sizeof(stats->table.times[stats_index]));
   1736 		}break;
   1737 
   1738 		case ComputeTimingInfoKind_ComputeFrameEnd:{
   1739 			assert(t->compute_frame_active == 1);
   1740 			t->compute_frame_active = 0;
   1741 			stats_index = stats->latest_frame_index = (stats_index + 1) % countof(stats->table.times);
   1742 			stats->table.shader_count = t->in_flight_shader_count;
   1743 			memory_copy(stats->table.shader_ids, t->in_flight_shader_ids, sizeof(t->in_flight_shader_ids));
   1744 		}break;
   1745 
   1746 		case ComputeTimingInfoKind_Shader:{
   1747 			t->in_flight_shader_count = Max(t->in_flight_shader_count, info.shader_slot + 1u);
   1748 			t->in_flight_shader_ids[info.shader_slot] = info.shader;
   1749 			stats->table.times[stats_index][info.shader_slot] += info.timer_count * gpu_clocks_to_nano;
   1750 		}break;
   1751 
   1752 		case ComputeTimingInfoKind_RF_Data:{
   1753 			stats->latest_rf_index = (stats->latest_rf_index + 1) % countof(stats->table.rf_time_deltas);
   1754 			f32 delta = info.timer_count / (f32)os_system_info()->timer_frequency;
   1755 			stats->table.rf_time_deltas[stats->latest_rf_index] = delta;
   1756 			has_rf = 1;
   1757 		}break;
   1758 		}
   1759 		/* NOTE(rnp): do this at the end so that stats table is always in a consistent state */
   1760 		t->read_index++;
   1761 	}
   1762 
   1763 	for (u32 i = 0; i < stats->table.shader_count; i++) {
   1764 		f32 sum = 0;
   1765 		for EachElement(stats->table.times, it)
   1766 			sum += stats->table.times[it][i];
   1767 		stats->average_times[i] = sum / countof(stats->table.times);
   1768 	}
   1769 
   1770 	if (has_rf) {
   1771 		f32 sum = 0;
   1772 		for EachElement(stats->table.rf_time_deltas, i)
   1773 			sum += stats->table.rf_time_deltas[i];
   1774 		stats->rf_time_delta_average = sum / countof(stats->table.rf_time_deltas);
   1775 	}
   1776 }
   1777 
   1778 DEBUG_EXPORT BEAMFORMER_COMPLETE_COMPUTE_FN(beamformer_complete_compute)
   1779 {
   1780 	BeamformerSharedMemory *sm = ctx->shared_memory;
   1781 	complete_queue(ctx, &sm->external_work_queue, arena);
   1782 	complete_queue(ctx, ctx->beamform_work_queue, arena);
   1783 }
   1784 
   1785 DEBUG_EXPORT BEAMFORMER_RF_UPLOAD_FN(beamformer_rf_upload)
   1786 {
   1787 	BeamformerSharedMemory *sm                  = ctx->shared_memory;
   1788 	BeamformerSharedMemoryLockKind scratch_lock = BeamformerSharedMemoryLockKind_ScratchSpace;
   1789 	BeamformerSharedMemoryLockKind upload_lock  = BeamformerSharedMemoryLockKind_UploadRF;
   1790 
   1791 	u64 rf_block_rf_size;
   1792 	if (atomic_load_u32(sm->locks + upload_lock) &&
   1793 	    (rf_block_rf_size = atomic_swap_u64(&sm->rf_block_rf_size, 0)))
   1794 	{
   1795 		beamformer_shared_memory_take_lock(ctx->shared_memory, (i32)scratch_lock, (u32)-1);
   1796 
   1797 		BeamformerRFBuffer *rf = ctx->rf_buffer;
   1798 
   1799 		rf->active_rf_size = gpu_round_up_to_sync_size(rf_block_rf_size & 0x00FFFFFFFFFFFFFFull, 64);
   1800 		if unlikely((u64)rf->buffer.size < countof(rf->upload_complete_values) * rf->active_rf_size) {
   1801 			gpu_buffer_allocate(&rf->buffer, (GPUBufferAllocateInfo){
   1802 				.size  = countof(rf->upload_complete_values) * rf->active_rf_size,
   1803 				.flags = GPUUsageFlag_HostWrite,
   1804 				.label = str8("RawRFBuffer"),
   1805 				.timelines_used = (GPUTimeline []){GPUTimeline_Compute},
   1806 				.timeline_count = 1,
   1807 			});
   1808 		}
   1809 
   1810 		u64 slot = rf->insertion_index % countof(rf->upload_complete_values);
   1811 
   1812 		/* NOTE(rnp): don't overwrite slot if the compute thread hasn't processed it */
   1813 		spin_wait(rf->insertion_index - atomic_load_u64(&rf->compute_index) >= countof(rf->upload_complete_values));
   1814 		gpu_host_wait_timeline(GPUTimeline_Compute, rf->compute_complete_values[slot], -1ULL);
   1815 
   1816 		assert((ctx->shared_memory_size % os_system_info()->page_size) == 0 &&
   1817 		       (os_system_info()->page_size % gpu_round_up_to_sync_size(1, 64)) == 0);
   1818 
   1819 		u64 wait_value = gpu_semaphore_value(rf->upload_semaphore) + 1;
   1820 		atomic_store_u64(rf->upload_complete_values + slot, wait_value);
   1821 		atomic_add_u64(&rf->insertion_index, 1);
   1822 
   1823 		void *host_pointer = gpu_buffer_host_pointer(&rf->buffer);
   1824 		memory_copy_non_temporal((u8 *)host_pointer + slot * rf->active_rf_size,
   1825 		                         beamformer_shared_memory_data_pointer(sm, ctx->shared_memory_size),
   1826 		                         rf->active_rf_size);
   1827 		store_fence();
   1828 
   1829 		GPUSemaphoreSignalInfo signal_info = {.semaphore = rf->upload_semaphore, .value = wait_value};
   1830 		gpu_buffer_make_visible(&rf->buffer, slot * rf->active_rf_size, rf->active_rf_size, &signal_info, 1);
   1831 
   1832 		beamformer_shared_memory_release_lock(ctx->shared_memory, (i32)scratch_lock);
   1833 		post_sync_barrier(ctx->shared_memory, upload_lock);
   1834 		os_wake_all_waiters(ctx->compute_worker_sync);
   1835 
   1836 		u64 current_time = os_timer_count();
   1837 		push_compute_timing_info(ctx->compute_timing_table, (ComputeTimingInfo){
   1838 			.kind        = ComputeTimingInfoKind_RF_Data,
   1839 			.timer_count = current_time - rf->timestamp,
   1840 		});
   1841 		rf->timestamp = current_time;
   1842 	}
   1843 }
   1844 
   1845 function void
   1846 beamformer_queue_compute(BeamformerCtx *ctx, BeamformerComputeWorkContext cw)
   1847 {
   1848 	BeamformerSharedMemory *sm = ctx->shared_memory;
   1849 	BeamformerSharedMemoryLockKind dispatch_lock = BeamformerSharedMemoryLockKind_DispatchCompute;
   1850 	if (!sm->live_imaging_parameters.active && beamformer_shared_memory_take_lock(sm, (i32)dispatch_lock, 0))
   1851 	{
   1852 		BeamformWork *work = beamform_work_queue_push(ctx->beamform_work_queue);
   1853 		if (work) {
   1854 			work->kind = BeamformerWorkKind_Compute;
   1855 			memory_copy(&work->compute_context, &cw, sizeof(cw));
   1856 			beamform_work_queue_push_commit(ctx->beamform_work_queue);
   1857 		}
   1858 	}
   1859 	os_wake_all_waiters(&ctx->compute_worker.sync_variable);
   1860 }
   1861 
   1862 #include "ui.c"
   1863 
   1864 function void
   1865 beamformer_process_input_events(BeamformerCtx *ctx, BeamformerInput *input,
   1866                                 BeamformerInputEvent *events, u32 event_count)
   1867 {
   1868 	for (u32 index = 0; index < event_count; index++) {
   1869 		BeamformerInputEvent *event = events + index;
   1870 		switch (event->kind) {
   1871 
   1872 		// NOTE(rnp): ui will handle these
   1873 		case BeamformerInputEventKind_ButtonPress:
   1874 		case BeamformerInputEventKind_ButtonRelease:
   1875 		case BeamformerInputEventKind_MouseScroll:
   1876 		case BeamformerInputEventKind_WindowResize:
   1877 		{}break;
   1878 
   1879 		case BeamformerInputEventKind_ExecutableReload:{
   1880 			ui_init(ctx, ctx->ui_arena);
   1881 		}break;
   1882 
   1883 		case BeamformerInputEventKind_FileEvent:{
   1884 			BeamformerFileReloadContext *frc = event->file_watch_user_context;
   1885 			switch (frc->kind) {
   1886 			case BeamformerFileReloadKind_ComputeShader:{
   1887 				for EachElement(ctx->compute_context.compute_plans, block) {
   1888 					BeamformerComputePlan *cp = ctx->compute_context.compute_plans[block];
   1889 					for (u32 slot = 0; cp && slot < cp->pipeline.shader_count; slot++) {
   1890 						i32 shader_index = beamformer_shader_reloadable_index_by_shader[cp->pipeline.shaders[slot]];
   1891 						if (beamformer_reloadable_shader_kinds[shader_index] == frc->shader_reload.shader)
   1892 							atomic_or_u32(&cp->dirty_programs, 1 << slot);
   1893 					}
   1894 				}
   1895 
   1896 				// TODO(rnp): track latest parameter block
   1897 				if (ctx->latest_frame) {
   1898 					BeamformerFrame *f = ctx->latest_frame;
   1899 					beamformer_queue_compute(ctx, (BeamformerComputeWorkContext){
   1900 						.voxel_transform   = f->voxel_transform,
   1901 						.rf_offset         = 0,
   1902 						.focus_depth       = f->focus_depth,
   1903 						.steering_angle    = f->steering_angle,
   1904 						.tx_rx_orientation = f->transmit_receive_orientation,
   1905 						.parameter_set     = 0,
   1906 						.view_plane        = f->view_plane_tag,
   1907 					});
   1908 				}
   1909 			}break;
   1910 
   1911 			case BeamformerFileReloadKind_RenderShader:{
   1912 				beamformer_reload_render_pipeline(frc->shader_reload.pipeline, frc->shader_reload.shader, ctx->arena);
   1913 				ctx->render_shader_updated = 1;
   1914 			}break;
   1915 
   1916 			InvalidDefaultCase;
   1917 			}
   1918 		}break;
   1919 
   1920 		InvalidDefaultCase;
   1921 		}
   1922 	}
   1923 }
   1924 
   1925 function void
   1926 beamformer_panel_group_insert_at(BeamformerUIPanel *group, BeamformerUIPanel *tab, u64 new_child_index)
   1927 {
   1928 	if (tab->parent) beamformer_ui_panel_unlink(tab);
   1929 	new_child_index = Min(new_child_index, group->child_count);
   1930 
   1931 	tab->parent = group;
   1932 	group->child_count++;
   1933 	if (group->kind == BeamformerPanelKind_TabGroup) group->u.tab_focus = tab;
   1934 
   1935 	BeamformerUIPanel *previous_sibling = new_child_index == 0 ? 0 : group->first_child;
   1936 	for (u64 child_index = 1; child_index < new_child_index; child_index++)
   1937 		previous_sibling = previous_sibling->next_sibling;
   1938 
   1939 	if (previous_sibling) {
   1940 		tab->previous_sibling = previous_sibling;
   1941 		tab->next_sibling     = previous_sibling->next_sibling;
   1942 		if (tab->next_sibling) tab->next_sibling->previous_sibling = tab;
   1943 		previous_sibling->next_sibling = tab;
   1944 		if (previous_sibling == group->last_child) group->last_child = tab;
   1945 	} else {
   1946 		DLLInsertFirst(0, group->first_child, group->last_child, tab, next_sibling, previous_sibling);
   1947 	}
   1948 }
   1949 
   1950 BEAMFORMER_EXPORT void
   1951 beamformer_frame_step(void *memory, BeamformerInput *input)
   1952 {
   1953 	BeamformerCtx *ctx = beamformer_context = memory;
   1954 	beamformer_input = input;
   1955 
   1956 	u64 current_time = os_timer_count();
   1957 	dt_for_frame = (f64)(current_time - ctx->frame_timestamp) / os_system_info()->timer_frequency;
   1958 	ctx->frame_timestamp = current_time;
   1959 	ctx->frame_index++;
   1960 
   1961 	coalesce_timing_table(ctx->compute_timing_table, ctx->compute_shader_stats);
   1962 
   1963 	// NOTE(rnp): reset frame state
   1964 	{
   1965 		ctx->registers = &ctx->base_registers;
   1966 		swap(ctx->command_queues[0], ctx->command_queues[1]);
   1967 		zero_struct(ctx->command_queues + 0);
   1968 		//zero_struct(ctx->registers);
   1969 		arena_clear(beamformer_frame_arena());
   1970 	}
   1971 
   1972 	beamformer_process_input_events(ctx, input, input->event_queue, input->event_count);
   1973 
   1974 	BeamformerSharedMemory *sm = ctx->shared_memory;
   1975 	u32 live_imaging_active = atomic_load_u32(&sm->live_imaging_parameters.active);
   1976 	if (live_imaging_active != ctx->live_imaging_active) {
   1977 		if (ctx->live_imaging_active) {
   1978 			if (ctx->auto_live_control_panel) {
   1979 				BeamformerUIPanel *parent = ctx->auto_live_control_panel->parent;
   1980 				beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)ctx->auto_live_control_panel);
   1981 				if (parent->child_count == 1)
   1982 					beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)parent);
   1983 			}
   1984 		} else {
   1985 			if (beamformer_registers()->live_controls) {
   1986 				beamformer_command(beamformer_command_infos[BeamformerCommandKind_FocusTab].string,
   1987 				                   .tree_node = beamformer_registers()->live_controls);
   1988 			} else {
   1989 				ctx->auto_live_control_panel = beamformer_ui_push_panel(0, BeamformerPanelKind_LiveImagingControls);
   1990 				beamformer_command(beamformer_command_infos[BeamformerCommandKind_SplitTree].string,
   1991 				                   .tree_node        = (u64)ctx->auto_live_control_panel,
   1992 				                   .split_axis       = Axis2_X,
   1993 				                   .split_left_tree  = (u64)ui_context->tree,
   1994 				                   .split_right_tree = 0,
   1995 				                   .drop_target_tree = (u64)ui_context->tree);
   1996 			}
   1997 			ctx->live_imaging_active_frame = ctx->frame_index;
   1998 		}
   1999 		ctx->live_imaging_active = live_imaging_active;
   2000 	}
   2001 
   2002 	if (atomic_load_u32(sm->locks + BeamformerSharedMemoryLockKind_UploadRF))
   2003 		os_wake_all_waiters(&ctx->upload_worker.sync_variable);
   2004 	if (atomic_load_u32(sm->locks + BeamformerSharedMemoryLockKind_DispatchCompute))
   2005 		os_wake_all_waiters(&ctx->compute_worker.sync_variable);
   2006 
   2007 	beamformer_registers()->frame = (u64)(ctx->latest_frame - ctx->compute_context.backlog.frames);
   2008 
   2009 	beamformer_ui_frame();
   2010 
   2011 	// NOTE(rnp): execute commands
   2012 	for (BeamformerCommandNode *node = ctx->command_queues[0].first;
   2013 	     node;
   2014 	     node = node == node->next ? 0 : node->next)
   2015 	{
   2016 		BeamformerRegistersScope()
   2017 		{
   2018 			memory_copy(beamformer_registers(), node->command.registers, sizeof(*node->command.registers));
   2019 			BeamformerCommandKind kind = beamformer_command_kind_from_string(node->command.name);
   2020 			switch (kind) {
   2021 			InvalidDefaultCase;
   2022 			case BeamformerCommandKind_CloseTab:{
   2023 				BeamformerUIPanel *tab = (BeamformerUIPanel *)beamformer_registers()->tree_node;
   2024 				ui_kill_panel(tab);
   2025 			}break;
   2026 
   2027 			case BeamformerCommandKind_FocusTab:{
   2028 				BeamformerUIPanel *tab = (BeamformerUIPanel *)beamformer_registers()->tree_node;
   2029 				assert(tab->parent->kind == BeamformerPanelKind_TabGroup);
   2030 				tab->parent->u.tab_focus = tab;
   2031 			}break;
   2032 
   2033 			case BeamformerCommandKind_MoveTab:{
   2034 				BeamformerUIPanel *move   = (BeamformerUIPanel *)beamformer_registers()->tree_node;
   2035 				BeamformerUIPanel *group  = (BeamformerUIPanel *)beamformer_registers()->drop_target_tree;
   2036 				BeamformerUIPanel *parent = move->parent;
   2037 				u64 new_child_index = beamformer_registers()->drop_child_index;
   2038 				beamformer_panel_group_insert_at(group, move, new_child_index);
   2039 
   2040 				if (move->kind == BeamformerPanelKind_LiveImagingControls) {
   2041 					beamformer_context->base_registers.v.live_controls = (u64)move;
   2042 					if (move == ctx->auto_live_control_panel)
   2043 						ctx->auto_live_control_panel = 0;
   2044 				}
   2045 
   2046 				if (parent->child_count == 0)
   2047 					beamformer_command(beamformer_command_infos[BeamformerCommandKind_CloseTab].string, .tree_node = (u64)parent);
   2048 			}break;
   2049 
   2050 			case BeamformerCommandKind_OpenTab:{
   2051 				BeamformerUIPanel *panel = (BeamformerUIPanel *)beamformer_registers()->tree_node;
   2052 				assert(panel->kind == BeamformerPanelKind_TabGroup);
   2053 
   2054 				BeamformerPanelKind new_panel_kind = beamformer_panel_kind_from_string(beamformer_registers()->string);
   2055 				beamformer_ui_push_panel(panel, new_panel_kind);
   2056 			}break;
   2057 
   2058 			case BeamformerCommandKind_SplitTree:{
   2059 				BeamformerUIPanel *drag  = (BeamformerUIPanel *)beamformer_registers()->tree_node;
   2060 				BeamformerUIPanel *left  = (BeamformerUIPanel *)beamformer_registers()->split_left_tree;
   2061 				BeamformerUIPanel *right = (BeamformerUIPanel *)beamformer_registers()->split_right_tree;
   2062 				Axis2 axis = beamformer_registers()->split_axis;
   2063 
   2064 				BeamformerUIPanel *new_split     = beamformer_ui_push_panel(0, BeamformerPanelKind_Split);
   2065 				BeamformerUIPanel *new_tab_group = beamformer_ui_push_panel(0, BeamformerPanelKind_TabGroup);
   2066 				beamformer_panel_group_insert_at(new_tab_group, drag, 0);
   2067 
   2068 				BeamformerUIPanel *target = 0;
   2069 				u32 target_child_index = 0;
   2070 				f32 new_split_pct = 0.5f;
   2071 
   2072 				if (left == 0 || right == 0) {
   2073 					// NOTE(rnp): split on edge of window
   2074 					target             = left ? left : right;
   2075 					target_child_index = left ? 0 : 1;
   2076 
   2077 					if (target->kind == BeamformerPanelKind_TabGroup) {
   2078 						new_split->kind        = BeamformerPanelKind_TabGroup;
   2079 						new_split->u.tab_focus = target->u.tab_focus;
   2080 					}
   2081 
   2082 					for (BeamformerUIPanel *child = target->last_child, *next; child; child = next) {
   2083 						next = child->previous_sibling;
   2084 						beamformer_panel_group_insert_at(new_split, child, 0);
   2085 					}
   2086 
   2087 					beamformer_panel_group_insert_at(target, new_tab_group, 0);
   2088 				} else if (((drag == left)  && right->kind == BeamformerPanelKind_Split) ||
   2089 				           ((drag == right) && left->kind  == BeamformerPanelKind_Split))
   2090 				{
   2091 					// NOTE(rnp): split on internal split
   2092 					target             = left == drag ? right : left;
   2093 					target_child_index = 1;
   2094 					new_split_pct      = 1.f / 3.f;
   2095 					beamformer_panel_group_insert_at(new_split, new_tab_group, 0);
   2096 					beamformer_panel_group_insert_at(new_split, target->last_child, 1);
   2097 				} else {
   2098 					// NOTE(rnp): TabGroup Split
   2099 					target             = left == drag ? right : left;
   2100 					target_child_index = left == drag ? 1 : 0;
   2101 					assert(target->kind == BeamformerPanelKind_TabGroup);
   2102 
   2103 					BeamformerUIPanel *focus = target->u.tab_focus;
   2104 					new_split->kind = BeamformerPanelKind_TabGroup;
   2105 					for (BeamformerUIPanel *child = target->last_child, *next; child; child = next) {
   2106 						next = child->previous_sibling;
   2107 						beamformer_panel_group_insert_at(new_split, child, 0);
   2108 					}
   2109 					new_split->u.tab_focus = focus;
   2110 
   2111 					beamformer_panel_group_insert_at(target, new_tab_group, 0);
   2112 				}
   2113 
   2114 				beamformer_panel_group_insert_at(target, new_split, target_child_index);
   2115 				if (target->kind == BeamformerPanelKind_Split) {
   2116 					new_split->u.split.axis     = target->u.split.axis;
   2117 					new_split->u.split.fraction = target->u.split.fraction;
   2118 				}
   2119 				target->kind             = BeamformerPanelKind_Split;
   2120 				target->u.split.axis     = axis;
   2121 				target->u.split.fraction = new_split_pct;
   2122 			}break;
   2123 
   2124 			}
   2125 		}
   2126 	}
   2127 
   2128 	ctx->render_shader_updated = 0;
   2129 }