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
2 changes: 1 addition & 1 deletion docs/RENDERER_2026_ARCHITECTURE_PASS.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Use **Vulkan as the primary renderer architecture**, freeze OpenGL as compatibil

## Phase 2: Lighting Scale

- Add Vulkan light records plus cluster/tile culling. **Incremental (engine):** `r_forwardPlus 1` (default 0) allocates light + tile SSBOs, packs **at most `MAX_DLIGHTS` (32)** lights from `backEnd.refdef` (indices align with `tess.dlightBits`; excess lights are omitted and a **developer** log notes when the source count exceeds the cap), and runs a **compute tile cull** (`forward_plus_tile_cull.comp`, 16×16 tiles, up to **8** index slots per tile; active count **4–8** via latched **`r_forwardPlusMaxPerTile`**, default **8**) after `RB_BeginDrawingView` inside the main render pass. Tile grid and NDC→pixel use **`vk_get_render_target_width/height`** (FBO / `r_renderScale`); the tile SSBO is **reallocated when that resolution changes** (no `vid_restart` for resize alone). **PBR fragment:** descriptor set 18 binds light + tile + **param** SSBOs (clip matrix for shading). `r_forwardPlusDebug` (0–1) = debug overlay; **`r_forwardPlusShade`** (0–4, default 0) adds **experimental diffuse + microfacet spec** (per-light `CalcSpecular`, scaled) from tile-culled **point and linear/spot** lights. When `r_forwardPlus` is on, **`pbrForwardPlus.y`** carries **`floatBitsToUint(tess.dlightBits)`** so the shader **skips** any packed light index whose bit is set in `tess.dlightBits` (first **32** indices only; matches typical `MAX_DLIGHTS` range). Primary direct is still **softly renormalized** vs Forward+ energy. Works with deluxe/lightmap; toggling shade **invalidates cached pipelines** next frame.
- Add Vulkan light records plus cluster/tile culling. **Incremental (engine):** `r_forwardPlus 1` (default 0) allocates light + tile SSBOs, packs **at most `MAX_DLIGHTS` (32)** lights from `backEnd.refdef` (indices align with `tess.dlightBits`; excess lights are omitted and a **developer** log notes when the source count exceeds the cap), and runs a **compute tile cull** (`forward_plus_tile_cull.comp`, **`VK_FP_TILE_DIM` (16)** px tiles via `ceil(viewport / dim)`, up to **8** index slots per tile; active count **4–8** via latched **`r_forwardPlusMaxPerTile`**, default **8**) after `RB_BeginDrawingView` inside the main render pass. **Compute + PBR fragment** derive **tile pixel size** from the packed **viewport ÷ tile grid** so cull, debug overlay, and experimental shade stay aligned if tile policy changes. Tile grid and NDC→pixel use **`vk_get_render_target_width/height`** (FBO / `r_renderScale`); the tile SSBO is **reallocated when that resolution changes** (no `vid_restart` for resize alone). **PBR fragment:** descriptor set 18 binds light + tile + **param** SSBOs (clip matrix for shading). `r_forwardPlusDebug` (0–1) = debug overlay; **`r_forwardPlusShade`** (0–4, default 0) adds **experimental diffuse + microfacet spec** (per-light `CalcSpecular`, scaled) from tile-culled **point and linear/spot** lights. When `r_forwardPlus` is on, **`pbrForwardPlus.y`** carries **`floatBitsToUint(tess.dlightBits)`** so the shader **skips** any packed light index whose bit is set in `tess.dlightBits` (first **32** indices only; matches typical `MAX_DLIGHTS` range). Primary direct is still **softly renormalized** vs Forward+ energy. Works with deluxe/lightmap; toggling shade **invalidates cached pipelines** next frame.
- Introduce Forward+ shading for local lights.
- Keep shadow budgets conservative and explicit.

Expand Down
14 changes: 7 additions & 7 deletions scripts/renderer_regression_check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -219,14 +219,14 @@ else
fi

