Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 76 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ jobs:
uses: actions/checkout@v7
with:
path: SleakCraft
fetch-depth: 0

- name: Check out the engine
uses: actions/checkout@v7
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
26 changes: 25 additions & 1 deletion Client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -47,10 +52,29 @@ function(sleak_track_asset_dir SRC_DIR LABEL)
endfunction()

set(SLEAK_ASSET_OUTPUTS "")
# 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)
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()
# 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")

add_custom_target(CopyAssets ALL DEPENDS ${SLEAK_ASSET_OUTPUTS})
add_dependencies(CopyAssets GameShaders)
add_dependencies(SleakCraft CopyAssets)

# --- Post-build: copy runtime DLLs (Windows) ---
Expand Down
9 changes: 6 additions & 3 deletions Client/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ static void PrintHelp(const char* exe) {
<< " -t <name> Window title (use _ for spaces)\n"
<< " --fullscreen Start in fullscreen\n"
<< "\nWorld\n"
<< " -world <name> Load world if save exists, create new otherwise\n"
<< " -world <name> Load world if save exists, create new "
"otherwise\n"
<< " -seed <n> Seed for new world (default: random)\n"
<< " -rd <n> Initial render distance in chunks (default: 8)\n"
<< " -rd <n> Initial render distance in chunks (default: "
"8)\n"
<< "\nGraphics\n"
<< " -msaa <n> MSAA sample count: 1, 2, 4, 8\n"
<< " --vsync Enable VSync on launch\n"
Expand All @@ -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 <file> Write frame N as a PPM, then exit (Vulkan)\n"
<< " -capture <file> Write frame N as a PPM, then exit (Vulkan, "
"OpenGL)\n"
<< " -capture-frame <n> Frame index for -capture (default: 600)\n"
<< "\nDebug\n"
<< " --validate Enable Vulkan validation (Debug: always on)\n"
Expand Down
2 changes: 1 addition & 1 deletion Engine
Submodule Engine updated 50 files
+36 −19 .github/workflows/ci.yml
+15 −2 CMakeLists.txt
+2 −20 assets/shaders/default_shader.frag
+ − assets/shaders/default_shader.frag.spv
+2 −16 assets/shaders/default_shader.vert
+ − assets/shaders/default_shader.vert.spv
+2 −16 assets/shaders/gbuffer.vert
+ − assets/shaders/gbuffer.vert.spv
+69 −0 assets/shaders/include/sleak/binding.glsl
+62 −0 assets/shaders/include/sleak/frame.glsl
+34 −0 assets/shaders/include/sleak/shadow_light.glsl
+2 −27 assets/shaders/lighting_pass.frag
+2 −15 assets/shaders/skinned_shader.frag
+ − assets/shaders/skinned_shader.frag.spv
+2 −16 assets/shaders/skinned_shader.vert
+ − assets/shaders/skinned_shader.vert.spv
+4 −1 docs/pages/lighting.md
+83 −17 docs/pages/shader_authoring.md
+83 −0 include/private/Graphics/Common/ConstantBuffer.hpp
+22 −0 include/private/Graphics/Common/FrameCapture.hpp
+53 −0 include/private/Graphics/Common/FrameDataBuilder.hpp
+58 −0 include/private/Graphics/Common/MatrixMath.hpp
+3 −0 include/private/Graphics/Common/RenderContext.hpp
+14 −0 include/private/Graphics/Common/Renderer.hpp
+62 −0 include/private/Graphics/Common/ShaderIncludes.hpp
+33 −0 include/private/Graphics/OpenGL/OpenGLRenderer.hpp
+7 −3 include/private/Graphics/OpenGL/OpenGLShader.hpp
+3 −44 include/private/Graphics/Vulkan/VulkanInternal.hpp
+29 −3 include/private/Graphics/Vulkan/VulkanRenderer.hpp
+19 −1 include/public/Core/Application.hpp
+2 −1 include/public/Debug/ShaderHotReload.hpp
+113 −0 scripts/check_spirv_fresh.py
+221 −0 scripts/check_ubo_layout.py
+21 −4 src/Core/Application.cpp
+110 −19 src/Debug/ShaderHotReload.cpp
+24 −0 src/Graphics/Common/FrameCapture.cpp
+152 −0 src/Graphics/Common/FrameData.cpp
+2 −0 src/Graphics/Common/RenderCommandQueue.cpp
+433 −0 src/Graphics/Common/ShaderIncludes.cpp
+124 −1 src/Graphics/OpenGL/OpenGLRenderer.cpp
+23 −16 src/Graphics/OpenGL/OpenGLShader.cpp
+6 −16 src/Graphics/Vulkan/VulkanCapture.cpp
+4 −4 src/Graphics/Vulkan/VulkanDeferred.cpp
+14 −9 src/Graphics/Vulkan/VulkanDescriptors.cpp
+147 −0 src/Graphics/Vulkan/VulkanFrameData.cpp
+10 −1 src/Graphics/Vulkan/VulkanProfiler.cpp
+6 −0 src/Graphics/Vulkan/VulkanRenderer.cpp
+11 −2 src/Graphics/Vulkan/VulkanShadow.cpp
+4 −20 src/Lighting/LightManager.cpp
+14 −4 src/Scene/SceneBase.cpp
55 changes: 55 additions & 0 deletions Game/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,58 @@ 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")
# 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"
)
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})
20 changes: 4 additions & 16 deletions Game/assets/shaders/flat_shader.frag
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#version 450
#extension GL_GOOGLE_include_directive : require

layout(location = 0) in vec3 fragWorldPos;
layout(location = 1) in vec3 fragWorldNorm;
Expand All @@ -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;

Expand Down Expand Up @@ -120,5 +108,5 @@ void main() {
lit = mix(uFogColor.rgb, lit, fogFactor);
}

outColor = vec4(lit, texColor.a);
outColor = SleakFrameGuard(vec4(lit, texColor.a));
}
Binary file modified Game/assets/shaders/flat_shader.frag.spv
Binary file not shown.
17 changes: 2 additions & 15 deletions Game/assets/shaders/flat_shader.vert
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#version 450
#extension GL_GOOGLE_include_directive : require

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
Expand All @@ -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;
Expand Down
Binary file modified Game/assets/shaders/flat_shader.vert.spv
Binary file not shown.
5 changes: 4 additions & 1 deletion Game/assets/shaders/flat_shader_gl.frag
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#version 450 core
#extension GL_GOOGLE_include_directive : require

in vec3 fragWorldPos;
in vec3 fragWorldNorm;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -142,5 +145,5 @@ void main() {
lit = mix(FogColor.rgb, lit, fogFactor);
}

outColor = vec4(lit, texColor.a);
outColor = SleakFrameGuard(vec4(lit, texColor.a));
}
17 changes: 5 additions & 12 deletions Game/assets/shaders/flat_shader_gl.vert
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -39,5 +32,5 @@ void main() {
fragColor = inColor;
fragUV = inUV;

fragShadowCoord = ShadowLightVP * worldPos;
fragShadowCoord = uLightVP * worldPos;
}
Loading
Loading