Skip to content

feat(graphics): Industry-Grade Shadow System & Vulkan Memory Optimization - #241

Merged
MichaelFisher1997 merged 33 commits into
mainfrom
dev
Jan 28, 2026
Merged

feat(graphics): Industry-Grade Shadow System & Vulkan Memory Optimization#241
MichaelFisher1997 merged 33 commits into
mainfrom
dev

Conversation

@MichaelFisher1997

Copy link
Copy Markdown
Collaborator

Summary

This PR implements a professional, industry-standard shadow system and optimizes Vulkan memory management.

Key Changes:

  • Professional Shadow Quality:
    • Implemented high-quality 16-Tap Poisson Disk PCF filtering for smooth, filmic shadow edges.
    • Added Normal Offset Bias in the shadow vertex shader to eliminate self-shadowing artifacts (shadow acne) without causing peter panning.
    • Stabilized Cascaded Shadow Maps (CSM) by implementing texel-snapping for light-space X/Y coordinates.
  • Precision & Stability:
    • Aligned the entire shadow pipeline with the Reverse-Z standard (mapping Near to 1.0 and Far to 0.0), providing maximum precision for voxel surfaces.
    • Corrected light rotation matrices in csm.zig to ensure proper scene capture.
    • Refined shadow bias logic with per-cascade scaling and slope-adaptive bias.
  • Performance & Validation:
    • Implemented Persistent Memory Mapping for all host-visible Vulkan buffers.
    • This fixes numerous "memory already mapped" validation errors and reduces driver overhead by eliminating per-frame map/unmap operations.
  • Material Integration:
    • Restored and fully integrated the professional terrain.frag logic, including PBR lighting, cloud shadows, SSAO, and smooth LOD transitions.

MichaelFisher1997 and others added 26 commits January 24, 2026 11:06
…218)