echo ""
echo "Forward+ tile pixel size: VK_FP_TILE_DIM vs compute shader literals:"
fp_dim="$(sed -n 's/^#define VK_FP_TILE_DIM[[:space:]]*\([0-9][0-9]*\)u*$/\1/p' "$FP_C" | head -1)"
if [[ -z "$fp_dim" ]]; then
fail "could not parse VK_FP_TILE_DIM from vk_forward_plus.c"
elif ! grep -q "tileX \* ${fp_dim}u" "$FP_COMP" 2>/dev/null; then
fail "forward_plus_tile_cull.comp sphere_tile_overlap must use VK_FP_TILE_DIM (${fp_dim}) in tile corner math"
echo "Forward+ tile cull: sphere overlap uses viewport-derived tile pixels (not hard-coded 16px):"
if grep -q 'tileX \* 16u\|tileY \* 16u' "$FP_COMP" 2>/dev/null; then
fail "forward_plus_tile_cull.comp must not hard-code 16px tiles; derive tilePx from viewport / tileGrid (see vk_forward_plus VK_FP_TILE_DIM)"
elif ! grep -q 'tilePxX' "$FP_COMP" 2>/dev/null || ! grep -q 'sphere_tile_overlap' "$FP_COMP" 2>/dev/null; then
fail "forward_plus_tile_cull.comp expected tilePxX/tilePxY sphere_tile_overlap path"
else
pass "VK_FP_TILE_DIM=$fp_dim matches forward_plus_tile_cull.comp tile grid math"
fp_dim="$(sed -n 's/^#define VK_FP_TILE_DIM[[:space:]]*\([0-9][0-9]*\)u*$/\1/p' "$FP_C" | head -1)"
pass "forward_plus_tile_cull uses dynamic tile pixels (VK_FP_TILE_DIM=$fp_dim on host grid)"
fi

echo ""
Expand Down
17 changes: 10 additions & 7 deletions src/renderers/vulkan/shaders/glsl/forward_plus_tile_cull.comp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#version 450

/*
* Forward+ scaffolding: per-tile light index list (max 8 per 16x16 tile).
* Forward+ scaffolding: per-tile light index list (max 8 per tile; tile pixel size = viewport / tileGrid).
* CPU-packed lights: binding 0 — vec4 (x=count,...), vec4 (tiles_x, tiles_y, viewport_w, viewport_h), then count * 4 vec4 records.
*/

Expand Down Expand Up @@ -31,11 +31,11 @@ layout(push_constant) uniform Push {
uint maxPerTile;
} pc;

bool sphere_tile_overlap(vec2 centerPx, float radiusPx, uint tileX, uint tileY) {
float minX = float(tileX * 16u);
float minY = float(tileY * 16u);
float maxX = minX + 16.0;
float maxY = minY + 16.0;
bool sphere_tile_overlap(vec2 centerPx, float radiusPx, uint tileX, uint tileY, float tilePxX, float tilePxY) {
float minX = float(tileX) * tilePxX;
float minY = float(tileY) * tilePxY;
float maxX = minX + tilePxX;
float maxY = minY + tilePxY;
vec2 closest = clamp(centerPx, vec2(minX, minY), vec2(maxX, maxY));
return distance(centerPx, closest) <= radiusPx + 1e-3;
}
Expand All @@ -47,6 +47,7 @@ void main() {
}

uint tilesX = pc.tileGrid.x;
uint tilesY = pc.tileGrid.y;
uint tileX = tileId % tilesX;
uint tileY = tileId / tilesX;

Expand All @@ -66,6 +67,8 @@ void main() {

float vw = lights.data[1].z;
float vh = lights.data[1].w;
float tilePxX = vw / max(float(tilesX), 1.0);
float tilePxY = vh / max(float(tilesY), 1.0);

uint outCount = 0u;
for (uint li = 0u; li < n && outCount < cap; li++) {
Expand All @@ -90,7 +93,7 @@ void main() {
float distW = max(abs(clip.w), 1e-3);
float radiusPx = clamp(radius * (0.5 * vw) / distW, 2.0, vw + vh);

if (!sphere_tile_overlap(centerPx, radiusPx, tileX, tileY)) {
if (!sphere_tile_overlap(centerPx, radiusPx, tileX, tileY, tilePxX, tilePxY)) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
glslang_validator_path=/usr/bin/glslangValidator
glslang_validator_version=Glslang Version: 11:15.1.0
generated_at=2026-04-20T19:11:32Z
shader_data_sha256=d85565a5bab935619eea87c0372b8ad703ad0e7a8ba7ca3f8710f8c8e9d366e3
generated_at=2026-04-20T19:18:59Z
shader_data_sha256=b6c7f805a5810cddfd7365a786824d42ea896f25f1d35c3ec412e157eaeee493
shader_binding_sha256=960921d52493ab4fcf2f8b60edb34fa0b64a852c47b5ad2ae3abdf06c35ebe4a
Loading
Loading