ogl_beamforming

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

Commit: 8f2090b23db12a1eb3d00073924cea207d0a2b4e
Parent: 0ca3fddb96126960f05807675b2da3d7a7fdf1ba
Author: Randy Palamar
Date:   Sun, 13 Apr 2025 21:42:56 -0600

core/ui: don't use raylib for rendering beamformed data

Diffstat:
Mbeamformer.c | 81++++++++++++-------------------------------------------------------------------
Mbeamformer.h | 26+++++++++++++++-----------
Mshaders/render.glsl | 13++-----------
Mstatic.c | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mui.c | 151+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mutil.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.h | 2++
7 files changed, 262 insertions(+), 167 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -391,56 +391,6 @@ do_compute_shader(BeamformerCtx *ctx, Arena arena, BeamformComputeFrame *frame, } } -static u32 -compile_shader(OS *os, Arena a, u32 type, s8 shader, s8 name) -{ - u32 sid = glCreateShader(type); - glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len); - glCompileShader(sid); - - i32 res = 0; - glGetShaderiv(sid, GL_COMPILE_STATUS, &res); - - if (res == GL_FALSE) { - Stream buf = arena_stream(&a); - stream_append_s8(&buf, name); - stream_append_s8(&buf, s8(": failed to compile\n")); - - i32 len = 0, out_len = 0; - glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len); - glGetShaderInfoLog(sid, len, &out_len, (char *)(buf.data + buf.widx)); - stream_commit(&buf, out_len); - glDeleteShader(sid); - os->write_file(os->stderr, stream_to_s8(&buf)); - - sid = 0; - } - - return sid; -} - -static u32 -link_program(OS *os, Arena a, u32 shader_id) -{ - i32 success = 0; - u32 result = glCreateProgram(); - glAttachShader(result, shader_id); - glLinkProgram(result); - glGetProgramiv(result, GL_LINK_STATUS, &success); - if (success == GL_FALSE) { - i32 len = 0; - Stream buf = arena_stream(&a); - stream_append_s8(&buf, s8("shader link error: ")); - glGetProgramInfoLog(result, buf.cap - buf.widx, &len, (c8 *)(buf.data + buf.widx)); - stream_reset(&buf, len); - stream_append_byte(&buf, '\n'); - os->write_file(os->stderr, stream_to_s8(&buf)); - glDeleteProgram(result); - result = 0; - } - return result; -} - function s8 push_compute_shader_header(Arena *a, b32 parameters, ComputeShaderID shader) { @@ -510,24 +460,17 @@ reload_compute_shader(BeamformerCtx *ctx, s8 path, s8 extra, ComputeShaderReload shader_text.len += header.len; if (shader_text.data == header.data) { - u32 shader_id = compile_shader(&ctx->os, tmp, GL_COMPUTE_SHADER, shader_text, path); - if (shader_id) { - u32 new_program = link_program(&ctx->os, tmp, shader_id); - if (new_program) { - Stream buf = arena_stream(&tmp); - stream_append_s8(&buf, s8("loaded: ")); - stream_append_s8(&buf, path); - stream_append_s8(&buf, extra); - stream_append_byte(&buf, '\n'); - ctx->os.write_file(ctx->os.stderr, stream_to_s8(&buf)); - glDeleteProgram(cs->programs[csr->shader]); - cs->programs[csr->shader] = new_program; - glUseProgram(cs->programs[csr->shader]); - glBindBufferBase(GL_UNIFORM_BUFFER, 0, cs->shared_ubo); - LABEL_GL_OBJECT(GL_PROGRAM, cs->programs[csr->shader], csr->label); - result = 1; - } - glDeleteShader(shader_id); + s8 info = {.data = tmp.beg}; + push_s8(&tmp, path); + push_s8(&tmp, extra); + info.len = tmp.beg - info.data; + u32 new_program = load_shader(&ctx->os, tmp, 1, (s8){0}, (s8){0}, shader_text, + info, csr->label); + if (new_program) { + glDeleteProgram(cs->programs[csr->shader]); + cs->programs[csr->shader] = new_program; + glUseProgram(cs->programs[csr->shader]); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, cs->shared_ubo); } } else { Stream buf = arena_stream(&tmp); @@ -826,7 +769,7 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) draw_ui(ctx, input, frame_to_draw->ready_to_present? &frame_to_draw->frame : 0, frame_to_draw->image_plane_tag, &frame_to_draw->stats); - ctx->fsctx.updated = 0; + ctx->frame_view_render_context.updated = 0; if (WindowShouldClose()) ctx->should_exit = 1; diff --git a/beamformer.h b/beamformer.h @@ -53,15 +53,18 @@ typedef struct { #undef X } CudaLib; +#define FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC 1 +#define FRAME_VIEW_RENDER_THRESHOLD_LOC 2 +#define FRAME_VIEW_RENDER_GAMMA_LOC 3 +#define FRAME_VIEW_RENDER_LOG_SCALE_LOC 4 + typedef struct { - Shader shader; - b32 updated; - /* TODO(rnp): cleanup! */ - i32 db_cutoff_id; - i32 threshold_id; - i32 gamma_id; - i32 log_scale_id; -} FragmentShaderCtx; + u32 shader; + u32 framebuffer; + u32 vao; + u32 vbo; + b32 updated; +} FrameViewRenderContext; #include "beamformer_parameters.h" #include "beamformer_work_queue.h" @@ -172,7 +175,10 @@ typedef struct { BeamformComputeFrame averaged_frames[2]; ComputeShaderCtx csctx; - FragmentShaderCtx fsctx; + + /* TODO(rnp): ideally this would go in the UI but its hard to manage with the UI + * destroying itself on hot-reload */ + FrameViewRenderContext frame_view_render_context; Arena export_buffer; @@ -193,8 +199,6 @@ struct ComputeShaderReloadContext { b32 needs_header; }; -#define LABEL_GL_OBJECT(type, id, s) {s8 _s = (s); glObjectLabel(type, id, _s.len, (c8 *)_s.data);} - #define BEAMFORMER_FRAME_STEP_FN(name) void name(BeamformerCtx *ctx, Arena *arena, \ BeamformerInput *input) typedef BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step_fn); diff --git a/shaders/render.glsl b/shaders/render.glsl @@ -1,14 +1,5 @@ /* See LICENSE for license details. */ -#version 430 core - -in vec2 fragTexCoord; -out vec4 v_out_colour; - -layout(binding = 0) uniform sampler3D u_out_data_tex; -layout(location = 1) uniform float u_db_cutoff = 60; -layout(location = 2) uniform float u_threshold = 40; -layout(location = 3) uniform float u_gamma = 1; -layout(location = 4) uniform bool u_log_scale; +layout(binding = 0) uniform sampler3D u_out_data_tex; /* input: h [0,360] | s,v [0, 1] * * output: rgb [0,1] */ @@ -26,7 +17,7 @@ void main() //vec2 min_max = texelFetch(u_out_data_tex, ivec3(0), textureQueryLevels(u_out_data_tex) - 1).xy; /* TODO(rnp): select between x and y and specify slice */ - ivec2 coord = ivec2(fragTexCoord * vec2(out_data_dim)); + ivec2 coord = ivec2(fragment_texture_coordinate * vec2(out_data_dim)); ivec3 smp_coord = ivec3(coord.x, 0, coord.y); float smp = length(texelFetch(u_out_data_tex, smp_coord, 0).xy); diff --git a/static.c b/static.c @@ -184,20 +184,45 @@ dump_gl_params(GLParams *gl, Arena a, OS *os) #endif } -static FILE_WATCH_CALLBACK_FN(reload_render_shader) +function FILE_WATCH_CALLBACK_FN(reload_render_shader) { - FragmentShaderCtx *ctx = (FragmentShaderCtx *)user_data; - Shader updated_fs = LoadShader(0, (c8 *)path.data); - - if (updated_fs.id) { - UnloadShader(ctx->shader); - LABEL_GL_OBJECT(GL_PROGRAM, updated_fs.id, s8("Render Shader")); - ctx->shader = updated_fs; - ctx->db_cutoff_id = GetShaderLocation(updated_fs, "u_db_cutoff"); - ctx->threshold_id = GetShaderLocation(updated_fs, "u_threshold"); - ctx->gamma_id = GetShaderLocation(updated_fs, "u_gamma"); - ctx->log_scale_id = GetShaderLocation(updated_fs, "u_log_scale"); - ctx->updated = 1; + FrameViewRenderContext *ctx = (typeof(ctx))user_data; + + local_persist s8 vertex = s8("" + "#version 460 core\n" + "\n" + "layout(location = 0) in vec2 vertex_position;\n" + "layout(location = 1) in vec2 vertex_texture_coordinate;\n" + "\n" + "layout(location = 0) out vec2 fragment_texture_coordinate;\n" + "\n" + "void main()\n" + "{\n" + "\tfragment_texture_coordinate = vertex_texture_coordinate;\n" + "\tgl_Position = vec4(vertex_position, 0, 1);\n" + "}\n"); + + Arena *a = &tmp; + s8 header = {.data = a->beg}; + push_s8(a, s8("#version 460 core\n\n")); + push_s8(a, s8("layout(location = 0) in vec2 fragment_texture_coordinate;\n")); + push_s8(a, s8("layout(location = 0) out vec4 v_out_colour;\n\n")); + push_s8(a, s8("layout(location = " str(FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC) ") uniform float u_db_cutoff = 60;\n")); + push_s8(a, s8("layout(location = " str(FRAME_VIEW_RENDER_THRESHOLD_LOC) ") uniform float u_threshold = 40;\n")); + push_s8(a, s8("layout(location = " str(FRAME_VIEW_RENDER_GAMMA_LOC) ") uniform float u_gamma = 1;\n")); + push_s8(a, s8("layout(location = " str(FRAME_VIEW_RENDER_LOG_SCALE_LOC) ") uniform bool u_log_scale;\n")); + push_s8(a, s8("\n#line 1\n")); + header.len = a->beg - header.data; + + s8 fragment = os->read_whole_file(a, (c8 *)path.data); + fragment.data -= header.len; + fragment.len += header.len; + ASSERT(fragment.data == header.data); + u32 new_program = load_shader(os, tmp, 0, vertex, fragment, (s8){0}, path, s8("Render Shader")); + if (new_program) { + glDeleteProgram(ctx->shader); + ctx->shader = new_program; + ctx->updated = 1; } return 1; @@ -355,7 +380,29 @@ setup_beamformer(BeamformerCtx *ctx, Arena *memory) #undef X os_wake_waiters(&worker->sync_variable); + FrameViewRenderContext *fvr = &ctx->frame_view_render_context; + glGenFramebuffers(1, &fvr->framebuffer); + LABEL_GL_OBJECT(GL_FRAMEBUFFER, fvr->framebuffer, s8("Frame View Render Framebuffer")); + f32 vertices[] = { + -1, 1, 0, 0, + -1, -1, 0, 1, + 1, -1, 1, 1, + -1, 1, 0, 0, + 1, -1, 1, 1, + 1, 1, 1, 0, + }; + glGenVertexArrays(1, &fvr->vao); + glBindVertexArray(fvr->vao); + glGenBuffers(1, &fvr->vbo); + glBindBuffer(GL_ARRAY_BUFFER, fvr->vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, 0, 4 * sizeof(f32), 0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 2, GL_FLOAT, 0, 4 * sizeof(f32), (void *)(2 * sizeof(f32))); + glEnableVertexAttribArray(1); + glBindVertexArray(0); + s8 render = s8(static_path_join("shaders", "render.glsl")); - reload_render_shader(&ctx->os, render, (iptr)&ctx->fsctx, *memory); - os_add_file_watch(&ctx->os, memory, render, reload_render_shader, (iptr)&ctx->fsctx); + reload_render_shader(&ctx->os, render, (iptr)fvr, *memory); + os_add_file_watch(&ctx->os, memory, render, reload_render_shader, (iptr)fvr); } diff --git a/ui.c b/ui.c @@ -1,13 +1,24 @@ /* See LICENSE for license details. */ /* TODO(rnp): - * [ ]: ui should be in its own thread and that thread should only be concerned with the ui + * [ ]: refactor: ui should be in its own thread and that thread should only be concerned with the ui + * [ ]: refactor: ui shouldn't fully destroy itself on hot reload + * [ ]: refactor: move remaining fragment shader stuff into ui + * [ ]: refactor: re-add next_hot variable. this will simplify the code and number of checks + * being performed inline. example: + * if (hovering) + * next_hot = var; + * draw_text(..., var->hover_t); + * // elsewhere in code + * if (!is->active) + * hot = next_hot .... + * + * hot->hover_t += hover_speed * dt_for_frame * [ ]: scroll bar for views that don't have enough space * [ ]: compute times through same path as parameter list ? * [ ]: allow views to collapse to just their title bar * - title bar struct with expanded. Check when pushing onto draw stack; if expanded * do normal behaviour else make size title bar size and ignore the splits fraction. * [ ]: enforce a minimum region size or allow regions themselves to scroll - * [ ]: move remaining fragment shader stuff into ui * [ ]: refactor: draw_left_aligned_list() * [ ]: refactor: draw_variable_table() * [ ]: refactor: add_variable_no_link() @@ -295,11 +306,14 @@ typedef struct BeamformerFrameView { Variable dynamic_range; Variable gamma; - FragmentShaderCtx *ctx; - BeamformFrame *frame; + FrameViewRenderContext *ctx; + BeamformFrame *frame; struct BeamformerFrameView *prev, *next; - RenderTexture2D rendered_view; + uv2 texture_dim; + u32 texture_mipmaps; + u32 texture; + BeamformerFrameViewType type; b32 needs_update; } BeamformerFrameView; @@ -343,7 +357,7 @@ struct BeamformerUI { BeamformerUIParameters params; b32 flush_params; - FragmentShaderCtx *fsctx; + FrameViewRenderContext *frame_view_render_context; OS *os; }; @@ -401,6 +415,18 @@ clamp_text_to_width(Font font, s8 text, f32 limit) return result; } +function Texture +make_raylib_texture(BeamformerFrameView *v) +{ + Texture result; + result.id = v->texture; + result.width = v->texture_dim.w; + result.height = v->texture_dim.h; + result.mipmaps = v->texture_mipmaps; + result.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + return result; +} + static void stream_append_variable_base(Stream *s, VariableType type, void *var, void *scale) { @@ -447,31 +473,27 @@ stream_append_variable(Stream *s, Variable *var) } } -static void +function void resize_frame_view(BeamformerFrameView *view, uv2 dim) { - UnloadRenderTexture(view->rendered_view); - /* TODO(rnp): sometimes when accepting data on w32 something happens - * and the program will stall in vprintf in TraceLog(...) here. - * for now do this to avoid the problem */ - //SetTraceLogLevel(LOG_NONE); - view->rendered_view = LoadRenderTexture(dim.x, dim.y); - //SetTraceLogLevel(LOG_INFO); - - /* TODO(rnp): add some ID for the specific view here */ - LABEL_GL_OBJECT(GL_FRAMEBUFFER, view->rendered_view.id, s8("Frame View")); - LABEL_GL_OBJECT(GL_TEXTURE, view->rendered_view.texture.id, s8("Frame View Texture")); - glGenerateTextureMipmap(view->rendered_view.texture.id); + glDeleteTextures(1, &view->texture); + glCreateTextures(GL_TEXTURE_2D, 1, &view->texture); - //SetTextureFilter(view->rendered_view.texture, TEXTURE_FILTER_ANISOTROPIC_8X); - //SetTextureFilter(view->rendered_view.texture, TEXTURE_FILTER_TRILINEAR); - //SetTextureFilter(view->rendered_view.texture, TEXTURE_FILTER_BILINEAR); + view->texture_dim = dim; + view->texture_mipmaps = ctz_u32(MAX(dim.x, dim.y)) + 1; + /* TODO(rnp): HDR? */ + glTextureStorage2D(view->texture, view->texture_mipmaps, GL_RGBA8, dim.x, dim.y); + glGenerateTextureMipmap(view->texture); /* NOTE(rnp): work around raylib's janky texture sampling */ - i32 id = view->rendered_view.texture.id; - glTextureParameteri(id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTextureParameteri(id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTextureParameterfv(id, GL_TEXTURE_BORDER_COLOR, (f32 []){0, 0, 0, 1}); + glTextureParameteri(view->texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTextureParameteri(view->texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTextureParameterfv(view->texture, GL_TEXTURE_BORDER_COLOR, (f32 []){0, 0, 0, 1}); + glTextureParameteri(view->texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTextureParameteri(view->texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + /* TODO(rnp): add some ID for the specific view here */ + LABEL_GL_OBJECT(GL_TEXTURE, view->texture, s8("Frame View Texture")); } static void @@ -842,7 +864,7 @@ ui_fill_live_frame_view(BeamformerUI *ui, BeamformerFrameView *bv) bv->lateral_scale_bar.causes_compute = 1; bv->axial_scale_bar_active->u.b32 = 1; bv->lateral_scale_bar_active->u.b32 = 1; - bv->ctx = ui->fsctx; + bv->ctx = ui->frame_view_render_context; } function void @@ -906,7 +928,7 @@ static b32 view_update(BeamformerUI *ui, BeamformerFrameView *view) { b32 needs_resize = 0; - uv2 current = {.w = view->rendered_view.texture.width, .h = view->rendered_view.texture.height}; + uv2 current = view->texture_dim; if (view->type == FVT_LATEST) { u32 index = view->cycler->u.cycler.state % (IPT_LAST + 1); view->needs_update |= view->frame != ui->latest_plane[index]; @@ -928,36 +950,46 @@ view_update(BeamformerUI *ui, BeamformerFrameView *view) return (view->ctx->updated || view->needs_update) && view->frame; } -static void -update_frame_views(BeamformerUI *ui) +function void +update_frame_views(BeamformerUI *ui, Rect window) { + b32 fbo_bound = 0; for (BeamformerFrameView *view = ui->views; view; view = view->next) { if (view_update(ui, view)) { - BeamformFrame *frame = view->frame; - BeginTextureMode(view->rendered_view); - ClearBackground(PINK); - BeginShaderMode(view->ctx->shader); - glUseProgram(view->ctx->shader.id); - glBindTextureUnit(0, frame->texture); - glUniform1f(view->ctx->db_cutoff_id, view->dynamic_range.u.f32); - glUniform1f(view->ctx->threshold_id, view->threshold.u.f32); - glUniform1f(view->ctx->gamma_id, view->gamma.u.scaled_f32.val); - glUniform1ui(view->ctx->log_scale_id, view->log_scale->u.b32); - DrawTexture(view->rendered_view.texture, 0, 0, WHITE); - EndShaderMode(); - EndTextureMode(); - glGenerateTextureMipmap(view->rendered_view.texture.id); + if (!fbo_bound) { + fbo_bound = 1; + glBindFramebuffer(GL_FRAMEBUFFER, view->ctx->framebuffer); + glUseProgram(view->ctx->shader); + glBindVertexArray(view->ctx->vao); + glViewport(0, 0, view->texture_dim.w, view->texture_dim.h); + glClearColor(0.79, 0.46, 0.77, 1); + } + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, view->texture, 0); + glClear(GL_COLOR_BUFFER_BIT); + glBindTextureUnit(0, view->frame->texture); + glUniform1f(FRAME_VIEW_RENDER_DYNAMIC_RANGE_LOC, view->dynamic_range.u.f32); + glUniform1f(FRAME_VIEW_RENDER_THRESHOLD_LOC, view->threshold.u.f32); + glUniform1f(FRAME_VIEW_RENDER_GAMMA_LOC, view->gamma.u.scaled_f32.val); + glUniform1ui(FRAME_VIEW_RENDER_LOG_SCALE_LOC, view->log_scale->u.b32); + + glDrawArrays(GL_TRIANGLES, 0, 6); + glGenerateTextureMipmap(view->texture); view->needs_update = 0; } } + if (fbo_bound) { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(window.pos.x, window.pos.y, window.size.w, window.size.h); + /* NOTE(rnp): I don't trust raylib to not mess with us */ + glBindVertexArray(0); + } } -static b32 +function b32 frame_view_ready_to_present(BeamformerFrameView *view) { - uv2 render_size = {.w = view->rendered_view.texture.width, - .h = view->rendered_view.texture.height}; - return !uv2_equal((uv2){0}, render_size) && view->frame; + return !uv2_equal((uv2){0}, view->texture_dim) && view->frame; } static Color @@ -1449,10 +1481,9 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa .y = frame->max_coordinate.z - frame->min_coordinate.z, }; - Texture *output = &view->rendered_view.texture; v2 pixels_per_meter = { - .w = (f32)output->width / output_dim.w, - .h = (f32)output->height / output_dim.h, + .w = (f32)view->texture_dim.w / output_dim.w, + .h = (f32)view->texture_dim.h / output_dim.h, }; v2 texture_points = mul_v2(pixels_per_meter, requested_dim); @@ -1464,7 +1495,7 @@ draw_beamformer_frame_view(BeamformerUI *ui, Arena a, Variable *var, Rect displa Rectangle tex_r = {texture_start.x, texture_start.y, texture_points.x, -texture_points.y}; NPatchInfo tex_np = { tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH }; - DrawTextureNPatch(*output, tex_np, vr.rl, (Vector2){0}, 0, WHITE); + DrawTextureNPatch(make_raylib_texture(view), tex_np, vr.rl, (Vector2){0}, 0, WHITE); v2 start_pos = vr.pos; start_pos.y += vr.size.y; @@ -2506,16 +2537,16 @@ ui_init(BeamformerCtx *ctx, Arena store) UnloadFont(ui->small_font); for (BeamformerFrameView *view = ui->views; view; view = view->next) - if (view->rendered_view.id) - UnloadRenderTexture(view->rendered_view); + if (view->texture) + glDeleteTextures(1, &view->texture); } DEBUG_DECL(u8 *arena_start = store.beg); ui = ctx->ui = push_struct(&store, typeof(*ui)); ui->os = &ctx->os; - ui->fsctx = &ctx->fsctx; ui->arena = store; + ui->frame_view_render_context = &ctx->frame_view_render_context; /* TODO: build these into the binary */ /* TODO(rnp): better font, this one is jank at small sizes */ @@ -2599,18 +2630,16 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw } /* NOTE(rnp): can't render to a different framebuffer in the middle of BeginDrawing()... */ - update_frame_views(ui); + Rect window_rect = {.size = {.w = ctx->window_size.w, .h = ctx->window_size.h}}; + update_frame_views(ui, window_rect); BeginDrawing(); ClearBackground(colour_from_normalized(BG_COLOUR)); - v2 mouse = input->mouse; - Rect window_rect = {.size = {.w = ctx->window_size.w, .h = ctx->window_size.h}}; - - draw_ui_regions(ui, window_rect, mouse); + draw_ui_regions(ui, window_rect, input->mouse); if (ui->interaction.type == IT_TEXT) draw_active_text_box(ui, ui->interaction.active); if (ui->interaction.type == IT_MENU) - draw_active_menu(ui, ui->arena, ui->interaction.active, mouse, window_rect); + draw_active_menu(ui, ui->arena, ui->interaction.active, input->mouse, window_rect); EndDrawing(); } diff --git a/util.c b/util.c @@ -807,3 +807,82 @@ fill_hadamard_transpose(i32 *out, i32 *tmp, u32 dim) kronecker_product(out, tmp, (uv2){.x = dim, .y = dim}, hadamard_12_12_transpose, (uv2){.x = 12, .y = 12}); } + +function u32 +compile_shader(OS *os, Arena a, u32 type, s8 shader, s8 name) +{ + u32 sid = glCreateShader(type); + glShaderSource(sid, 1, (const char **)&shader.data, (int *)&shader.len); + glCompileShader(sid); + + i32 res = 0; + glGetShaderiv(sid, GL_COMPILE_STATUS, &res); + + if (res == GL_FALSE) { + Stream buf = arena_stream(&a); + stream_append_s8(&buf, name); + stream_append_s8(&buf, s8(": failed to compile\n")); + + i32 len = 0, out_len = 0; + glGetShaderiv(sid, GL_INFO_LOG_LENGTH, &len); + glGetShaderInfoLog(sid, len, &out_len, (char *)(buf.data + buf.widx)); + stream_commit(&buf, out_len); + glDeleteShader(sid); + os->write_file(os->stderr, stream_to_s8(&buf)); + + sid = 0; + } + + return sid; +} + +function u32 +link_program(OS *os, Arena a, u32 *shader_ids, u32 shader_id_count) +{ + i32 success = 0; + u32 result = glCreateProgram(); + for (u32 i = 0; i < shader_id_count; i++) + glAttachShader(result, shader_ids[i]); + glLinkProgram(result); + glGetProgramiv(result, GL_LINK_STATUS, &success); + if (success == GL_FALSE) { + i32 len = 0; + Stream buf = arena_stream(&a); + stream_append_s8(&buf, s8("shader link error: ")); + glGetProgramInfoLog(result, buf.cap - buf.widx, &len, (c8 *)(buf.data + buf.widx)); + stream_reset(&buf, len); + stream_append_byte(&buf, '\n'); + os->write_file(os->stderr, stream_to_s8(&buf)); + glDeleteProgram(result); + result = 0; + } + return result; +} + +function u32 +load_shader(OS *os, Arena arena, b32 compute, s8 vs_text, s8 fs_text, s8 cs_text, s8 info_name, s8 label) +{ + u32 result = 0; + if (compute) { + u32 shader_id = compile_shader(os, arena, GL_COMPUTE_SHADER, cs_text, info_name); + if (shader_id) result = link_program(os, arena, (u32 []){shader_id}, 1); + glDeleteShader(shader_id); + } else { + u32 fs_id = compile_shader(os, arena, GL_FRAGMENT_SHADER, fs_text, info_name); + u32 vs_id = compile_shader(os, arena, GL_VERTEX_SHADER, vs_text, info_name); + if (fs_id && vs_id) result = link_program(os, arena, (u32 []){vs_id, fs_id}, 2); + glDeleteShader(fs_id); + glDeleteShader(vs_id); + } + + if (result) { + Stream buf = arena_stream(&arena); + stream_append_s8(&buf, s8("loaded: ")); + stream_append_s8(&buf, info_name); + stream_append_byte(&buf, '\n'); + os->write_file(os->stderr, stream_to_s8(&buf)); + LABEL_GL_OBJECT(GL_PROGRAM, result, label); + } + + return result; +} diff --git a/util.h b/util.h @@ -311,6 +311,8 @@ struct OS { DEBUG_DECL(renderdoc_end_frame_capture_fn *end_frame_capture); }; +#define LABEL_GL_OBJECT(type, id, s) {s8 _s = (s); glObjectLabel(type, id, _s.len, (c8 *)_s.data);} + #include "util.c" #endif /* _UTIL_H_ */