From 8d8556871370cf76a73941200f1f39f4617d60b9 Mon Sep 17 00:00:00 2001 From: pavel Date: Fri, 3 Jul 2026 09:22:17 +0200 Subject: [PATCH] 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 --- .gitignore | 3 -- README.md | 4 +-- client/odin/gpu_renderer.odin | 40 ++++++++++----------- client/odin/shaders/compiled/rect.frag.spv | Bin 0 -> 448 bytes client/odin/shaders/compiled/rect.vert.spv | Bin 0 -> 1620 bytes client/odin/shaders/compiled/text.frag.spv | Bin 0 -> 1064 bytes client/odin/shaders/compiled/text.vert.spv | Bin 0 -> 1768 bytes docs/shader-toolchain.md | 7 ++-- 8 files changed, 24 insertions(+), 30 deletions(-) create mode 100644 client/odin/shaders/compiled/rect.frag.spv create mode 100644 client/odin/shaders/compiled/rect.vert.spv create mode 100644 client/odin/shaders/compiled/text.frag.spv create mode 100644 client/odin/shaders/compiled/text.vert.spv diff --git a/.gitignore b/.gitignore index afcc94f..651dd90 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,6 @@ daemon/build/ daemon/dev/ daemon/META-INF/ -# Compiled shaders -client/odin/shaders/compiled/ - # Odin build output /odin client/odin/odin diff --git a/README.md b/README.md index a0fe970..591f7fb 100644 --- a/README.md +++ b/README.md @@ -76,13 +76,13 @@ Gradle workspace import records module source roots, test source roots, resource Use `Ctrl+Shift+T` to toggle Gradle tasks in the right sidebar. Use Up/Down, PageUp/PageDown, Home/End, mouse wheel, or row clicks to select tasks, and Enter to run the selected task; the sidebar shows the loaded task count and reports started/finished/failed events, the final response, and recent stdout/stderr output. -Compile starter SDL3 GPU shaders with: +The SDL3 GPU shaders are compiled to SPIR-V under `client/odin/shaders/compiled/` and embedded into the client binary at build time, so the editor has no runtime shader files to find. The compiled files are committed; after editing the GLSL sources, regenerate them with: ```sh scripts/compile-shaders.sh ``` -The script uses `glslc` from shaderc and emits Vulkan/SPIR-V files under `client/odin/shaders/compiled/`. On Fedora, install it with `sudo dnf install glslc`. +The script uses `glslc` from shaderc. On Fedora, install it with `sudo dnf install glslc`. Run autonomous opencode continuation loops with: diff --git a/client/odin/gpu_renderer.odin b/client/odin/gpu_renderer.odin index 6a4c613..945948b 100644 --- a/client/odin/gpu_renderer.odin +++ b/client/odin/gpu_renderer.odin @@ -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} } diff --git a/client/odin/shaders/compiled/rect.frag.spv b/client/odin/shaders/compiled/rect.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..86ea3d5c7bd62ce73dff83bcdc4ba25a1e608eb4 GIT binary patch literal 448 zcmYjN%L>9U5L~VG{Y224DBi_`iXeKETnZlifFNxTf!c~z!Qb<%ya>+32Thr7c6PF} ziIX3fL~>G-2ENbgNFI!UE83{My*Ig}!DKd{QqdI03aM#HNeYO>@nB*WDnJcrLq{G4 z*g5Db^1z{jR7H7O?RLuBYX5Z7m-HTmIx=yn19J}j)Z9Yq${C%B{pcEG1uhg>Usqh~ zom^AxCsA^SA2poi_GJ`u&Nl$Kn6v)NyO@%5j_kx@_dV>3;D$gSdftaQy@&Xh5&0tI uSKpi-ac>nD!cb`}c@g zG5IWzm{qfo*#-TcbZiV*lGG&+CHs=R{&Ld4lu$5>rr8RbM@P-p>!9232gCGB6b6xv z!=PseVK=qokhuAoI2(7PQExV&sEFA+i@JkO;$GL_qlZq=PlnP(&9Nr^J{+Xs=XbIF zkPJ@bC^=p(GYZBw{M1hdDe-x;4yi-#P3ezO&<+Qa3h?B@bMPfYo7yB6Th4fPlkqlA zkL_1YM9##=F64U|+5Rg#OgnKmoch=-zEKoSee}aVIn2gh$3ZuVl0k;qamV4Nn|+61 zou+b`ojczs8kcAA=9!-dob_6F+@}>yGpH-Vx>ye_xt-Y7r0S z-Qe)=NzX~VAIvuiccAZs8R?Nj&TlbAX=?Fa3zD+rP%QgimGDNq!&AjD<7o-}p%8o9 zlCW#`IU9G*`F^SvdFLhM!vApGMaQ%2?~c198=igpJ}%3q5BBN%sK|!*eN<)pK4#-~ z#pCmR)MQsA@V<{Lvf*d@xGGE!_}c1?yd<)<|dhy4Szi~x%Gynhq literal 0 HcmV?d00001 diff --git a/client/odin/shaders/compiled/text.frag.spv b/client/odin/shaders/compiled/text.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..2496ea5521170438138cacce286f33200e44b399 GIT binary patch literal 1064 zcmYk4TT2^J6oq$9qVXCvjn;Zgv{j)XKD4DE3VkT^5{Tl1k3&cz4va}iCINpy|5$%j zUj)~8<{X$U+kMtLYhTXJlon2x%s$wvt=om=wq*-qOx&W2QTw5NInDZ)-+x@8*szil zqN!Qc%8K|)HyLuVDgPw@?9L^3rzXlOsj}HWC6;Z)qIMj0I#K&i+#8SMY4(~9;xri! z;(jt2^s;0=Kvym3l3_2M^^1av6vf`)s(N(O@pD%ZyrwvdpGLzh$ILg6XLAMJSKO?l zSr+$3>1g7(vYUPGnaI~@ksu5BL}%Uursk0x1M~hOW*1;~R#z5zEw_=p#*WU5;+oFO z#=da4JzLgaekQ+Y0q-k+MHlFr>c8qmaMvEQ2W(e(Ly_9dfxf0lEqL@`-UNq!TM<3G zZs`9<#iktl=5Zs3uHLTBd?n;j56n*B=(ndx?QjofM{xApSEL{MP@nyU{9~QbqWP)c zkasW}hUUMRiXy$)Bi{t^Mwp$f%h?B*H*d*#@1wGqsV+wYe=m+cJ92pJR$ZtGM-6fg zt{Ii8u&2R7oF*a1@5cPaP$n^ dndg|{PTOGSb2<6wxu-sF`)y}%5Lhj5qCQV8@059UNVp_={}3n@56o? zets9*4@v)6hO*;jGQ(hG!%w}WpW5=`a8xb?M(F@sLMz;6L4$^kq2~TWv7~3$4PHfaeUs>FZ{*Hp-$YXll&SCmK z3OY%Y^fP~E&~~`tdQ%}-tD)TVhML~Un>}k|tYj}{y{P?%*M+$w@$|vR`<~E$7CGVY zg+qIe7&z|iIrebOmb1C*zgmmgy8@5TE@p2mkGU(D+)MhRYdV3v>{qlaOIIbU%C8?m zhOt$Jr={T#@T@dvf%hEt_gEG_o%!jbRd;x9472!o?TP2iiq59LKKEJeITw!HVBQ@Z zxz9;gCB%~(yewf++ln;4@F7@vwP%+32? zLpb-gH2uU*-Qc?8{JD39S@=JY#-Ci^M-I~m;=#-rAKzo%^n}C2^QN&AzblQ6eE5Tz zJKT=m8$IDpEMk6(DM{nYd}kz_eISY`hgPHu64|Fn?^k6Yw9shF!^DSUP>? literal 0 HcmV?d00001 diff --git a/docs/shader-toolchain.md b/docs/shader-toolchain.md index 885c2f2..e08ae5a 100644 --- a/docs/shader-toolchain.md +++ b/docs/shader-toolchain.md @@ -12,7 +12,7 @@ For this Linux-first prototype, use GLSL compiled to SPIR-V with `glslc` from sh scripts/compile-shaders.sh ``` -Outputs are written to `client/odin/shaders/compiled/` and intentionally ignored by git. +Outputs are written to `client/odin/shaders/compiled/` and are committed to git: the Odin client embeds them at compile time with `#load`, so they must exist to build. After editing the GLSL sources, rerun the script and rebuild the client. Fedora install: @@ -34,7 +34,8 @@ Current recommendation: Current renderer state: -- The editor loads `rect.vert.spv` and `rect.frag.spv` for the direct SDL3 GPU path. -- The editor loads `text.vert.spv` and `text.frag.spv` for font-atlas text. +- The editor embeds `rect.vert.spv` and `rect.frag.spv` for the direct SDL3 GPU path. +- The editor embeds `text.vert.spv` and `text.frag.spv` for font-atlas text. +- The binary has no runtime dependency on the shader files; they are baked in at build time. - Text is rendered from a baked `stb_truetype` atlas using `/usr/share/fonts/google-noto/NotoSansMono-Regular.ttf`. - A later portability step should bundle a project font or add configurable font discovery.