* feat: enable and stabilize LOD system (#216)

* feat: enable and stabilize LOD system

- Exposed lod_enabled toggle in settings and presets.
- Updated presets to enable LOD on HIGH/ULTRA.
- Optimized LOD performance by moving cleanup to throttled update.
- Fixed LOD-to-chunk transition masking in shader.
- Added unit tests for LOD settings application.

* refactor: apply SOLID principles and performance documentation to LOD system

- Introduced ILODConfig interface to decouple settings from LOD logic (Dependency Inversion).
- Extracted mask radius calculation into a pure function in ILODConfig for better testability (Single Responsibility).
- Documented the 4-frame throttle rationale in LODManager.
- Fixed a bug where redundant LOD chunks were being re-queued immediately after being unloaded.
- Added end-to-end unit test for covered chunk cleanup.

* fix: resolve build errors and correctly use ILODConfig interface

- Fixed type error in World.zig where LODManager was used as a function instead of a type.
- Updated all remaining direct radii accesses to use ILODConfig.getRadii() in WorldStreamer and WorldRenderer.
- Verified fixes with successful 'zig build test'.

* fix: remove redundant LOD cleanup from render path

- Removed redundant 'unloadLODWhereChunksLoaded' call from 'LODRenderer.render', fixing the double-call bug.
- Decoupled 'calculateMaskRadius' by adding it to the 'ILODConfig' vtable.
- Synchronized code with previous comments to ensure the throttled cleanup is now correctly localized to the update loop.

* Phase 2: Render Pipeline Modernization - Offscreen HDR Buffer & Post-Process Pass (#217)

* Phase 2: Render Pipeline Modernization - Offscreen HDR Buffer & Post-Process Pass

* Phase 2: Add synchronization barriers and improve resource lifecycle safety

* Phase 2: Add descriptor null-guards and configurable tone mapper selection

* Phase 2: Fix Vulkan offscreen HDR rendering and post-process pass initialization

Detailed changes:
- Decoupled main rendering pass from swapchain using an offscreen HDR buffer.
- Fixed initialization order to ensure HDR resources and main render pass are created before pipelines.
- Implemented dedicated post-process framebuffers for swapchain presentation.
- Added a fallback post-process pass for UI-only frames to ensure correct image layout transition.
- Fixed missing SSAO blur render pass creation.
- Added shadow 'regular' sampler and bound it to descriptor set binding 4.
- Added nullification of Vulkan handles after destruction to prevent validation errors.
- Improved swapchain recreation logic with pipeline rebuild tracking.
- Added debug logging for render pass and swapchain lifecycle.

* Implement FXAA, Bloom, and Velocity Buffer for Phase 3

* Phase 3 Fixes: Address review comments (memory leaks, hardcoded constants)

* feat: modularize FXAA and Bloom systems, add velocity buffer, and improve memory safety

* fix: add missing shadow sampler creation in createShadowResources

* fix: add missing G-Pass image/framebuffer creation and fix Bloom push constant stage flags

- Add G-Pass normal, velocity, and depth image creation that was lost during refactoring
- Create G-Pass framebuffer with all 3 attachments (normal, velocity, depth)
- Fix Bloom push constants to use VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT
  matching the pipeline layout definition

Fixes integration test failures with NULL VkImage and push constant validation errors.

* fix: resolve push constant and render pass ordering issues in Vulkan backend

* feat: add comprehensive branching strategy, PR templates, and contributing guidelines

- Add dev branch with branch protection (0 reviews, strict mode, linear history)
- Add 4 PR templates: feature, bug, hotfix, ci
- Add CONTRIBUTING.md with full workflow documentation
- Update build.yml to trigger on main and dev
- Add hotfix keywords to issue-labeler.json
- Add universal PR template as fallback

Resolves: Branching strategy and workflow improvements

* refactor: move FXAA/Bloom resource creation to systems, fix leaks

* fix: correct bloom descriptor binding count to fix validation error
* feat: enable and stabilize LOD system (#216)

* feat: enable and stabilize LOD system

- Exposed lod_enabled toggle in settings and presets.
- Updated presets to enable LOD on HIGH/ULTRA.
- Optimized LOD performance by moving cleanup to throttled update.
- Fixed LOD-to-chunk transition masking in shader.
- Added unit tests for LOD settings application.

* refactor: apply SOLID principles and performance documentation to LOD system

- Introduced ILODConfig interface to decouple settings from LOD logic (Dependency Inversion).
- Extracted mask radius calculation into a pure function in ILODConfig for better testability (Single Responsibility).
- Documented the 4-frame throttle rationale in LODManager.
- Fixed a bug where redundant LOD chunks were being re-queued immediately after being unloaded.
- Added end-to-end unit test for covered chunk cleanup.

* fix: resolve build errors and correctly use ILODConfig interface

- Fixed type error in World.zig where LODManager was used as a function instead of a type.
- Updated all remaining direct radii accesses to use ILODConfig.getRadii() in WorldStreamer and WorldRenderer.
- Verified fixes with successful 'zig build test'.

* fix: remove redundant LOD cleanup from render path

- Removed redundant 'unloadLODWhereChunksLoaded' call from 'LODRenderer.render', fixing the double-call bug.
- Decoupled 'calculateMaskRadius' by adding it to the 'ILODConfig' vtable.
- Synchronized code with previous comments to ensure the throttled cleanup is now correctly localized to the update loop.

* Phase 4: Implement GPU Profiling, Timing Overlay, and Preset Rebalancing

* fix: keep UI visible after post-processing and stabilize LOD threading
- fix(lod): implemented availability-based LOD transitions to eliminate horizon gaps
- fix(vulkan): resolved clear value and descriptor layout validation errors in Medium+ presets
- fix(lighting): boosted sky/cloud radiance to match PBR terrain and reduced fog gloom
- fix(ui): restored and thickened block selection outline in HDR pass
- fix(shadows): corrected Reverse-Z depth bias to eliminate acne on near blocks
- feat(hud): added LOD count to debug overlay
…xtures (#222)

* feat: implement visual polish including dithered LOD transitions, improved AO, and grid-free textures

- Implement screen-space dithered crossfading for LOD transitions
- Add distance-aware voxel AO to eliminate dark rectangular artifacts on distant terrain
- Disable texture atlas mipmapping to remove visible block boundary grid lines
- Enhance block selection outline thickness and expansion for better visibility
- Pass mask_radius from vertex to fragment shader for precise transition control

* fix: add missing bayerDither4x4 function and clean up magic numbers in terrain shader

- Implement missing bayerDither4x4 function in fragment shader
- Add missing vMaskRadius input declaration to fragment shader
- Extract LOD_TRANSITION_WIDTH and AO_FADE_DISTANCE to constants
- Remove trailing whitespace in UV calculation
- Fix shader compilation error introduced in previous commit

* fix: restore missing shadows and fix broken lighting logic in terrain shader

- Rewrite terrain fragment shader lighting logic to fix broken brackets and scope issues
- Ensure totalShadow is applied to all lighting branches (Legacy, PBR, and non-PBR)
- Clean up variable naming to avoid shadowing uniform block names
- Maintain previous visual polish fixes (LOD dithering, distance-aware AO, and grid-free textures)

* fix(graphics): Restore and optimize shadows, add debug view

- Fixed shadow rendering by correcting reverse-Z bias direction in shadow_system.zig
- Improved shadow visibility by masking ambient occlusion (reduced ambient by 80% in shadow)
- Optimized shadow resolution defaults (High: 4096->2048) for better performance (~12ms frame time)
- Added 'G' key toggle for Red/Green shadow debug view
- Fixed input/settings synchronization on app startup to ensure correct RHI state
- Fixed shadow acne by increasing depth bias slope factor

* chore: remove temporary test output files
* Refactor: Relocate drawDebugShadowMap to Debug Overlay System

* Refactor: Address code review comments for Debug Overlay refactor

* Fix: Shadow sampler initialization order and safety in destroyTexture

* Polish: Add InvalidImageView error and doc comments for Debug Overlay

* Docs: Add documentation for registerExternalTexture and DebugShadowOverlay

* Test: Add unit test for ResourceManager.registerExternalTexture validation
* refactor: relocate computeSSAO to dedicated SSAOSystem

- Introduced ISSAOContext in rhi.zig to follow interface segregation.
- Created SSAOSystem in vulkan/ssao_system.zig to encapsulate SSAO resources and logic.
- Updated Vulkan backend to integrate the new system and implement ISSAOContext.
- Refactored RenderGraph and mocks to use the new segregated interface.
- Closes #225

* refactor(ssao): improve SSAOSystem implementation based on PR feedback

- Extracted initialization phases into helper functions (SRP).
- Fixed misnamed command_pool parameter type.
- Added error handling for vkMapMemory.
- Improved shader module cleanup using defer and errdefer.
- Standardized error handling style for Vulkan calls.

* refactor(ssao): final polish of SSAOSystem implementation

- Extracted kernel and noise constants.
- Standardized shader path constants.
- Improved parameter naming and error checking.
- Added unit test for SSAO parameter defaults.
- Verified all 181 tests pass.

* fix(integration): resolve compilation errors and apply final polish

- Fixed Mat4.inverse() call in render_graph.zig.
- Removed stray ssao_kernel_ubo references in rhi_vulkan.zig.
- Fixed VulkanDevice mutability in SSAOSystem.init.
- Applied all code quality improvements from PR review.

* fix(rhi): resolve compilation and logic errors in SSAO refactor

- Implemented registerNativeTexture in ResourceManager to support externally managed images.
- Fixed double-free in ResourceManager.deinit by checking for memory ownership.
- Fixed TextureFormat usage (.red instead of .r8_unorm).
- Fixed stray SSAO resource references in rhi_vulkan.zig.
- Restored main descriptor set updates for SSAO map in rhi_vulkan.zig.
- Added missing initial layout transitions for SSAO images.

* refactor(graphics): final polish and standardization of post-process systems

- Introduced shader_registry.zig to centralize and de-duplicate shader paths.
- Removed redundant vkQueueWaitIdle in SSAOSystem texture initialization.
- Added internal unit tests for SSAOSystem (noise and kernel generation).
- Merged latest dev changes and resolved vtable conflicts.
- Standardized error handling and resource cleanup across SSAO, Bloom, and FXAA systems.

* fix(ssao): restore queue wait before freeing upload command buffer

Restored 'vkQueueWaitIdle' in SSAOSystem.initNoiseTexture to ensure the upload command buffer
is no longer in use by the GPU before it is freed. This fixes a validation error and
potential segmentation fault during initialization.
Fixes two critical energy conservation violations in PBR lighting:

1. Sun emission missing π division (CRITICAL)
   - Direct lighting was 3.14x too bright because sun color
     was not divided by π while BRDF diffuse term already was
   - Added / PI division to all sun color calculations (4 locations):
     * Volumetric lighting (line 242)
     * PBR direct lighting (line 453)
     * Non-PBR blocks direct lighting (line 486)
     * LOD mode direct lighting (line 519)
   - This reduces direct lighting energy to physically correct levels

2. IBL environment map not pre-filtered
   - Environment map was sampled at fixed mip level 8.0
     regardless of surface roughness
   - Added MAX_ENV_MIPS = 8.0 constant
   - Now samples mip level based on surface roughness:
     * PBR blocks: envMipLevel = roughness * 8.0
     * Non-PBR blocks: envMipLevel = 0.5 * 8.0
   - Rough surfaces get blurrier ambient reflections
   - Smooth surfaces get sharper ambient reflections

Impact:
- Sunlit surfaces are ~3.14x less bright (physically correct)
- Ambient reflections now properly vary with roughness
- Tone-mapping handles reduced energy appropriately
- Shadows more visible in sunlit areas

Fixes #230
- Replaced magic numbers (3.0, 4.0, 0.5, 8.0) with documented constants
- Refactored terrain.frag main() into focused functions (calculatePBR, calculateNonPBR, calculateLOD)
- Removed duplicate/dead code in terrain.frag lighting logic
- Corrected GlobalUniforms comment for pbr_params.w
- Verified removal of HUD notification spam in world.zig

Addresses code quality issues identified in PR review for #230.
…vation

- Extracted duplicate IBL sampling logic into sampleIBLAmbient()
- Added descriptive comments for MAX_ENV_MIP_LEVEL and SUN_PBR_MULTIPLIER constants
- Increased epsilon to 0.001 in BRDF denominators to improve numerical stability
- Improved naming consistency for albedo/vColor in main dispatch
- Added comment explaining vMaskRadius usage scope
- Cleaned up minor inconsistencies in rhi_vulkan.zig and world.zig
…rain shader

- Unified IBL ambient sampling via sampleIBLAmbient()
- Extracted BRDF evaluation into computeBRDF() for PBR clarity
- Consolidated cascade blending logic into calculateCascadeBlending()
- Standardized legacy lighting paths into calculateLegacyDirect()
- Documented physics justification for SUN_RADIANCE_TO_IRRADIANCE constant
- Extracted IBL_CLAMP_VALUE and VOLUMETRIC_DENSITY_SCALE constants
- Renamed calculateShadow to calculateShadowFactor for naming consistency
- Improved numerical stability by using 0.001 epsilon in BRDF denominators

Refinement of #230 fixes based on PR review.
- Standardized all lighting functions to compute* naming pattern
- Extracted IBL_CLAMP_VALUE and VOLUMETRIC_DENSITY_SCALE constants
- Consolidated legacy lighting logic into computeLegacyDirect()
- Improved physics derivation documentation for sun radiance multipliers
- Refactored monolithic main() to reduce nesting and improve readability
- Verified numerical stability with consistent 0.001 epsilon

Final polish for issue #230.
- Extracted remaining magic numbers into documented constants (IBL_CLAMP, VOLUMETRIC_DENSITY_FACTOR, etc.)
- Consolidated legacy lighting intensity multipliers
- Standardized all lighting functions to compute* naming convention
- Added detailed physics derivation comments for normalization factors
- Improved numerical stability in BRDF calculations

Final polish of #230.
- Renamed computeCascadeBlending to computeShadowCascades for clarity
- Extracted remaining magic numbers (DIELECTRIC_F0, COOK_TORRANCE_DENOM_FACTOR, VOLUMETRIC_DENSITY_FACTOR)
- Documented physics justification for radiance-to-irradiance conversion factor
- Standardized all function naming to compute* pattern
- Consolidated legacy and LOD lighting intensity constants (LEGACY_LIGHTING_INTENSITY, LOD_LIGHTING_INTENSITY)
- Re-verified energy conservation normalization factors across all lighting paths.
- Restored SUN_VOLUMETRIC_INTENSITY constant (3.0) for LOD/Volumetric lighting
- Fixed syntax error (duplicate code block/dangling brace)
- Verified shader compilation and project build
- Ensured legacy and LOD lighting paths use correct, distinct multipliers
- Restored missing getVolShadow function definition
- Removed duplicate/broken code blocks causing syntax errors
- Verified shader compilation and energy conservation logic
- Finalized refactoring for issue #230
… and 16-tap PCF

- Implemented high-quality 16-tap Poisson disk PCF filtering for smooth shadow edges
- Corrected shadow pipeline to use Reverse-Z (Near=1.0, Far=0.0) for maximum precision
- Refined shadow bias system with per-cascade scaling and slope-adaptive bias
- Optimized Vulkan memory management by implementing persistent mapping for all host-visible buffers
- Fixed Vulkan validation errors caused by frequent map/unmap operations
- Added normal offset bias in shadow vertex shader to eliminate self-shadowing artifacts
- Integrated stable cascade selection based on Euclidean distance
@github-actions github-actions Bot added documentation Improvements or additions to documentation engine game shaders labels Jan 27, 2026
@github-actions github-actions Bot removed the game label Jan 27, 2026
@opencode-agent

Copy link
Copy Markdown
Contributor

Code Review: Industry-Grade Shadow System & Vulkan Memory Optimization

Executive Summary

The PR implements professional-quality cascaded shadow maps with PCF filtering, normal offset bias, and persistent memory mapping. The core shadow algorithm is sound with proper texel snapping and Reverse-Z alignment. However, architectural concerns exist around tight coupling and the monolithic VulkanContext structure.


SOLID Principles Assessment

Principle Score Assessment
Single Responsibility 6/10 rhi_vulkan.zig violates SRP (~5000 lines, manages swapchain, rendering, UI, bloom, FXAA, clouds, shadows, G-buffer). descriptor_manager.zig and csm.zig follow SRP well.
Open/Closed 5/10 rhi_vulkan.zig is difficult to extend—new pipeline types require modifying VulkanContext. descriptor_manager.zig is extensible via binding arrays.
Liskov Substitution 3/10 No explicit interfaces for ShadowSystem, DescriptorManager, or ResourceManager. RHI interface exists but concrete types used directly throughout.
Interface Segregation 4/10 VulkanContext has 50+ unrelated fields (UI state mixed with shadow state). DescriptorManager has unused bindings for some passes.
Dependency Inversion 4/10 ShadowSystem depends on concrete rhi types. DescriptorManager takes concrete *ResourceManager. Dependencies should flow through abstractions.

Critical Issues & Actionable Suggestions

1. Memory Management Bug (rhi_vulkan.zig:1022-1025)

if (ctx.shadow_system.shadow_pipeline != null) {
    c.vkDestroyPipeline(vk, ctx.shadow_system.shadow_pipeline, null);
    ctx.shadow_system.shadow_pipeline = null;
}

Issue: Destroys pipeline but doesn't recreate if createShadowResources fails partway through.
Fix: Add recreation logic or use errdefer for rollback.

2. Silent Null Returns (descriptor_manager.zig:260)

const dest = self.global_ubos_mapped[frame_index] orelse return;

Issue: Silently returns on mapping failure—causes silent rendering errors.
Fix: Return explicit error: return error.UnmappedBuffer.

3. Expensive Matrix Inverse (csm.zig:68-69)
Issue: inverse() called 3x per frame per cascade—should be cached.
Fix: Compute once and reuse: const inv_view = cam_view.inverse();

4. Debug Artifact (csm.zig:65)

const radius = @ceil(cascade_extents[split_index] / 2.0);

Issue: radius calculated but not used.
Fix: Remove or log for debugging purposes.

5. Shader Path Hardcoding (rhi_vulkan.zig:1027-1030)
Issue: SPIR-V paths hardcoded instead of using shader registry constants.
Fix: Create ShaderRegistry struct with centralized paths.


Architecture Recommendations

High Priority - Extract Shadow Pipeline:

pub const ShadowPipeline = struct {
    create(device, renderPass, extent) !Pipeline
};

Medium Priority - Split VulkanContext:

VulkanContext → 
  ├── RenderPassManager
  ├── PipelineManager  
  ├── FrameManager
  └── SubsystemManager (ShadowSystem, BloomSystem, etc.)

Medium Priority - Add Shadow System Interface:

pub const ShadowRenderer = interface {
    fn beginCascade(cmd, index, matrix) void;
    fn endCascade(cmd) void;
    fn getTexture() TextureHandle;
};

Positive Findings

  • Hardware PCF via comparison sampler (better than software PCF)
  • Deletion queues prevent use-after-free issues
  • Push constants for efficient per-draw data
  • Stable CSM algorithm with proper texel snapping
  • Reverse-Z support correctly implemented

Verdict

Approve with suggestions. The implementation is functionally sound and follows graphics programming best practices. Address the critical memory management and null-handling issues before merge. The architectural concerns are technical debt that can be addressed incrementally in future PRs.

New%20session%20-%202026-01-27T00%3A18%3A16.846Z
opencode session  |  github run

…adable Settings (#238)

* Fix keymap system bugs: G key hardcoding and F3 conflict

Fixes #235: Replaces hardcoded G key check in world.zig with input mapper action.

Fixes #236: Resolves F3 conflict by adding toggle_timing_overlay action (F4 default) and updating app.zig.

* Address code review: Remove redundant default key comments

* Implement human-readable JSON format for keybindings (V3 settings)

Fixes #237: Migrates from array-based to object-based JSON format with action names as keys.

* Address review: Add debounce logic and migration warnings

Adds explicit 200ms debounce to debug toggles in App and WorldScreen.

Adds warning log when legacy settings have more bindings than supported.

* Enable InputSettings unit tests
@github-actions github-actions Bot added the game label Jan 27, 2026
@opencode-agent

Copy link
Copy Markdown
Contributor

Code Review: Industry-Grade Shadow System & Vulkan Memory Optimization

SOLID Principles Assessment

Principle Score Assessment
Single Responsibility 6/10 rhi_vulkan.zig remains large (~5000 lines) handling multiple subsystems. descriptor_manager.zig and csm.zig follow SRP well.
Open/Closed 5/10 rhi_vulkan.zig requires modification to add new pipeline types. descriptor_manager.zig extensible via binding arrays.
Liskov Substitution 3/10 No explicit interfaces for ShadowSystem, DescriptorManager. RHI interface exists but concrete types used directly.
Interface Segregation 4/10 VulkanContext contains 50+ mixed fields. DescriptorManager has unused bindings for some passes.
Dependency Inversion 4/10 ShadowSystem depends on concrete rhi types. Dependencies should flow through abstractions.

Critical Issues

  1. Memory Management Bug (rhi_vulkan.zig:1022-1025): Pipeline destroyed but not recreated if createShadowResources fails mid-process.

  2. Silent Null Returns (descriptor_manager.zig:260): Silently returns on mapping failure—causes silent rendering errors.

  3. Matrix Inverse Performance (csm.zig:68-69): inverse() called 3x per frame—should be cached.

  4. Debug Artifact (csm.zig:65): radius calculated but unused.

Previous Review Feedback

The opencode-agent review identified these same issues. No commits appear to address them after the review was posted.

Verdict

Approve with suggestions. The shadow algorithm and memory mapping implementation are sound. Address the memory management bug, null-handling issues, and caching optimization before merge.

New%20session%20-%202026-01-27T00%3A35%3A25.875Z
opencode session  |  github run

- Fix memory management bug in shadow pipeline recreation
- Add error logging for failed UBO memory mapping
- Optimize CSM matrix inverse performance
- Fix cascade split bounding sphere bug (last_split update)
- Stabilize and refine shadow bias parameters
@opencode-agent

Copy link
Copy Markdown
Contributor

Code Review: Industry-Grade Shadow System & Vulkan Memory Optimization

Previous Review Feedback Status

Issue Status Notes
Memory Management Bug (rhi_vulkan.zig:1106-1112) FIXED Pipeline now created before old one destroyed (correct pattern)
Silent Null Returns (descriptor_manager.zig:260) PARTIAL Now logs error but still returns void (caller unaware of failure)
Matrix Inverse Performance (csm.zig:48) FIXED inv_cam_view computed once outside loop
Debug Artifact (csm.zig:65) FIXED radius is now used for texel snapping and ortho bounds

SOLID Principles Assessment

Principle Score Assessment
Single Responsibility 6/10 rhi_vulkan.zig remains monolithic (~5000 lines). ShadowSystem, DescriptorManager, CSM follow SRP well.
Open/Closed 5/10 rhi_vulkan.zig requires modification to add new pipeline types. DescriptorManager extensible via binding arrays.
Liskov Substitution 3/10 No explicit interfaces for ShadowSystem or DescriptorManager. Concrete types used directly throughout codebase.
Interface Segregation 4/10 VulkanContext has 50+ mixed fields (UI state, shadow state, bloom, etc.). DescriptorManager has unused bindings for some passes.
Dependency Inversion 4/10 ShadowSystem depends on concrete rhi types. Dependencies should flow through abstractions.

Critical Issues

1. Silent Failure in Uniform Updates (descriptor_manager.zig:259-266)

pub fn updateGlobalUniforms(self: *DescriptorManager, frame_index: usize, data: *const anyopaque) void {
    const dest = self.global_ubos_mapped[frame_index] orelse {
        std.log.err("Failed to update global uniforms: UBO not mapped", .{});
        return;  // Silent void return - caller doesn't know update failed
    };

Impact: Render frame uses stale uniform data without caller awareness.
Fix: Change return type to !void and propagate error, or use a flag to skip rendering.

2. Unused Binding in Descriptor Layout (descriptor_manager.zig)
The descriptor set layout likely contains unused bindings for passes that don't use all texture slots.
Impact: Wastes descriptor pool memory.
Fix: Generate descriptor layouts dynamically based on pass requirements.


Architectural Concerns

High Priority - God Object Pattern (rhi_vulkan.zig)
VulkanContext manages swapchain, rendering, UI, bloom, FXAA, clouds, shadows, G-buffer. This violates SRP and makes testing difficult.

Recommended Refactoring:

// Split into focused subsystems
pub const RenderBackend = struct {
    device: VulkanDevice,
    swapchain: SwapchainManager,
    frame: FrameManager,
    pipelines: PipelineRegistry,
    passes: RenderPassManager,
};

Positive Findings

  • 16-Tap PCF: Properly implemented in shader with comparison sampler
  • Reverse-Z: Correctly aligned throughout shadow pipeline (clear to 0.0, GREATER_OR_EQUAL compare)
  • Texel Snapping: Stable CSM with proper floor-based snapping in light space
  • Normal Offset Bias: Implemented in shadow.vert with texel-scaled offset
  • Persistent Mapping: Eliminates per-frame map/unmap overhead
  • Deletion Queues: Proper frame-delayed resource cleanup

Verdict

Approve with Suggestions. The shadow algorithm is sound and memory optimization is correct. Address the silent uniform update failure before merge - consider changing to error union or adding a validity flag so callers can skip rendering when uniforms are unavailable.

The architectural concerns (God Object pattern, lack of interfaces) are technical debt suitable for incremental refactoring in future PRs.

New%20session%20-%202026-01-27T01%3A07%3A20.298Z
opencode session  |  github run

- Implemented safe pipeline recreation in rhi_vulkan.zig to prevent memory bugs
- Added error logging for UBO memory mapping failures in descriptor_manager.zig
- Optimized CSM performance by caching the inverse camera view matrix
- Re-verified and stabilized shadow cascade bounding sphere logic
- Cleaned up redundant code and ensured SOLID principle compliance
@opencode-agent

Copy link
Copy Markdown
Contributor

Code Review: Industry-Grade Shadow System & Vulkan Memory Optimization

Previous Review Feedback Status

Issue Previous Review Current Status Notes
Memory Management Bug (rhi_vulkan.zig:1106-1112) FIXED VERIFIED FIXED Pipeline created before destruction - correct pattern
Silent Null Returns (descriptor_manager.zig:259-266) PARTIAL STILL PRESENT Logs error but returns void - caller unaware
Matrix Inverse Performance (csm.zig:48) FIXED VERIFIED FIXED inv_cam_view computed once outside loop
Debug Artifact (csm.zig:65) FIXED VERIFIED FIXED radius now used for texel snapping

Verification of Implementation

Shadow System Features Verified:

  • 16-Tap Poisson Disk PCF: Implemented in shader with comparison sampler (hardware PCF)
  • Normal Offset Bias: shadow.vert:13-14 scales bias by texel size for stability
  • Texel Snapping: csm.zig:79-80 properly floors light-space coordinates
  • Reverse-Z Alignment: shadow_system.zig:105 clears to 0.0, uses GREATER_OR_EQUAL compare
  • Persistent Memory Mapping: Verified in descriptor_manager.zig mapped buffers
  • Slope-Adaptive Bias: shadow_system.zig:1114 uses negative bias for Reverse-Z

SOLID Principles Assessment

Principle Score Assessment
Single Responsibility 6/10 rhi_vulkan.zig (~5000 lines) manages swapchain, rendering, UI, bloom, FXAA, clouds, shadows, G-buffer. ShadowSystem, DescriptorManager, CSM follow SRP well.
Open/Closed 5/10 rhi_vulkan.zig requires modification to add new pipeline types. DescriptorManager extensible via binding arrays.
Liskov Substitution 3/10 No explicit interfaces for ShadowSystem or DescriptorManager. Concrete types used directly throughout codebase.
Interface Segregation 4/10 VulkanContext has 50+ mixed fields (UI state, shadow state, bloom, etc.). DescriptorManager has unused bindings for some passes.
Dependency Inversion 4/10 ShadowSystem depends on concrete rhi types. Dependencies should flow through abstractions.

Critical Issues Requiring Attention

1. Silent Failure in Uniform Updates (descriptor_manager.zig:259-272)

pub fn updateGlobalUniforms(self: *DescriptorManager, frame_index: usize, data: *const anyopaque) void {
    const dest = self.global_ubos_mapped[frame_index] orelse {
        std.log.err("Failed to update global uniforms: memory not mapped", .{});
        return;  // Silent void return - caller doesn't know update failed
    };
    // ...memcpy...
}

Impact: Render frame uses stale uniform data without caller awareness
Fix: Change return type to !void and propagate error, or use a validity flag to skip rendering

2. Hardcoded SPIR-V Paths (rhi_vulkan.zig)
Issue: Shader paths not centralized in shader registry
Fix: Use shader_registry.getPath(.shadow_vert) instead of inline strings

Positive Findings

  • Deletion queues prevent use-after-free issues
  • Push constants for efficient per-draw data
  • Stable CSM algorithm with proper texel snapping
  • Correct Reverse-Z implementation throughout pipeline
  • Hardware PCF via comparison sampler (superior to software PCF)
  • Proper error checking in ShadowSystem.beginPass with log messages
  • Frame-delayed resource cleanup via deletion queues

Recommendations

High Priority:

  1. Fix silent void returns in updateGlobalUniforms and updateShadowUniforms - change to !void return type
  2. Add validity flag or error propagation so callers can handle mapping failures gracefully

Medium Priority:

  1. Extract ShadowPipeline creation into standalone function/struct
  2. Consider extracting VulkanContext subsystems into separate managers for testability

Low Priority:

  1. Centralize shader paths in shader_registry.zig
  2. Add unit tests for CSM matrix calculations

Verdict

APPROVE WITH SUGGESTIONS. The shadow algorithm is professionally implemented and the memory optimization is correct. The silent uniform update failures (Issue #2) must be addressed before merge to prevent rendering with stale data. The architectural concerns (God Object pattern, lack of interfaces) are technical debt suitable for incremental refactoring in future PRs.

New%20session%20-%202026-01-27T01%3A13%3A07.164Z
opencode session  |  github run

* Fix keymap system bugs: G key hardcoding and F3 conflict

Fixes #235: Replaces hardcoded G key check in world.zig with input mapper action.

Fixes #236: Resolves F3 conflict by adding toggle_timing_overlay action (F4 default) and updating app.zig.

* Address code review: Remove redundant default key comments

* Implement human-readable JSON format for keybindings (V3 settings)

Fixes #237: Migrates from array-based to object-based JSON format with action names as keys.

* Address review: Add debounce logic and migration warnings

Adds explicit 200ms debounce to debug toggles in App and WorldScreen.

Adds warning log when legacy settings have more bindings than supported.

* Enable InputSettings unit tests

* Refactor Input system to use SOLID interfaces

Implements Phase 3 of Issue #234.

Introduces IRawInputProvider and IInputMapper interfaces.

Decouples game logic (Player, MapController) from concrete Input implementation.

* Fix settings persistence and add G-key logging

Saves settings automatically after V1/V2 to V3 migration.

Adds logging to track shadow debug visualization toggle.

* Fix settings persistence and add diagnostic logging

Force-saves settings after migration to update file to V3.

Adds console logs for migration status and G-key action triggering.

* Add healing logic for broken key mappings

Detects and fixes cases where toggle actions were mismapped to Escape during migration.

* Final SOLID cleanup: complete interface contract and fix abstraction leaks

Adds isMouseButtonReleased, getWindowWidth, getWindowHeight, and shouldQuit to IRawInputProvider.

Eliminates all direct field access to Input struct in GameSession and Screens.

Fixes unsafe pointer casting in MapController and WorldScreen.

* Complete SOLID refactor: inject interfaces into EngineContext and eliminate leaks

Finalizes Phase 3 of Issue #234.

Injects IRawInputProvider and IInputMapper into EngineContext.

Eliminates all direct field access in high-level logic.

Ensures type safety with @aligncast for window pointers.

* SOLID Input Refactor: Finalize interface contract and eliminate leaks

Adds missing isMouseButtonReleased, getWindowWidth/Height, shouldQuit to IRawInputProvider.

Eliminates all direct field access in high-level logic (GameSession, Player, MapController).

Ensures validated pointer casting for window pointers.
@opencode-agent

Copy link
Copy Markdown
Contributor

Code Review: Industry-Grade Shadow System & Vulkan Memory Optimization

Previous Review Feedback Status

Issue Previous Review Current Status Notes
Memory Management Bug (rhi_vulkan.zig) FIXED VERIFIED FIXED Pipeline created before destruction
Silent Null Returns (descriptor_manager.zig:259-272) PARTIAL STILL PRESENT Logs error but returns void
Matrix Inverse Performance (csm.zig) FIXED VERIFIED FIXED inv_cam_view computed once
Debug Artifact (csm.zig) FIXED VERIFIED FIXED radius now used

SOLID Principles Assessment

Principle Score Assessment
Single Responsibility 6/10 rhi_vulkan.zig (~5000 lines) manages multiple subsystems. Input system well-factored into input.zig and interfaces.zig.
Open/Closed 5/10 rhi_vulkan.zig requires modification for new pipeline types. DescriptorManager extensible via binding arrays.
Liskov Substitution 3/10 No interfaces for ShadowSystem/DescriptorManager. New input/interfaces.zig is a positive step.
Interface Segregation 4/10 VulkanContext has 50+ mixed fields. Input interfaces show good ISP patterns.
Dependency Inversion 4/10 ShadowSystem depends on concrete types. New input interfaces depend on abstractions.

Critical Issues Requiring Attention

1. Silent Failure in Uniform Updates (descriptor_manager.zig:259-272)

pub fn updateGlobalUniforms(self: *DescriptorManager, frame_index: usize, data: *const anyopaque) void {
    const dest = self.global_ubos_mapped[frame_index] orelse {
        std.log.err("Failed to update global uniforms: memory not mapped", .{});
        return;  // Silent void return - caller unaware of failure
    };
}

Impact: Render frame uses stale uniform data without caller awareness.
Fix: Change return type to !void or add validity flag.

2. New Input System Complexity (input.zig:1-117)
The new input system adds ~117 lines. Verify the InputAction and InputState patterns integrate correctly with existing input_mapper.zig.

New Files Assessment

src/engine/input/interfaces.zig (+96 lines):
Well-designed abstraction layer for input handling. Follows ISP with focused interfaces. Enables testability and loose coupling.

Positive Findings

  • ✓ 16-Tap Poisson Disk PCF with hardware comparison sampler
  • ✓ Reverse-Z correctly implemented (clear to 0.0, GREATER_OR_EQUAL compare)
  • ✓ Texel snapping for stable CSM
  • ✓ Normal offset bias in shadow.vert
  • ✓ Persistent memory mapping eliminates per-frame map/unmap
  • ✓ Deletion queues prevent use-after-free
  • ✓ Input system refactored with proper abstractions

Recommendations

High Priority:

  1. Fix silent void returns in updateGlobalUniforms/updateShadowUniforms - change to !void
  2. Add validity flag so callers can skip rendering when uniforms unavailable

Medium Priority:

  1. Apply input interface pattern to ShadowSystem, DescriptorManager
  2. Extract shadow pipeline creation into standalone ShadowPipeline struct

Low Priority:

  1. Centralize shader paths in shader_registry.zig
  2. Add unit tests for cascade matrix calculations

Verdict

APPROVE WITH SUGGESTIONS. The shadow algorithm is professionally implemented. The silent uniform update failures (Issue #1) must be addressed before merge. The new input interfaces demonstrate good architectural direction—apply this pattern to graphics subsystems incrementally.

New%20session%20-%202026-01-27T01%3A36%3A34.175Z
opencode session  |  github run

- Propagated uniform update errors to prevent silent rendering failures
- Centralized all SPIR-V shader paths in shader_registry.zig
- Fixed VulkanContext field access and Uniform matrix assignments
- Optimized camera view inverse performance and stabilized CSM logic
- Ensured full integration of PBR, fog, and cloud shadows in terrain shader
@opencode-agent

Copy link
Copy Markdown
Contributor

Code Review: Industry-Grade Shadow System & Vulkan Memory Optimization

Previous Review Feedback Status

Issue Previous Review Current Status Notes
Memory Management Bug FIXED VERIFIED FIXED Pipeline created before destruction at rhi_vulkan.zig:1106-1112
Silent Null Returns (descriptor_manager.zig) PARTIAL STILL PRESENT Logs error but returns void at lines 259-272
Matrix Inverse Performance (csm.zig) FIXED VERIFIED FIXED inv_cam_view computed once outside loop
Debug Artifact (csm.zig) FIXED VERIFIED FIXED radius used for texel snapping

SOLID Principles Assessment

Principle Score Assessment
Single Responsibility 6/10 rhi_vulkan.zig (~5000 lines) manages swapchain, rendering, UI, bloom, FXAA, clouds, shadows, G-buffer. ShadowSystem, DescriptorManager, CSM, and new input/interfaces.zig follow SRP well.
Open/Closed 5/10 rhi_vulkan.zig requires modification to add new pipeline types. DescriptorManager extensible via binding arrays. New input/interfaces.zig demonstrates good O/Closed patterns.
Liskov Substitution 3/10 No interfaces for ShadowSystem or DescriptorManager. New input/interfaces.zig shows positive direction—apply to graphics subsystems.
Interface Segregation 4/10 VulkanContext has 50+ mixed fields. New input interfaces demonstrate good ISP patterns. DescriptorManager may have unused bindings.
Dependency Inversion 4/10 ShadowSystem depends on concrete rhi types. New input interfaces depend on abstractions—extend this pattern.

Critical Issues Requiring Attention

1. Silent Failure in Uniform Updates (descriptor_manager.zig:259-272)

This issue persists across all reviews and MUST be addressed before merge:

pub fn updateGlobalUniforms(self: *DescriptorManager, frame_index: usize, data: *const anyopaque) void {
    const dest = self.global_ubos_mapped[frame_index] orelse {
        std.log.err("Failed to update global uniforms: memory not mapped", .{});
        return;  // Silent void return - caller unaware of failure
    };
    // ...memcpy without error indication
}

Impact: Render frame uses stale uniform data without caller awareness. This can cause visual artifacts or incorrect lighting.

Fix Required: Change return type to !void and propagate error:

pub fn updateGlobalUniforms(self: *DescriptorManager, frame_index: usize, data: *const anyopaque) !void {
    const dest = self.global_ubos_mapped[frame_index] orelse {
        std.log.err("Failed to update global uniforms: memory not mapped", .{});
        return error.UnmappedBuffer;
    };
    @memcpy(dest, data, self.global_ubo_size);
}

Update callers in rhi_vulkan.zig to handle the error:

try self.descriptor_manager.updateGlobalUniforms(frame_index, &global_uniforms);

Verification of Implementation

Shadow System Features:

  • 16-Tap Poisson Disk PCF: Implemented in assets/shaders/vulkan/shadow.vert with hardware comparison sampler
  • Normal Offset Bias: shadow.vert:13-14 scales bias by texel size for artifact elimination
  • Texel Snapping: csm.zig:79-80 properly floors light-space coordinates for stable shadows
  • Reverse-Z Alignment: shadow_system.zig:105 clears to 0.0, uses GREATER_OR_EQUAL compare
  • Persistent Memory Mapping: Verified in descriptor_manager.zig—eliminates per-frame map/unmap
  • Slope-Adaptive Bias: shadow_system.zig:1114 uses negative bias for Reverse-Z

Input System (New):

  • input/interfaces.zig (+96 lines): Well-designed abstraction layer following ISP
  • Integration: Updated all screens to use new input patterns in input_mapper.zig (+127/-44)

Positive Findings

  • Deletion queues prevent use-after-free issues
  • Push constants for efficient per-draw data
  • Stable CSM algorithm with proper texel snapping
  • Correct Reverse-Z implementation throughout pipeline
  • Hardware PCF via comparison sampler (superior to software PCF)
  • Proper error checking in ShadowSystem.beginPass with log messages
  • Frame-delayed resource cleanup via deletion queues
  • New shader_registry.zig centralizes shader paths (+23/-0)

Recommendations

High Priority (Required for Merge):

  1. Fix silent void returns in updateGlobalUniforms and updateShadowUniforms—change to !void return type
  2. Add error propagation to callers so they can handle mapping failures gracefully

Medium Priority (Future PRs):

  1. Extract ShadowPipeline creation into standalone ShadowPipeline struct
  2. Apply input/interfaces.zig pattern to ShadowSystem and DescriptorManager
  3. Consider splitting VulkanContext into focused subsystems for testability

Low Priority:

  1. Add unit tests for cascade matrix calculations in tests.zig
  2. Document PCF and bias parameters in shader comments

Verdict

APPROVE WITH REQUIREMENTS. The shadow algorithm is professionally implemented and the memory optimization is correct. The persistent silent uniform update failures MUST be addressed before merge—this is the only blocking issue.

The architectural concerns (God Object pattern, lack of interfaces) are technical debt suitable for incremental refactoring. The new input/interfaces.zig demonstrates the team is moving in the right direction—continue applying these patterns to graphics subsystems in future PRs.

New%20session%20-%202026-01-27T02%3A10%3A39.550Z
opencode session  |  github run

Guard SSAO/G-pass inputs against invalid data and add debug toggles to isolate render passes.
@github-actions github-actions Bot added the world label Jan 28, 2026
@opencode-agent

Copy link
Copy Markdown
Contributor

Code Review: Industry-Grade Shadow System & Vulkan Memory Optimization

Previous Review Feedback Verification

Issue Previous Status Current Status Evidence
Silent Null Returns (descriptor_manager.zig) PARTIAL FIXED updateGlobalUniforms now returns !void with error.UnmappedBuffer (line 259-263)
Memory Management Bug (rhi_vulkan.zig:1106-1112) FIXED VERIFIED New pipeline created before old one destroyed (correct pattern)
Matrix Inverse Performance (csm.zig:48) FIXED VERIFIED inv_cam_view computed once outside loop
Debug Artifact (csm.zig:65) FIXED VERIFIED radius used for texel snapping calculation

Implementation Quality Verification

Feature Status Location
16-Tap Poisson Disk PCF ✓ Verified Hardware comparison sampler in shadow shader
Normal Offset Bias ✓ Verified shadow.vert:13 - scales by texel size
Texel Snapping ✓ Verified csm.zig:85-89 - floor-based snapping
Reverse-Z Alignment ✓ Verified shadow_system.zig:105 - clears to 0.0
Persistent Memory Mapping ✓ Verified descriptor_manager.zig:74-84
Slope-Adaptive Bias ✓ Verified shadow_system.zig:1114 - negative bias

SOLID Principles Assessment

Principle Score Assessment
Single Responsibility 6/10 rhi_vulkan.zig (~5000 lines) manages swapchain, rendering, UI, bloom, FXAA, clouds, shadows, G-buffer. ShadowSystem, DescriptorManager, and CSM follow SRP well. New input/interfaces.zig demonstrates good separation.
Open/Closed 6/10 rhi_vulkan.zig requires modification for new pipeline types. descriptor_manager.zig extensible via binding arrays. Shader registry (shader_registry.zig:23) centralizes paths for easier extension.
Liskov Substitution 3/10 No explicit interfaces for ShadowSystem or DescriptorManager. rhi.zig provides RHI abstraction but concrete types used directly. New input/interfaces.zig shows positive direction.
Interface Segregation 4/10 VulkanContext has 50+ mixed fields (UI state, shadow state, bloom, etc.). DescriptorManager binding count fixed at 11 bindings regardless of pass requirements.
Dependency Inversion 5/10 ShadowSystem depends on concrete rhi types. New input interfaces demonstrate abstraction patterns. DescriptorManager takes concrete *ResourceManager.

Actionable Suggestions

High Priority (Optional - Technical Debt):

  1. Extract Shadow Pipeline - Create ShadowPipeline struct in shadow_system.zig:

    pub const ShadowPipeline = struct {
        fn create(device, renderPass, extent) !Pipeline
    };
  2. Add Shadow System Interface - Define abstraction for testability:

    pub const ShadowRenderer = interface {
        fn beginCascade(cmd, index, matrix) void;
        fn endCascade(cmd) void;
        fn getTexture() TextureHandle;
    };
  3. Dynamic Descriptor Layouts - Generate descriptor layouts based on pass requirements instead of fixed 11 bindings.

Medium Priority:

  1. Split VulkanContext into focused subsystems:

    VulkanContext →
      ├── RenderPassManager
      ├── PipelineManager
      ├── FrameManager
      └── SubsystemManager
    
  2. Apply input/interfaces.zig pattern to ShadowSystem and DescriptorManager.

Low Priority:

  1. Add unit tests for cascade matrix calculations in tests.zig.
  2. Document PCF and bias parameters in shader comments for maintainability.

Positive Findings

  • Deletion queues prevent use-after-free issues
  • Push constants for efficient per-draw data
  • Stable CSM algorithm with proper texel snapping
  • Correct Reverse-Z implementation throughout pipeline
  • Hardware PCF via comparison sampler (superior to software PCF)
  • Proper error checking in ShadowSystem.beginPass with log messages
  • Frame-delayed resource cleanup via deletion queues
  • New shader_registry.zig centralizes shader paths (+23/-0)
  • Input system refactored with proper abstractions (input/interfaces.zig)

Verdict

APPROVE. All critical issues from previous reviews have been addressed:

  • Silent void returns fixed (now returns !void with proper error propagation)
  • Memory management pattern is correct
  • Matrix inverse cached
  • Debug artifacts removed

The shadow algorithm is professionally implemented with industry-standard techniques (PCF, texel snapping, Reverse-Z, normal offset bias). The architectural concerns (God Object pattern, lack of interfaces) are technical debt suitable for incremental refactoring in future PRs. The new input/interfaces.zig demonstrates the team is moving in the right direction.

New%20session%20-%202026-01-28T00%3A14%3A56.062Z
opencode session  |  github run

@MichaelFisher1997
MichaelFisher1997 merged commit f69ff20 into main Jan 28, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation engine game shaders world

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant