From 8e9ab4fbac7610115e63d327ed41b1353675b6a0 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 12:25:34 +0300 Subject: [PATCH 01/10] build: pin Engine to shader-includes --- Engine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine b/Engine index d319e9e..e03be79 160000 --- a/Engine +++ b/Engine @@ -1 +1 @@ -Subproject commit d319e9e2dba42e0753efa4bc29625a27a18a6e57 +Subproject commit e03be79058b35cc92aadbf48201cf67cdd37f336 From 8e5930e0054123d9082bad9b4baba89a61261646 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 01:54:31 +0300 Subject: [PATCH 02/10] build: compile game shaders against the include libraries --- Client/CMakeLists.txt | 17 +++++++++++++ Game/CMakeLists.txt | 49 ++++++++++++++++++++++++++++++++++++++ scripts/compile_shaders.sh | 7 +++++- 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt index 1d9ac50..32f9195 100644 --- a/Client/CMakeLists.txt +++ b/Client/CMakeLists.txt @@ -47,10 +47,27 @@ function(sleak_track_asset_dir SRC_DIR LABEL) endfunction() set(SLEAK_ASSET_OUTPUTS "") +# Game SPIR-V built from source claims its outputs first, so the committed +# fallback copies are skipped +get_property(_game_spirv GLOBAL PROPERTY GAME_SPIRV_FILES) +foreach(_spv ${_game_spirv}) + get_filename_component(_name "${_spv}" NAME) + set(_dst "${_asset_out_dir}/shaders/${_name}") + add_custom_command( + OUTPUT "${_dst}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_spv}" "${_dst}" + DEPENDS "${_spv}" + COMMENT "Copy game SPIR-V: ${_name}" + VERBATIM + ) + list(APPEND SLEAK_ASSET_OUTPUTS "${_dst}") + list(APPEND _sleak_seen_outputs "${_dst}") +endforeach() sleak_track_asset_dir("${CMAKE_SOURCE_DIR}/Game/assets" "game") sleak_track_asset_dir("${CMAKE_SOURCE_DIR}/Engine/assets" "engine") add_custom_target(CopyAssets ALL DEPENDS ${SLEAK_ASSET_OUTPUTS}) +add_dependencies(CopyAssets GameShaders) add_dependencies(SleakCraft CopyAssets) # --- Post-build: copy runtime DLLs (Windows) --- diff --git a/Game/CMakeLists.txt b/Game/CMakeLists.txt index f547fbb..98a89bd 100644 --- a/Game/CMakeLists.txt +++ b/Game/CMakeLists.txt @@ -14,3 +14,52 @@ target_include_directories(SleakGame PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_link_libraries(SleakGame PRIVATE Engine) + +# --- SPIR-V shaders (committed .spv are only the fallback without glslc) --- +set(GAME_SHADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets/shaders") +set(GAME_SHADER_INCLUDE_DIR "${GAME_SHADER_DIR}/include") +file(GLOB GAME_VULKAN_SHADERS CONFIGURE_DEPENDS + "${GAME_SHADER_DIR}/*.vert" + "${GAME_SHADER_DIR}/*.frag" +) +list(FILTER GAME_VULKAN_SHADERS EXCLUDE REGEX "_gl\\.(vert|frag)$") +file(GLOB_RECURSE GAME_SHADER_INCLUDES CONFIGURE_DEPENDS + "${GAME_SHADER_INCLUDE_DIR}/*.glsl") +# Engine include edits must rebuild game SPIR-V too +get_property(_engine_include_dir GLOBAL PROPERTY ENGINE_SHADER_INCLUDE_DIR) +get_property(_engine_includes GLOBAL PROPERTY ENGINE_SHADER_INCLUDES) +if(NOT _engine_include_dir) + set(_engine_include_dir "${CMAKE_SOURCE_DIR}/Engine/assets/shaders/include") +endif() + +find_program(GLSLC glslc + HINTS "$ENV{VULKAN_SDK}/Bin" "$ENV{VULKAN_SDK}/bin" "${VULKAN_SDK}/Bin") +set(GAME_SPIRV_FILES "") +if(GLSLC) + set(_spirv_dir "${CMAKE_CURRENT_BINARY_DIR}/shaders") + file(MAKE_DIRECTORY "${_spirv_dir}") + foreach(_src ${GAME_VULKAN_SHADERS}) + get_filename_component(_name "${_src}" NAME) + set(_spv "${_spirv_dir}/${_name}.spv") + add_custom_command( + OUTPUT "${_spv}" + COMMAND ${GLSLC} -I "${_engine_include_dir}" + -I "${GAME_SHADER_INCLUDE_DIR}" "${_src}" -o "${_spv}" + DEPENDS "${_src}" ${_engine_includes} ${GAME_SHADER_INCLUDES} + COMMENT "Compiling ${_name} to SPIR-V" + VERBATIM + ) + list(APPEND GAME_SPIRV_FILES "${_spv}") + endforeach() +else() + foreach(_src ${GAME_VULKAN_SHADERS}) + if(NOT EXISTS "${_src}.spv") + message(FATAL_ERROR + "glslc not found and ${_src} has no committed SPIR-V. " + "Install the Vulkan SDK or set VULKAN_SDK.") + endif() + endforeach() +endif() +add_custom_target(GameShaders ALL DEPENDS ${GAME_SPIRV_FILES}) +# Client stages these over the committed copies +set_property(GLOBAL PROPERTY GAME_SPIRV_FILES ${GAME_SPIRV_FILES}) diff --git a/scripts/compile_shaders.sh b/scripts/compile_shaders.sh index fdb2cc4..bf7ec9f 100755 --- a/scripts/compile_shaders.sh +++ b/scripts/compile_shaders.sh @@ -11,6 +11,11 @@ SHADER_DIRS=( "$ROOT_DIR/Engine/assets/shaders" "$ROOT_DIR/Game/assets/shaders" ) +# Include libraries: engine sleak/, game craft/ +INCLUDE_FLAGS=( + -I "$ROOT_DIR/Engine/assets/shaders/include" + -I "$ROOT_DIR/Game/assets/shaders/include" +) if ! command -v glslc >/dev/null; then echo "error: glslc not found" >&2 @@ -28,7 +33,7 @@ for dir in "${SHADER_DIRS[@]}"; do for shader in "$dir"/*.vert "$dir"/*.frag "$dir"/*.comp; do [[ -f "$shader" ]] || continue case "$(basename "$shader")" in *_gl.*) continue ;; esac - if glslc "$shader" -o "$shader.spv"; then + if glslc "${INCLUDE_FLAGS[@]}" "$shader" -o "$shader.spv"; then echo "compiled ${shader#$ROOT_DIR/} -> ${shader#$ROOT_DIR/}.spv" count=$((count + 1)) else From cd5ac31dc1ccfa004e17fc454b41eda804ee9468 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 01:54:31 +0300 Subject: [PATCH 03/10] shaders: voxel and water shaders on the shared blocks --- Game/assets/shaders/flat_shader.frag | 20 +++---------- Game/assets/shaders/flat_shader.frag.spv | Bin 10600 -> 12928 bytes Game/assets/shaders/flat_shader.vert | 17 ++--------- Game/assets/shaders/flat_shader.vert.spv | Bin 3884 -> 4456 bytes Game/assets/shaders/gbuffer_voxel.vert | 20 +++---------- Game/assets/shaders/gbuffer_voxel.vert.spv | Bin 4052 -> 6492 bytes Game/assets/shaders/shadow_depth_voxel.vert | 5 +++- .../shaders/shadow_depth_voxel.vert.spv | Bin 1436 -> 3268 bytes Game/assets/shaders/water_shader.frag | 27 +++--------------- Game/assets/shaders/water_shader.frag.spv | Bin 48304 -> 50360 bytes Game/assets/shaders/water_shader.vert | 23 +++------------ Game/assets/shaders/water_shader.vert.spv | Bin 4008 -> 5708 bytes 12 files changed, 22 insertions(+), 90 deletions(-) diff --git a/Game/assets/shaders/flat_shader.frag b/Game/assets/shaders/flat_shader.frag index 0e892f3..946920f 100644 --- a/Game/assets/shaders/flat_shader.frag +++ b/Game/assets/shaders/flat_shader.frag @@ -1,4 +1,5 @@ #version 450 +#extension GL_GOOGLE_include_directive : require layout(location = 0) in vec3 fragWorldPos; layout(location = 1) in vec3 fragWorldNorm; @@ -10,21 +11,8 @@ layout(location = 0) out vec4 outColor; layout(set = 0, binding = 0) uniform sampler2D diffuseTexture; -layout(set = 2, binding = 0) uniform ShadowLightUBO { - vec4 uLightDir; - vec4 uLightColor; - vec4 uAmbient; // rgb = sky ambient color, a = intensity - vec4 uCameraPos; - mat4 uLightVP; - float uShadowBias; - float uShadowStrength; - float uShadowTexelSize; - float uLightSize; - vec4 uFogColor; - float uFogStart; - float uFogEnd; - vec2 _fogPad; // std140: must be vec2 (8B) not float[2] (stride-16 = 32B) -}; +#include "sleak/shadow_light.glsl" +#include "sleak/frame.glsl" layout(set = 3, binding = 0) uniform sampler2DShadow shadowMap; @@ -120,5 +108,5 @@ void main() { lit = mix(uFogColor.rgb, lit, fogFactor); } - outColor = vec4(lit, texColor.a); + outColor = SleakFrameGuard(vec4(lit, texColor.a)); } diff --git a/Game/assets/shaders/flat_shader.frag.spv b/Game/assets/shaders/flat_shader.frag.spv index 80e3efdbf072709e2fbd1f0cf4cde171f85575bd..3bc97ba40d385916b2289b4bae0dadb623cbe02b 100644 GIT binary patch literal 12928 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 X-v}my`Afg|YM+QqLR@=ouKoW5S_@9s literal 10600 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 diff --git a/Game/assets/shaders/flat_shader.vert b/Game/assets/shaders/flat_shader.vert index a05e82c..7becd59 100644 --- a/Game/assets/shaders/flat_shader.vert +++ b/Game/assets/shaders/flat_shader.vert @@ -1,4 +1,5 @@ #version 450 +#extension GL_GOOGLE_include_directive : require layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inNormal; @@ -10,21 +11,7 @@ layout(push_constant) uniform TransformPC { mat4 World; }; -layout(set = 2, binding = 0) uniform ShadowLightUBO { - vec4 uLightDir; // xyz = direction, w = pad - vec4 uLightColor; // rgb = color, a = intensity - vec4 uAmbient; // rgb = color, a = intensity - vec4 uCameraPos; // xyz = camera world pos, w = pad - mat4 uLightVP; // light view-projection matrix - float uShadowBias; - float uShadowStrength; - float uShadowTexelSize; - float uLightSize; - vec4 uFogColor; // rgb + alpha - float uFogStart; // distance where fog begins - float uFogEnd; // distance where fog fully obscures - vec2 _fogPad; // std140: must be vec2 (8B) not float[2] (stride-16 = 32B) -}; +#include "sleak/shadow_light.glsl" layout(location = 0) out vec3 fragWorldPos; layout(location = 1) out vec3 fragWorldNorm; diff --git a/Game/assets/shaders/flat_shader.vert.spv b/Game/assets/shaders/flat_shader.vert.spv index 2e7ab08110791b094a983b20263278b8937b7efe..8afedd62ad48892e3861082442e88f3ec4141d41 100644 GIT binary patch delta 1593 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 diff --git a/Game/assets/shaders/shadow_depth_voxel.vert b/Game/assets/shaders/shadow_depth_voxel.vert index 006b837..a371bea 100644 --- a/Game/assets/shaders/shadow_depth_voxel.vert +++ b/Game/assets/shaders/shadow_depth_voxel.vert @@ -1,4 +1,5 @@ #version 450 +#extension GL_GOOGLE_include_directive : require // Shadow Depth Pass - Voxel Vertex Only // Compact VoxelVertex layout: only position needed for shadow map @@ -8,6 +9,8 @@ layout(location = 1) in vec3 inNormal; layout(location = 2) in vec4 inColor; layout(location = 3) in vec2 inUV; +#include "sleak/frame.glsl" + // Push constant: LightVP*World in slot 0, World in slot 1 layout(push_constant) uniform TransformPC { mat4 WVP; @@ -15,5 +18,5 @@ layout(push_constant) uniform TransformPC { }; void main() { - gl_Position = WVP * vec4(inPosition, 1.0); + gl_Position = SleakFrameGuardClip(WVP * vec4(inPosition, 1.0)); } diff --git a/Game/assets/shaders/shadow_depth_voxel.vert.spv b/Game/assets/shaders/shadow_depth_voxel.vert.spv index f1089785adac4d23ff67a8b4ec3e47b2d28ca94c..5374b5f6ea8ca0df92d7f9223f140402d1a7e079 100644 GIT binary patch literal 3268 zcmZ9O*>W326oxytBqxMGLRKIc*(M=HjByptVnPx*QY!Q1&Ezu{UwrE;^Q`&!(QAsw< zR;RhW-E7_Oboci=!}Oad>_j#WJ3X6(-P9h2=p8NS*|-}W^oj~)X%z1*OaG|u*ong6 z%k?Dahm9Z&cG4gUuLw3}b4>9T<;{a2>D42&))!&l6T_`Rsp4ij}HrTW=D;4EZ=p-r=X%{YDf@W@DZs=-^KYEAkAc{t^vQXxqRo34P?T?cn9zGi+{dQfpC5It@y4!Yn zp+gK$2T9b^j*nICEPU{mY&;5ENXayX?FX9iT!*)2z?d<}qd%tXjaBVX)-_#{7cS z99DAiaUb^H7d(1J|6tgW>xQBxFJ`-F2R~^ChTV4}Z^!%>?cgWvz_9yWda$$K4TQGKyVFNZ@z&L|3C-~+aWWL95#Mc8Et5^?@5kHGM`nOcH zBaTTsFzjj~^5%`ridgVl+TqcI=XVUA_~4l@cyc%`6nst`Jm)j|WUj%uoAWuHnU|{Q zynK*5Jo#7k>H7^nC!W4?20ttQl=iBKbwT`V+L2?Azth^$fu9w}?88Ic1EF6ed`wbN zj!$Act3Auhm|Ho|tbh^Qd8e1v>uSzl5XOAL6T|Bw7XIGQjxD&CzooO*b8kPN@gw;q z@kd0+$>nM;UsQ#Oc`?VpJyDk>@zkHXT-4`>+OfyqtR&1a_F%{$V!WoEej+Dlc)r`g zX2mmWzIN!}6wmhvIQsB>kAorqC_iIET(`AzC;pJ%(N2Eg*uyiIU_R$HosD^Z7yV70 zuqAe{zoj$!$Xof@%i)=0aHKzppd) z=p$bfeJ{V{zb@i#bnYm6Vg*~xCaz&vywJZ{Vlg@7pxX+*3Qa2XgS2q+V wv%v2Uaq<3F6)*nY6HzN-!tQ+$dh~Be`v)R)iu9u_(Ou5vi`uax4lv^QA0>SO_y7O^ literal 1436 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 b8+FXoH?|=V$=gysZv%7gM^RGFeR-3Q3P;HUg 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 eVEgoQsP>DfbL~D~+PtTE?L7dMt6aFS zEcpg3T)6ZjSe6SHF8nxdtn&Z6_nyg3E_HIx`JcxR%Ng_XJU=jRq?R@ygitZdd_7uiaYm)S}uud;TX-^{Sv8`E4`*p@zvC_(_<*cf++Y*I(93EYFGq3Y*DRhssr!I%*Wxbexqm%LRI&zk7 zy2x)d^UXRf+gV>nSJ3r}qOTiLo%Dk>q<@FBFI8!|x!SEZTGR4vI1K&e<(6b`Lk^d_ zRngH-jKdgc5rf|5Wj}2pyOZaIEw-~VyO^b&OH=2urA}~GZ#mCywW{t{;;99kGkRyr zn~|#TL!G_Fc9mtNYk~Ptqi`KGoo-}RDzWcF@4areNY^%9-Y;UHMULEu)hbeP*?L=QcJ^n= z4!41R6N7p!E`0-|)@D}JX;7Fgb^6w9`40&@o7Z(#`F`NPTjaasi=~I)`rwjQmEGK~ z^Lgx*j(KS7=XRg7ccT;LbK4i}!bpYs*xdJMq!%;w({vBGI9Y$*C@mg$bRl0`uba6l z*s6iA+u_sQqU&n&c^(ixxp6Je%33izc2Jm~Jo9QK^OAe6*?xZKB$sZ+Q+cZCGrtSM z7T42G_m(@wO^}?xS-mJJ!`;1&k}}-gSxZX3hPyi(HQR7^UtJu1c#%D`?D?czqV$h^ zm}kHj>nbbP>UCjzosO9X-BNZtE4JPZUkh{fwRnx?z-)W7-L=r(e$LDne6dceT0A`_ z2j&YtQ+E0ob6r{OuC-DypDXjk?n+kX^}3I4I%e9N&zQC>mby12TOG`GOV`Xwy^UW- zdS<+(pG}LRySf_b4{g)Wlo~pd!$at2Zr4@nFPim%_s!$mbA1^6TyMjB@H=3+z_To-$#G1>;#|PuTuk2S7w1A<*l{+o?uZN<4A{>RM(?n@ zC!!91S4KtTYVvXM_#*RLvsGuq{l*}Z1Ny0;H@O*Pn-TA{?~8xiB)aq2$rHWRgO6cW z&)x{LdiYMw&g!ul6OUS`aYSTxy8|{JVe}Qd35PXYE}Um`BNlSpf5Y%=iMT^xyg#uY z@=iZ6{QNw;AMaD_hrH7d48MOwb{^kB?1#M54-CJ9YOwjyyVwtTryrQB&-wWo@eaD& zBaHVC>|TWN?t%RlVFv^DyD;v|yyQ`l#J+r7JTphnf4Z0#e#otD@}PUav1PUww6i#q zWf-x+h+!B$U?UMW7_f1NHC#OIl=X=C$Z?MhBi4k-;^PB$C&H)$?E45K7T6CF#ytc3 zF~YXr7hkL0g?o{m&H0xIvl;#xVK&3xBFtv^dxY5x|BNvDNPd4s7;h)o-x0>|64*Zx z#%~fB1GirBdjvM_FyD86hvL2?@9aAmeiQQJZtxp5Dq@*@Ts*Tz=7)&)YB0#xgKTVCb0#JoeQ2 zh<0-1eDsfM=WKBNk@*b-BOh#^5GBc-)7X!S!g@)=oTR?k8_8GW|hrMe?wwd?Co#7?C6XFJ5vf1({*;-TlPwupuPEA6{P4U-T*N4Mc~O9Ju+_s`Zx6& z5u3AE3RLZ+`UH{5}wmALrtS9T@p?Zze?C8+5!+$flbXKP5spsU4Zx`Tjo= z@ka12SkD#$j(t-+GWlV@B;p(7j>LVw6mkZ@e z?yeV(y$L6!{1==_{b!tMugZx*WU2A4>gRdyq{{Sg67O=WUh0(Fl!+L}z(*~gX;ubY zOx#ATRWFn)iVB&mY>uN}PO{l*@07O}8|Alf5wqqVMN$bSZ!~YWwp(qnVN>gkj*JDb zP=pazM51$M`(nTaEH^Ic#kt?2$vn{^>$&84!d?be;&^sHBep(-8Qhi*N0nPbKVu-4 zTp(ja!1KZPNOBwQ;}FL>;Xe7CTZ|>UWMGwez6$TeH zN-{Jy@w7#GO3%qYCCPJRjl>rv$;FECbMLvJbIl8QB`qM3l!Vyy4@ZKk)#oMgW7phY ziSrJv~vNZ Z=x$Tkx7?O=)`5@p@gU&-=__6p{sYi$d=CHs From 585a8bc5a6c30237d017e44f5d73d0655dce252c Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 01:54:31 +0300 Subject: [PATCH 04/10] shaders: OpenGL voxel, water and lighting shaders on the includes --- Game/assets/shaders/flat_shader_gl.frag | 5 ++++- Game/assets/shaders/flat_shader_gl.vert | 17 +++++------------ Game/assets/shaders/gbuffer_gl.vert | 17 +++++------------ Game/assets/shaders/lighting_pass_gl.frag | 13 +++++++------ Game/assets/shaders/water_shader_gl.frag | 5 ++++- Game/assets/shaders/water_shader_gl.vert | 5 ++++- 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/Game/assets/shaders/flat_shader_gl.frag b/Game/assets/shaders/flat_shader_gl.frag index 7d9cd8b..60e7a45 100644 --- a/Game/assets/shaders/flat_shader_gl.frag +++ b/Game/assets/shaders/flat_shader_gl.frag @@ -1,4 +1,5 @@ #version 450 core +#extension GL_GOOGLE_include_directive : require in vec3 fragWorldPos; in vec3 fragWorldNorm; @@ -39,6 +40,8 @@ layout(std140, binding = 5) uniform ShadowUBO { float _shadowPad0, _shadowPad1; }; +#include "sleak/frame.glsl" + layout(binding = 0) uniform sampler2D diffuseTexture; layout(binding = 3) uniform sampler2DShadow shadowMap; @@ -142,5 +145,5 @@ void main() { lit = mix(FogColor.rgb, lit, fogFactor); } - outColor = vec4(lit, texColor.a); + outColor = SleakFrameGuard(vec4(lit, texColor.a)); } diff --git a/Game/assets/shaders/flat_shader_gl.vert b/Game/assets/shaders/flat_shader_gl.vert index 0111c70..bdcb5d2 100644 --- a/Game/assets/shaders/flat_shader_gl.vert +++ b/Game/assets/shaders/flat_shader_gl.vert @@ -1,4 +1,5 @@ #version 450 core +#extension GL_GOOGLE_include_directive : require layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inNormal; @@ -10,16 +11,8 @@ layout(std140, binding = 0) uniform TransformUBO { mat4 World; }; -layout(std140, binding = 5) uniform ShadowUBO { - mat4 ShadowLightVP; - float ShadowBias; - float ShadowStrength; - float ShadowTexelSize; - float ShadowLightSize; - uint PCSSEnabled; - uint ShadowMapEnabled; - float _shadowPad0, _shadowPad1; -}; +#include "sleak/shadow_light.glsl" +#include "sleak/frame.glsl" out vec3 fragWorldPos; out vec3 fragWorldNorm; @@ -28,7 +21,7 @@ out vec2 fragUV; out vec4 fragShadowCoord; void main() { - gl_Position = WVP * vec4(inPosition, 1.0); + gl_Position = SleakFrameGuardClip(WVP * vec4(inPosition, 1.0)); vec4 worldPos = World * vec4(inPosition, 1.0); fragWorldPos = worldPos.xyz; @@ -39,5 +32,5 @@ void main() { fragColor = inColor; fragUV = inUV; - fragShadowCoord = ShadowLightVP * worldPos; + fragShadowCoord = uLightVP * worldPos; } diff --git a/Game/assets/shaders/gbuffer_gl.vert b/Game/assets/shaders/gbuffer_gl.vert index bd9f5dc..085e5a7 100644 --- a/Game/assets/shaders/gbuffer_gl.vert +++ b/Game/assets/shaders/gbuffer_gl.vert @@ -1,4 +1,5 @@ #version 450 core +#extension GL_GOOGLE_include_directive : require layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inNormal; @@ -10,16 +11,8 @@ layout(std140, binding = 0) uniform TransformUBO { mat4 World; }; -layout(std140, binding = 5) uniform ShadowUBO { - mat4 ShadowLightVP; - float ShadowBias; - float ShadowStrength; - float ShadowTexelSize; - float ShadowLightSize; - uint PCSSEnabled; - uint ShadowMapEnabled; - float _shadowPad0, _shadowPad1; -}; +#include "sleak/shadow_light.glsl" +#include "sleak/frame.glsl" out vec3 fragWorldPos; out vec3 fragWorldNorm; @@ -30,7 +23,7 @@ out vec2 fragUV; out vec4 fragShadowCoord; void main() { - gl_Position = WVP * vec4(inPosition, 1.0); + gl_Position = SleakFrameGuardClip(WVP * vec4(inPosition, 1.0)); vec4 worldPos = World * vec4(inPosition, 1.0); fragWorldPos = worldPos.xyz; @@ -48,5 +41,5 @@ void main() { fragWorldBit = B; fragColor = inColor; fragUV = inUV; - fragShadowCoord = ShadowLightVP * worldPos; + fragShadowCoord = uLightVP * worldPos; } diff --git a/Game/assets/shaders/lighting_pass_gl.frag b/Game/assets/shaders/lighting_pass_gl.frag index 04b31ac..ebce548 100644 --- a/Game/assets/shaders/lighting_pass_gl.frag +++ b/Game/assets/shaders/lighting_pass_gl.frag @@ -1,4 +1,5 @@ #version 450 core +#extension GL_GOOGLE_include_directive : require // Deferred lighting pass. // Reads the 4 GBuffer textures (bound at units 8-11), @@ -26,6 +27,8 @@ layout(binding = 16) uniform sampler2D brdfLUT; // Shadow map (already bound at unit 3 by the shadow pass) layout(binding = 3) uniform sampler2DShadow shadowMap; +#include "sleak/frame.glsl" + // ---- UBOs ---- struct LightData { vec3 Position; uint Type; @@ -138,7 +141,7 @@ float CalcShadowPCF(vec3 worldPos, float NdotL) { vec4 sc = ShadowLightVP * vec4(worldPos, 1.0); vec3 proj = sc.xyz / sc.w; proj.xy = proj.xy * 0.5 + 0.5; - proj.z = proj.z * 0.5 + 0.5; // remap [-1,1]-> [0,1] for OpenGL + proj.z = SleakNdcToDepth(proj.z); if (proj.z > 1.0 || any(lessThan(proj.xy, vec2(0.0))) || any(greaterThan(proj.xy, vec2(1.0)))) return 1.0; @@ -160,11 +163,9 @@ float CalcShadowPCF(vec3 worldPos, float NdotL) { return mix(1.0, fadedShadow, ShadowStrength * edgeFade); } -// Reconstruct world position from depth. OpenGL: no vertex Y-flip, and the -// default clip depth range is [-1,1], so the stored [0,1] depth maps to -// NDC.z = 2*depth-1 before InvViewProj. +// World position of a GBuffer texel from its depth vec3 ReconstructWorldPos(vec2 uv, float depth) { - vec4 ndc = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + vec4 ndc = vec4(SleakUvToNdc(uv), SleakDepthToNdc(depth), 1.0); vec4 world = InvViewProj * ndc; return world.xyz / world.w; } @@ -305,5 +306,5 @@ void main() { // ---- Fog ---- color = ApplyFog(color, worldPos); - outColor = vec4(color, 1.0); + outColor = SleakFrameGuard(vec4(color, 1.0)); } diff --git a/Game/assets/shaders/water_shader_gl.frag b/Game/assets/shaders/water_shader_gl.frag index 696b055..5a7bb56 100644 --- a/Game/assets/shaders/water_shader_gl.frag +++ b/Game/assets/shaders/water_shader_gl.frag @@ -1,4 +1,5 @@ #version 450 core +#extension GL_GOOGLE_include_directive : require in vec3 fragWorldPos; in vec3 fragNormal; @@ -47,6 +48,8 @@ layout(std140, binding = 5) uniform ShadowUBO { layout(binding = 3) uniform sampler2DShadow shadowMap; +#include "sleak/frame.glsl" + // Scene read (VertexLayoutDesc::transparentReadsScene) layout(binding = 4) uniform sampler2D sceneColor; // tonemapped + fogged layout(binding = 5) uniform sampler2D sceneDepth; @@ -598,5 +601,5 @@ void main() { color = mix(w.backdrop, color, w.edge); else alpha = FallbackAlpha(P, fragNormal); - outColor = vec4(color, alpha); + outColor = SleakFrameGuard(vec4(color, alpha)); } diff --git a/Game/assets/shaders/water_shader_gl.vert b/Game/assets/shaders/water_shader_gl.vert index 378e49e..710f21e 100644 --- a/Game/assets/shaders/water_shader_gl.vert +++ b/Game/assets/shaders/water_shader_gl.vert @@ -1,4 +1,5 @@ #version 450 core +#extension GL_GOOGLE_include_directive : require layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inNormal; @@ -21,6 +22,8 @@ layout(std140, binding = 1) uniform MaterialUBO { float MatOpacity; float MatAlphaCutoff; float _matPad1, _matPad2; }; +#include "sleak/frame.glsl" + layout(std140, binding = 5) uniform ShadowUBO { mat4 ShadowLightVP; float ShadowBias; @@ -45,6 +48,6 @@ void main() { fragWorldPos = inPosition; fragNormal = inNormal; fragColor = inColor; - fragTime = MatTiling.x; // game time from SetTiling() + fragTime = uFrame.Time; fragShadowCoord = ShadowLightVP * vec4(inPosition, 1.0); } From c7754ad979376d1e534f725597e7aa8b35f28c47 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 01:54:32 +0300 Subject: [PATCH 05/10] world: water reads the engine clock --- Game/src/MainScene.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Game/src/MainScene.cpp b/Game/src/MainScene.cpp index a188f92..4cf4cfb 100644 --- a/Game/src/MainScene.cpp +++ b/Game/src/MainScene.cpp @@ -298,11 +298,6 @@ void MainScene::Update(float deltaTime) { m_chunkManager.SetShadowLightVP(lm ? lm->GetShadowLightVP() : nullptr); m_chunkManager.Update(pos.GetX(), pos.GetY(), pos.GetZ()); m_chunkManager.RenderColumns(); - - // Animate water — pass game time through material tiling.x - if (m_waterMaterial) { - m_waterMaterial->SetTiling(m_gameTime, 1.0f); - } m_chunkManager.RenderWater(); m_blockInteraction.RenderOutline(); From 67d0dd42226b97b358bb5572546332f88ba52a9b Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 01:54:32 +0300 Subject: [PATCH 06/10] client: list OpenGL under -capture --- Client/src/main.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Client/src/main.cpp b/Client/src/main.cpp index 4ec10c3..29f2f37 100644 --- a/Client/src/main.cpp +++ b/Client/src/main.cpp @@ -23,9 +23,11 @@ static void PrintHelp(const char* exe) { << " -t Window title (use _ for spaces)\n" << " --fullscreen Start in fullscreen\n" << "\nWorld\n" - << " -world Load world if save exists, create new otherwise\n" + << " -world Load world if save exists, create new " + "otherwise\n" << " -seed Seed for new world (default: random)\n" - << " -rd Initial render distance in chunks (default: 8)\n" + << " -rd Initial render distance in chunks (default: " + "8)\n" << "\nGraphics\n" << " -msaa MSAA sample count: 1, 2, 4, 8\n" << " --vsync Enable VSync on launch\n" @@ -37,7 +39,8 @@ static void PrintHelp(const char* exe) { << " -campose x,y,z,yaw,pitch\n" << " Pin the camera, degrees (Debug or --bench)\n" << " --gpuprofile Log per-pass GPU timings (Vulkan)\n" - << " -capture Write frame N as a PPM, then exit (Vulkan)\n" + << " -capture Write frame N as a PPM, then exit (Vulkan, " + "OpenGL)\n" << " -capture-frame Frame index for -capture (default: 600)\n" << "\nDebug\n" << " --validate Enable Vulkan validation (Debug: always on)\n" From ae77d865026d28a14a58f6a2dc38620cf8cb37d9 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 01:54:32 +0300 Subject: [PATCH 07/10] ci: include-aware shader checks and OpenGL validation --- .github/workflows/ci.yml | 79 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24e23a1..cafc3d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -184,6 +184,7 @@ jobs: uses: actions/checkout@v7 with: path: SleakCraft + fetch-depth: 0 - name: Check out the engine uses: actions/checkout@v7 @@ -192,8 +193,10 @@ jobs: ref: ${{ needs.engine-ref.outputs.ref }} path: SleakCraft/Engine - - name: Install glslc - run: sudo apt-get update && sudo apt-get install -y glslc + - name: Install shader tools + run: | + sudo apt-get update + sudo apt-get install -y glslc glslang-tools # Byte comparison against the committed .spv is not usable here: glslc # embeds its own generator version, so a different toolchain produces @@ -206,7 +209,8 @@ jobs: for src in *.vert *.frag; do [ -e "$src" ] || continue case "$src" in *_gl.vert|*_gl.frag) continue ;; esac - if ! glslc "$src" -o /tmp/out.spv; then + if ! glslc -I ../../../Engine/assets/shaders/include -I include \ + "$src" -o /tmp/out.spv; then echo "FAILED to compile: $src" fail=1 fi @@ -228,6 +232,71 @@ jobs: [ $missing -eq 0 ] && echo "All voxel shaders have committed SPIR-V." exit $missing + # Committed .spv are compiled against the engine the game pins, so a + # change to a game shader, a game include, or an engine include between + # the base and head pins needs fresh SPIR-V. + - name: Edited GLSL and engine includes come with SPIR-V + working-directory: SleakCraft + env: + BASE: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || github.event.before }} + run: | + if [ ! -f Engine/scripts/check_spirv_fresh.py ]; then + echo "::warning::The engine has no scripts/check_spirv_fresh.py." + exit 0 + fi + if [ "${{ github.event_name }}" = "pull_request" ]; then + git fetch --no-tags origin "${{ github.base_ref }}" + fi + if ! git cat-file -e "$BASE^{commit}" 2>/dev/null; then + echo "No base commit to compare against." + exit 0 + fi + BASE=$(git merge-base "$BASE" HEAD) + old_pin=$(git rev-parse "$BASE:Engine") + new_pin=$(git rev-parse "HEAD:Engine") + for tree in base head; do + mkdir -p "/tmp/$tree/Engine/assets/shaders" + cp -r Engine/assets/shaders/include "/tmp/$tree/Engine/assets/shaders/" 2>/dev/null || true + done + git archive "$BASE" Game/assets/shaders | tar -x -C /tmp/base + git archive HEAD Game/assets/shaders | tar -x -C /tmp/head + git diff --name-only "$BASE" HEAD -- Game/assets/shaders > /tmp/changed.txt + if git -C Engine fetch --no-tags --depth=1 origin "$old_pin" "$new_pin"; then + for pair in "base:$old_pin" "head:$new_pin"; do + tree=${pair%%:*} + pin=${pair#*:} + rm -rf "/tmp/$tree/Engine/assets/shaders/include" + if git -C Engine cat-file -e "$pin:assets/shaders/include" 2>/dev/null; then + git -C Engine archive "$pin" assets/shaders/include | tar -x -C "/tmp/$tree/Engine" + fi + done + git -C Engine diff --name-only "$old_pin" "$new_pin" -- assets/shaders/include \ + | sed 's|^|Engine/|' >> /tmp/changed.txt + else + echo "::warning::Engine pins are not on the remote, engine include changes are not checked." + fi + python3 Engine/scripts/check_spirv_fresh.py --base /tmp/base --head /tmp/head \ + --shaders Game/assets/shaders \ + --include Engine/assets/shaders/include \ + --include Game/assets/shaders/include \ + --changed /tmp/changed.txt + + - name: Every OpenGL shader validates + working-directory: SleakCraft/Game/assets/shaders + run: | + fail=0 + for src in *_gl.vert *_gl.frag; do + [ -e "$src" ] || continue + out="/tmp/gl_expanded.${src##*.}" + if ! glslangValidator -E -I../../../Engine/assets/shaders/include \ + -Iinclude "$src" > "$out" || + ! glslangValidator "$out"; then + echo "FAILED to validate: $src" + fail=1 + fi + done + exit $fail + # Compiling proves nothing about whether the game runs. Runners have no GPU, # but Mesa's software Vulkan (lavapipe) plus a virtual display is enough to # boot the real binary into a real world and catch startup crashes, missing @@ -323,6 +392,10 @@ jobs: echo "A crash signature appeared in the output." exit 1 fi + if grep -qE "Shader compilation failed|Shader program linking failed|Cannot read shader" smoke.log; then + echo "An OpenGL shader failed to build." + exit 1 + fi echo "Booted into a world and survived the run window." - name: Upload the smoke log From ce8dfb9853064f51daf8414ad939d43ed32c8bbd Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 12:26:03 +0300 Subject: [PATCH 08/10] build: refuse a game include/sleak and stage engine includes first --- Client/CMakeLists.txt | 10 +++++++++- Game/CMakeLists.txt | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt index 32f9195..8be6640 100644 --- a/Client/CMakeLists.txt +++ b/Client/CMakeLists.txt @@ -22,12 +22,17 @@ set(_asset_out_dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets") set(_sleak_seen_outputs "") +# Optional third argument: subfolder of bin/assets to copy into function(sleak_track_asset_dir SRC_DIR LABEL) + set(_dst_root "${_asset_out_dir}") + if(ARGC GREATER 2) + set(_dst_root "${_asset_out_dir}/${ARGV2}") + endif() file(GLOB_RECURSE _files CONFIGURE_DEPENDS "${SRC_DIR}/*") set(_outputs "") foreach(_src ${_files}) file(RELATIVE_PATH _rel "${SRC_DIR}" "${_src}") - set(_dst "${_asset_out_dir}/${_rel}") + set(_dst "${_dst_root}/${_rel}") list(FIND _sleak_seen_outputs "${_dst}" _seen_idx) if(NOT _seen_idx EQUAL -1) continue() @@ -63,6 +68,9 @@ foreach(_spv ${_game_spirv}) list(APPEND SLEAK_ASSET_OUTPUTS "${_dst}") list(APPEND _sleak_seen_outputs "${_dst}") endforeach() +# Include libraries resolve engine first, as glslc -I does +sleak_track_asset_dir("${CMAKE_SOURCE_DIR}/Engine/assets/shaders/include" + "engine" "shaders/include") sleak_track_asset_dir("${CMAKE_SOURCE_DIR}/Game/assets" "game") sleak_track_asset_dir("${CMAKE_SOURCE_DIR}/Engine/assets" "engine") diff --git a/Game/CMakeLists.txt b/Game/CMakeLists.txt index 98a89bd..a232e28 100644 --- a/Game/CMakeLists.txt +++ b/Game/CMakeLists.txt @@ -18,6 +18,12 @@ target_link_libraries(SleakGame PRIVATE Engine) # --- SPIR-V shaders (committed .spv are only the fallback without glslc) --- set(GAME_SHADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets/shaders") set(GAME_SHADER_INCLUDE_DIR "${GAME_SHADER_DIR}/include") +# sleak/ is the engine's include namespace +if(EXISTS "${GAME_SHADER_INCLUDE_DIR}/sleak") + message(FATAL_ERROR + "${GAME_SHADER_INCLUDE_DIR}/sleak would shadow the engine include " + "library. Put game includes in include/craft/ instead.") +endif() file(GLOB GAME_VULKAN_SHADERS CONFIGURE_DEPENDS "${GAME_SHADER_DIR}/*.vert" "${GAME_SHADER_DIR}/*.frag" From 6fb34b271659803abed8c0a4b012481d8d923bc4 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 12:26:04 +0300 Subject: [PATCH 09/10] build: shorten the game SPIR-V staging comment --- Client/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt index 8be6640..179816e 100644 --- a/Client/CMakeLists.txt +++ b/Client/CMakeLists.txt @@ -52,8 +52,7 @@ function(sleak_track_asset_dir SRC_DIR LABEL) endfunction() set(SLEAK_ASSET_OUTPUTS "") -# Game SPIR-V built from source claims its outputs first, so the committed -# fallback copies are skipped +# Built game SPIR-V wins over the committed copies get_property(_game_spirv GLOBAL PROPERTY GAME_SPIRV_FILES) foreach(_spv ${_game_spirv}) get_filename_component(_name "${_spv}" NAME) From f8289ffb6c795ea47909c9a36630310872672074 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Mon, 9 Nov 2026 12:44:30 +0300 Subject: [PATCH 10/10] build: pin Engine to the shader-includes merge --- Engine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine b/Engine index e03be79..a393409 160000 --- a/Engine +++ b/Engine @@ -1 +1 @@ -Subproject commit e03be79058b35cc92aadbf48201cf67cdd37f336 +Subproject commit a393409321b5138653632927c1656297f6899f07