Commit: 8a20ea056af8f588c5f76e9df2ece648694953c0
Parent: c83e4c553332bc4d2bbe4a3ccdd1cfacfcb4af87
Author: Randy Palamar
Date: Sat, 17 Jan 2026 19:26:55 -0700
vulkan: build default/empty compute shader
Diffstat:
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)) \