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 (80767B)


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