Embed compiled SPIR-V shaders in the client binary with #load

The GPU renderer previously read .spv files from a cwd-relative path at
runtime; when they were missing or the editor was launched from outside
the repo root, it silently fell back to the SDL renderer, losing syntax
highlighting. The shaders are now baked in at compile time, so the
binary is self-contained. The compiled .spv files are committed (no
longer gitignored) since they are required to build; regenerate with
scripts/compile-shaders.sh after editing the GLSL sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pavel 2026-07-03 09:22:17 +02:00
commit 8d85568713
8 changed files with 24 additions and 30 deletions

View file

@ -47,6 +47,14 @@ GPU_Renderer :: struct {
height: int,
}
// Compiled SPIR-V is embedded at build time so the binary never depends on
// finding shader files at runtime. Regenerate with scripts/compile-shaders.sh
// after editing the GLSL sources, then rebuild.
GPU_RECT_VERT_SPV :: #load("shaders/compiled/rect.vert.spv")
GPU_RECT_FRAG_SPV :: #load("shaders/compiled/rect.frag.spv")
GPU_TEXT_VERT_SPV :: #load("shaders/compiled/text.vert.spv")
GPU_TEXT_FRAG_SPV :: #load("shaders/compiled/text.frag.spv")
GPU_MAX_VERTICES :: 240000
GPU_MAX_TEXT_VERTICES :: 120000
GPU_FONT_ATLAS_SIZE :: 512
@ -83,12 +91,12 @@ gpu_renderer_make :: proc(window: ^SDL.Window) -> GPU_Renderer {
return GPU_Renderer{}
}
gpu.vertex_shader = gpu_create_shader(gpu.device, "client/odin/shaders/compiled/rect.vert.spv", .VERTEX)
gpu.fragment_shader = gpu_create_shader(gpu.device, "client/odin/shaders/compiled/rect.frag.spv", .FRAGMENT)
gpu.text_vertex_shader = gpu_create_shader(gpu.device, "client/odin/shaders/compiled/text.vert.spv", .VERTEX)
gpu.text_fragment_shader = gpu_create_shader(gpu.device, "client/odin/shaders/compiled/text.frag.spv", .FRAGMENT)
gpu.vertex_shader = gpu_create_shader(gpu.device, "rect.vert", GPU_RECT_VERT_SPV, .VERTEX, 0)
gpu.fragment_shader = gpu_create_shader(gpu.device, "rect.frag", GPU_RECT_FRAG_SPV, .FRAGMENT, 0)
gpu.text_vertex_shader = gpu_create_shader(gpu.device, "text.vert", GPU_TEXT_VERT_SPV, .VERTEX, 0)
gpu.text_fragment_shader = gpu_create_shader(gpu.device, "text.frag", GPU_TEXT_FRAG_SPV, .FRAGMENT, 1)
if gpu.vertex_shader == nil || gpu.fragment_shader == nil || gpu.text_vertex_shader == nil || gpu.text_fragment_shader == nil {
fmt.println("SDL GPU shaders unavailable; run scripts/compile-shaders.sh")
fmt.println("SDL GPU shader creation failed, falling back:", SDL.GetError())
gpu_renderer_destroy(&gpu)
return GPU_Renderer{}
}
@ -222,25 +230,18 @@ gpu_renderer_destroy :: proc(gpu: ^GPU_Renderer) {
gpu^ = {}
}
gpu_create_shader :: proc(device: ^SDL.GPUDevice, path: string, stage: SDL.GPUShaderStage) -> ^SDL.GPUShader {
bytes, err := os.read_entire_file(path, context.allocator)
if err != nil {
fmt.println("read shader failed:", path, err)
return nil
}
defer delete(bytes)
gpu_create_shader :: proc(device: ^SDL.GPUDevice, name: string, code: []u8, stage: SDL.GPUShaderStage, num_samplers: u32) -> ^SDL.GPUShader {
shader := SDL.CreateGPUShader(device, {
code_size = len(bytes),
code = raw_data(bytes),
code_size = len(code),
code = raw_data(code),
entrypoint = "main",
format = {.SPIRV},
stage = stage,
num_uniform_buffers = 1 if stage == .VERTEX else 0,
num_samplers = 1 if stage == .FRAGMENT && strings_has_suffix(path, "text.frag.spv") else 0,
num_samplers = num_samplers,
})
if shader == nil {
fmt.println("CreateGPUShader failed:", path, SDL.GetError())
fmt.println("CreateGPUShader failed:", name, SDL.GetError())
}
return shader
}
@ -545,11 +546,6 @@ gpu_global_font_advance :: proc(ch: u8) -> f32 {
return 8
}
strings_has_suffix :: proc(s, suffix: string) -> bool {
if len(suffix) > len(s) do return false
return s[len(s) - len(suffix):] == suffix
}
color_f32 :: proc(r, g, b, a: u8) -> [4]f32 {
return {f32(r) / 255.0, f32(g) / 255.0, f32(b) / 255.0, f32(a) / 255.0}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.