ogl_beamforming

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

Commit: 8a20ea056af8f588c5f76e9df2ece648694953c0
Parent: c83e4c553332bc4d2bbe4a3ccdd1cfacfcb4af87
Author: Randy Palamar
Date:   Sat, 17 Jan 2026 19:26:55 -0700

vulkan: build default/empty compute shader

Diffstat:
Mbuild.c | 30++++++++++++++++++------------
Mexternal/glslang_local/glslang.cpp | 2++
Mutil.c | 8++++++++
Mvulkan.c | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mvulkan.h | 1+
5 files changed, 161 insertions(+), 14 deletions(-)

diff --git a/build.c b/build.c @@ -546,7 +546,7 @@ use_sanitization(void) } function void -cmd_base(Arena *a, CommandList *c, b32 cpp) +cmd_base(Arena *a, CommandList *c, b32 cpp, b32 debug) { Config *o = &config; @@ -561,15 +561,15 @@ cmd_base(Arena *a, CommandList *c, b32 cpp) if (!cpp) cmd_append(a, c, COMMON_CFLAGS); cmd_append(a, c, COMMON_FLAGS); - if (o->debug) cmd_append(a, c, DEBUG_FLAGS); - else cmd_append(a, c, OPTIMIZED_FLAGS); + if (debug) cmd_append(a, c, DEBUG_FLAGS); + else cmd_append(a, c, OPTIMIZED_FLAGS); /* NOTE: ancient gcc bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80454 */ if (is_gcc) cmd_append(a, c, "-Wno-missing-braces"); if (!is_msvc) cmd_append(a, c, "-fms-extensions"); - if (o->debug && is_unix) cmd_append(a, c, "-gdwarf-4"); + if (debug && is_unix) cmd_append(a, c, "-gdwarf-4"); /* NOTE(rnp): need to avoid w32-gcc for ci */ b32 sanitize = use_sanitization(); @@ -590,7 +590,7 @@ check_rebuild_self(Arena arena, i32 argc, char *argv[]) build_fatal("failed to move: %s -> %s", binary, old_name); CommandList c = {0}; - cmd_base(&arena, &c, 0); + cmd_base(&arena, &c, 0, 0); cmd_append(&arena, &c, EXTRA_FLAGS); if (!is_msvc) cmd_append(&arena, &c, "-Wno-unused-function"); cmd_append(&arena, &c, __FILE__, OUTPUT_EXE(binary)); @@ -746,7 +746,7 @@ build_raylib(Arena a) os_copy_file("external/raylib/src/rlgl.h", "external/include/rlgl.h"); CommandList cc = {0}; - cmd_base(&a, &cc, 0); + cmd_base(&a, &cc, 0, config.debug); if (is_unix) cmd_append(&a, &cc, "-D_GLFW_X11"); cmd_append(&a, &cc, "-DPLATFORM_DESKTOP_GLFW"); if (!is_msvc) cmd_append(&a, &cc, "-Wno-unused-but-set-variable"); @@ -781,12 +781,12 @@ build_glslang(Arena a) { b32 result = 1; char *lib = OUTPUT_LIB(OS_STATIC_LIB("glslang")); - if (needs_rebuild(lib, "external/glslang")) { + if (needs_rebuild(lib, "external/glslang", "external/glslang_local/glslang.cpp")) { git_submodule_update(a, "external/glslang"); - os_copy_file("external/glslang/glslang/Include/glslang_c_interface.h", "external/include/glslang_c_interface.h"); + // NOTE(rnp): do not build this with debug symbols. The size explodes because c++ CommandList cc = {0}; - cmd_base(&a, &cc, 1); + cmd_base(&a, &cc, 1, 0); cmd_append(&a, &cc, "-std=c++17", "-fno-rtti", "-fno-exceptions", "-Wno-unused-but-set-variable"); cmd_append(&a, &cc, "-Iexternal/glslang_local", "-Iexternal/glslang"); @@ -820,7 +820,7 @@ function b32 build_helper_library(Arena arena) { CommandList cc = {0}; - cmd_base(&arena, &cc, 0); + cmd_base(&arena, &cc, 0, 0); cmd_append(&arena, &cc, EXTRA_FLAGS); ///////////// @@ -838,7 +838,7 @@ build_helper_library(Arena arena) function void cmd_beamformer_base(Arena *a, CommandList *c) { - cmd_base(a, c, 0); + cmd_base(a, c, 0, config.debug); cmd_append(a, c, "-Iexternal/include"); cmd_append(a, c, EXTRA_FLAGS); cmd_append(a, c, config.bake_shaders? "-DBakeShaders=1" : "-DBakeShaders=0"); @@ -861,15 +861,21 @@ build_beamformer_main(Arena arena) if (!is_msvc) cmd_append(&arena, &c, "-L."); cmd_append(&arena, &c, LINK_LIB("raylib")); } else { + if (!is_msvc) cmd_append(&arena, &c, "-flto"); cmd_append(&arena, &c, OUTPUT(OS_STATIC_LIB("raylib"))); } + // TODO(rnp): not sure how to do this with msvc. we don't want a runtime dependence on libc++ + cmd_append(&arena, &c, OUTPUT(OS_STATIC_LIB("glslang")), "-Wl,-Bstatic", "-lstdc++", "-Wl,-Bdynamic"); + if (!is_msvc) cmd_append(&arena, &c, "-lm"); if (is_unix) cmd_append(&arena, &c, "-lGL"); + if (is_w32) { cmd_append(&arena, &c, LINK_LIB("user32"), LINK_LIB("shell32"), LINK_LIB("gdi32"), LINK_LIB("opengl32"), LINK_LIB("winmm"), LINK_LIB("Synchronization")); if (!is_msvc) cmd_append(&arena, &c, "-Wl,--out-implib," OUTPUT(OS_STATIC_LIB("main"))); } + cmd_append(&arena, &c, (void *)0); return run_synchronous(arena, &c); @@ -902,7 +908,7 @@ function b32 build_tests(Arena arena) { CommandList cc = {0}; - cmd_base(&arena, &cc, 0); + cmd_base(&arena, &cc, 0, config.debug); cmd_append(&arena, &cc, EXTRA_FLAGS); #define TEST_PROGRAMS \ diff --git a/external/glslang_local/glslang.cpp b/external/glslang_local/glslang.cpp @@ -46,6 +46,8 @@ #include "glslang/MachineIndependent/preprocessor/PpTokens.cpp" #include "glslang/MachineIndependent/propagateNoContraction.cpp" #include "glslang/MachineIndependent/reflection.cpp" +#include "glslang/ResourceLimits/ResourceLimits.cpp" +#include "glslang/ResourceLimits/resource_limits_c.cpp" #if OS_WINDOWS // NOTE(rnp): includes windows.h: i.e. it needs its own TU diff --git a/util.c b/util.c @@ -607,6 +607,14 @@ s8_scan_backwards(s8 s, u8 byte) } function s8 +s8_trim_trailing(s8 s, u8 byte) +{ + s8 result = s; + while (result.len >= 1 && result.data[result.len - 1] == byte) result.len--; + return result; +} + +function s8 s8_cut_head(s8 s, iz cut) { s8 result = s; diff --git a/vulkan.c b/vulkan.c @@ -1,7 +1,10 @@ #include "beamformer_internal.h" #include "vulkan.h" +#include "external/glslang/glslang/Include/glslang_c_interface.h" +#include "external/glslang/glslang/Public/resource_limits_c.h" -#define vulkan_info(s) s8("[vulkan] " s) +#define glslang_info(s) s8("[glslang] " s) +#define vulkan_info(s) s8("[vulkan] " s) typedef enum { VulkanQueueKind_Graphics, @@ -54,11 +57,16 @@ static_assert(sizeof(VulkanQueue) == 64 && alignof(VulkanQueue) == 64, "VulkanQueue must be placed on its own cacheline"); typedef struct { + Arena arena; + i32 arena_lock; + VkInstance handle; VkDevice device; VkPhysicalDevice physical_device; - Arena arena; + // NOTE(rnp): fallback module for when a compute shader fails to compile + VkShaderModule default_compute_module; + GPUInfo gpu_info; struct { @@ -136,6 +144,120 @@ vk_entity_release(VulkanEntity *entity) } } +#define glslang_log(a, ...) glslang_log_(a, arg_list(s8, __VA_ARGS__)) +function void +glslang_log_(Arena arena, s8 *items, uz count) +{ + Stream sb = arena_stream(arena); + stream_append_s8(&sb, glslang_info("")); + stream_append_s8s_(&sb, items, count); + s8 log = s8_trim_trailing(stream_to_s8(&sb), '\n'); + os_console_log(log.data, log.len); +} + +function s8 +glsl_to_spirv(Arena *arena, u32 kind, s8 shader_text, s8 name) +{ + /* NOTE(rnp): glslang's garbage c interface doesn't expose internal usage of strings with length */ + assert(shader_text.data[shader_text.len] == 0); + + glslang_input_t input = { + .language = GLSLANG_SOURCE_GLSL, + .stage = kind, + .client = GLSLANG_CLIENT_VULKAN, + .client_version = GLSLANG_TARGET_VULKAN_1_4, + .target_language = GLSLANG_TARGET_SPV, + .target_language_version = GLSLANG_TARGET_SPV_1_6, + .code = (c8 *)shader_text.data, + .default_version = 100, + .default_profile = GLSLANG_NO_PROFILE, + .force_default_version_and_profile = 0, + .forward_compatible = 0, + .messages = GLSLANG_MSG_DEFAULT_BIT, + // TODO(rnp): fill this in based on the selected GPU. Then remove that junk from the library + .resource = glslang_default_resource(), + }; + glslang_shader_t *shader = glslang_shader_create(&input); + + s8 error = {0}; + if (glslang_shader_preprocess(shader, &input)) { + if (!glslang_shader_parse(shader, &input)) + error = s8("parsing failed"); + } else { + error = s8("preprocessing failed"); + } + + if (error.len) { + glslang_log(*arena, name, s8(": "), error, s8("\n"), + c_str_to_s8((c8 *)glslang_shader_get_info_log(shader)), + c_str_to_s8((c8 *)glslang_shader_get_info_debug_log(shader))); + glslang_shader_delete(shader); + shader = 0; + } + + s8 result = {0}; + if (shader) { + glslang_program_t *program = glslang_program_create(); + glslang_program_add_shader(program, shader); + i32 messages = GLSLANG_MSG_DEBUG_INFO_BIT|GLSLANG_MSG_SPV_RULES_BIT|GLSLANG_MSG_VULKAN_RULES_BIT; + if (glslang_program_link(program, messages)) { + glslang_spv_options_t options = { + .validate = 1, + .generate_debug_info = 1, + .emit_nonsemantic_shader_debug_info = 1, + .emit_nonsemantic_shader_debug_source = 1, + //.disable_optimizer = 1, + }; + + glslang_program_add_source_text(program, kind, (c8 *)shader_text.data, shader_text.len); + glslang_program_SPIRV_generate_with_options(program, kind, &options); + + u32 words = glslang_program_SPIRV_get_size(program); + result.data = (u8 *)push_array(arena, u32, words); + result.len = words * sizeof(u32); + glslang_program_SPIRV_get(program, (u32 *)result.data); + + s8 spirv_msg = c_str_to_s8((c8 *)glslang_program_SPIRV_get_messages(program)); + if (spirv_msg.len) glslang_log(*arena, name, s8(": spirv info: "), spirv_msg); + } else { + glslang_log(*arena, name, s8(": shader linking failed\n"), + c_str_to_s8((c8 *)glslang_program_get_info_log(program)), + c_str_to_s8((c8 *)glslang_program_get_info_debug_log(program))); + } + glslang_shader_delete(shader); + glslang_program_delete(program); + } + + return result; +} + +function u32 +vk_shader_kind_to_glslang_shader_kind(u32 kind) +{ + u32 result = ctz_u64(kind); + return result; +} + +function VkShaderModule +vk_compile_shader_module(u32 kind, s8 shader, s8 name) +{ + VkShaderModule result = 0; + + DeferLoop(take_lock(&vulkan_context->arena_lock, -1), release_lock(&vulkan_context->arena_lock)) + { + Arena arena = vulkan_context->arena; + s8 spirv = glsl_to_spirv(&arena, vk_shader_kind_to_glslang_shader_kind(kind), shader, name); + VkShaderModuleCreateInfo create_info = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = (uz)spirv.len, + .pCode = (u32 *)spirv.data, + }; + if (spirv.len > 0) vkCreateShaderModule(vulkan_context->device, &create_info, 0, &result); + } + + return result; +} + function void vk_load_instance(void) { @@ -529,6 +651,14 @@ vk_load(OSLibrary vulkan_library_handle, Arena *memory, Stream *err) vk_load_queues(&vulkan_context->arena, err); // TODO: setup compute pipeline + read_only local_persist s8 default_compute_shader = s8("" + "#version 430 core\n" + "void main() {}\n" + "\n"); + + vulkan_context->default_compute_module = vk_compile_shader_module(VK_SHADER_STAGE_COMPUTE_BIT, + default_compute_shader, + s8("error_compute_shader")); // TODO: setup render pipeline diff --git a/vulkan.h b/vulkan.h @@ -2164,6 +2164,7 @@ typedef struct { #define VkDeviceProcedureList \ X(vkAllocateMemory, VkResult, (VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo, const VkAllocationCallbacks *pAllocator, VkDeviceMemory *pMemory)) \ X(vkCreateSemaphore, VkResult, (VkDevice device, const VkSemaphoreCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSemaphore *pSemaphore)) \ + X(vkCreateShaderModule, VkResult, (VkDevice device, const VkShaderModuleCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkShaderModule *pShaderModule)) \ X(vkDestroyBuffer, void, (VkDevice device, VkBuffer buffer, const VkAllocationCallbacks *pAllocator)) \ X(vkFlushMappedMemoryRanges, VkResult, (VkDevice device, uint32_t memoryRangeCount, const VkMappedMemoryRange *pMemoryRanges)) \ X(vkFreeMemory, void, (VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks *pAllocator)) \