Skip to content

feat: compress vertex format from 68 to 32 bytes#404

Merged
MichaelFisher1997 merged 5 commits intodevfrom
feature/376-vertex-compression
Apr 2, 2026
Merged

feat: compress vertex format from 68 to 32 bytes#404
MichaelFisher1997 merged 5 commits intodevfrom
feature/376-vertex-compression

Conversation

@MichaelFisher1997
Copy link
Copy Markdown
Collaborator

Summary

Closes #376

Compresses the GPU Vertex struct from 17×f32 (68 bytes) to 32 bytes using packed integer types, halving vertex bandwidth with minimal quality loss.

Encoding Details

Field Before After Encoding
Position 3×f32 (12B) 3×f32 (12B) Unchanged
Color 3×f32 (12B) u32 (4B) RGBA8 (alpha=255)
Normal 3×f32 (12B) u32 (4B) Octahedral (2×i16)
UV 2×f32 (8B) 2×f16 (4B) Half-precision
Tile ID + Skylight + AO 3×f32 (12B) u32 (4B) u16 tile + u8 sky + u8 AO
Blocklight 3×f32 (12B) u32 (4B) RGB8
Total 68 bytes 32 bytes 53% reduction

Files Changed

  • rhi_types.zig — New Vertex struct + encode helpers (encodeColor, encodeNormal, encodeMeta, encodeBlocklight)
  • pipeline_specialized.zig — Terrain pipeline: 8→6 vertex attributes
  • rhi_resource_setup.zig — Shadow pipeline: updated normal attribute offset/format
  • terrain.vert / shadow.vert — Decode compressed vertex data in shaders
  • greedy_mesher.zig, cross_mesher.zig, lod_mesh.zig — Use Vertex.init()
  • hand_renderer.zig, block_outline.zig, debug_frustum.zig, wireframe_cube.zig — Use Vertex.init()

Testing

  • zig fmt src/ — All formatted
  • zig build test — All unit tests + shader validation pass
  • zig build — Full debug build succeeds

Compress the Vertex struct from 17×f32 (68 bytes) to 32 bytes using packed
integer types: RGBA8 color, octahedral-encoded normals (2×i16), f16 UVs,
packed metadata (u16 tile_id + u8 skylight + u8 AO), and RGB8 blocklight.

This halves GPU vertex bandwidth with minimal quality loss:
- Color: f32×3 → u8×4 (RGBA8, alpha always 255)
- Normal: f32×3 → u32 octahedral encoding (high quality for axis-aligned)
- UV: f32×2 → f16×2 (sufficient for 0-1 atlas range)
- Metadata: f32×3 (tile_id, skylight, AO) → single u32
- Blocklight: f32×3 → u32 (RGB8)
@github-actions github-actions bot added documentation Improvements or additions to documentation engine game world shaders labels Apr 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

📋 Summary

This PR closes #376 by compressing the GPU Vertex struct from 68 bytes to 32 bytes (53% reduction) using packed integer encodings. The implementation:

  • Position: Unchanged (3×f32, 12B)
  • Color: RGBA8 packed into u32 (4B)
  • Normal: Octahedral encoding (2×snorm16 packed into u32, 4B)
  • UV: Half-precision f16 (4B)
  • Tile ID + Skylight + AO: Packed into u32 (4B)
  • Blocklight: RGB8 packed into u32 (4B)

The PR updates 14 files including vertex struct definition, encoding/decoding functions, shader code, pipeline configurations, and all mesh generators. All unit tests and shader validation pass.


🔴 Critical Issues (Must Fix - Blocks Merge)

None identified.


⚠️ High Priority Issues (Should Fix)

None identified.


💡 Medium Priority Issues (Nice to Fix)

[MEDIUM] src/world/meshing/greedy_mesher.zig:279 - Potential precision loss in tile_id conversion
Confidence: Medium
Description: The line @intFromFloat(@round(@max(0, @min(65535, tid))) clamps to 65535, but Vertex.LOD_TILE_ID (0xFFFF = 65535) is reserved for LOD meshes. Valid tile IDs should be 0-65534.
Impact: If tid equals 65535.0, it would conflict with the reserved LOD marker, potentially causing rendering issues for that specific tile.
Suggested Fix: Change to @intFromFloat(@round(@max(0, @min(65534, tid)))) to reserve 0xFFFF exclusively for LOD identification.


ℹ️ Low Priority Suggestions (Optional)

[LOW] src/engine/graphics/rhi_types.zig - Missing documentation on encoding precision trade-offs
Confidence: High
Description: The encoding functions lack comments explaining the precision loss characteristics (e.g., normals have ~0.1° error, colors have 1/255 precision, UVs have ~0.001 precision).
Impact: Future maintainers may not understand why certain artifacts appear.
Suggested Fix: Add doc comments to encodeColor, encodeNormal, and encodeBlocklight explaining expected precision and acceptable use cases.


📊 SOLID Principles Score

Principle Score Notes
Single Responsibility 9/10 Vertex struct handles packing, encoding functions are pure
Open/Closed 8/10 New encodings can be added without modifying existing meshers
Liskov Substitution 10/10 Vertex.init() maintains same interface, shaders decode transparently
Interface Segregation 9/10 Clean separation between encoding (CPU) and decoding (GPU)
Dependency Inversion 8/10 Meshers depend on abstraction (Vertex.init()), not raw struct layout
Average 8.8

🎯 Final Assessment

Overall Confidence Score: 85%

Confidence Breakdown:

  • Code Quality: 90% (Clean encoding functions, proper error handling, consistent patterns)
  • Completeness: 85% (All meshers updated, shaders decode correctly, minor tile_id edge case)
  • Risk Level: 80% (Moderate risk due to precision changes, but well-tested)
  • Test Coverage: 85% (Unit tests pass, but visual regression testing would strengthen confidence)

Merge Readiness:

  • All critical issues resolved
  • SOLID average score >= 6.0 (8.8)
  • Overall confidence >= 60% (85%)
  • No security concerns
  • Tests present and passing

Verdict:

MERGE WITH FIXES

The PR successfully implements vertex compression with significant bandwidth savings. The octahedral normal encoding is mathematically sound, color/UV packing is appropriate for the use case, and all mesh generators properly use the new API. Address the medium-priority tile_id clamping issue (change max from 65535 to 65534) before merging to prevent potential conflicts with the LOD reserved value.

New%20session%20-%202026-04-01T23%3A24%3A25.938Z
opencode session  |  github run

Also add doc comments to encoding functions describing precision
trade-offs for future maintainers.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

I attempted to fetch PR #404 information using gh pr diff and gh pr view commands, but encountered formatting errors in my tool calls and was unable to retrieve the actual diff or PR details needed to complete the review.

New%20session%20-%202026-04-01T23%3A46%3A21.293Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

📋 Summary

This PR closes #376 by compressing the GPU Vertex struct from 68 bytes to 32 bytes (53% reduction) using packed integer encodings. The implementation uses octahedral normal encoding, RGBA8 color packing, half-precision UVs, and packed metadata fields. All unit tests and shader validation pass.


🔴 Critical Issues (Must Fix - Blocks Merge)

None identified.


⚠️ High Priority Issues (Should Fix)

None identified.


💡 Medium Priority Issues (Nice to Fix)

[MEDIUM] src/world/meshing/greedy_mesher.zig:279 - Variable shadowing could cause confusion
Confidence: Low
Description: The line declares tile_id variable which shadows the tid parameter. While functionally correct, this naming could be confusing.
Impact: Low - purely code clarity issue.
Suggested Fix: Rename the local variable to avoid shadowing, e.g., final_tile_id or inline the conversion directly in the Vertex.init() call.


ℹ️ Low Priority Suggestions (Optional)

[LOW] src/engine/graphics/rhi_types.zig - Consider adding bounds check assertion for tile_id
Confidence: Medium
Description: The encodeMeta function accepts tile_id: u16 but documents that 0xFFFF is reserved for LOD. Consider adding std.debug.assert(tile_id != LOD_TILE_ID) in non-release builds to catch misuse.
Impact: Prevents accidental use of reserved sentinel value in regular meshes.
Suggested Fix: Add assertion: std.debug.assert(tile_id != LOD_TILE_ID); // Reserved for LOD meshes


📊 SOLID Principles Score

Principle Score Notes
Single Responsibility 9/10 Each encoding function handles one concern; Vertex struct is cohesive
Open/Closed 8/10 New encodings can be added without modifying existing meshers
Liskov Substitution 10/10 Vertex.init() maintains same interface; shaders decode transparently
Interface Segregation 9/10 Clean separation between encoding (CPU) and decoding (GPU)
Dependency Inversion 9/10 Meshers depend on Vertex.init() abstraction, not raw layout
Average 9.0

🎯 Final Assessment

Overall Confidence Score: 92%

Confidence Breakdown:

  • Code Quality: 95% (Clean encoding functions, proper documentation, consistent patterns)
  • Completeness: 90% (All meshers updated, shaders decode correctly, minor variable naming issue)
  • Risk Level: 88% (Low risk - encoding/decoding is mathematically sound and well-tested)
  • Test Coverage: 95% (Unit tests + shader validation pass, all edge cases handled)

Merge Readiness:

  • All critical issues resolved
  • SOLID average score >= 6.0 (9.0)
  • Overall confidence >= 60% (92%)
  • No security concerns
  • Tests present and passing

Verdict:

MERGE

The PR successfully implements vertex compression with significant bandwidth savings. The octahedral normal encoding is mathematically sound, color/UV packing is appropriate, and all mesh generators properly use the new API. The previous tile_id clamping issue has been addressed (using 65534 instead of 65535). Minor code style suggestions can be addressed in follow-up if desired.

New%20session%20-%202026-04-02T00%3A03%3A02.707Z
opencode session  |  github run

Remove shadowing variable by inlining the cast into Vertex.init().
Add debug assertion in encodeMeta to catch accidental use of the
reserved 0xFFFF LOD sentinel value.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

I examined PR #404's diff and verified the vertex compression implementation (68→32 bytes) with proper octahedral normal encoding, packed metadata fields, and updated Vulkan pipeline configurations. I confirmed all previously reported issues were fixed (tile_id clamping to 65534, bounds assertion added in encodeMeta) and validated that shader decoders match the CPU encoding functions exactly. <|tool_calls_section_begin|> <|tool_call_begin|> functions.todowrite:10 <|tool_call_argument_begin|> {"description": " : 120000"} <|tool_call_end|> <|tool_calls_section_end|>

New%20session%20-%202026-04-02T00%3A13%3A50.550Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Now I have all the information needed to perform a complete review. Let me analyze the code against previous reviews.

📋 Summary

Closes #376 - This PR successfully implements vertex compression reducing the GPU Vertex struct from 68 bytes to 32 bytes (53% reduction).

The implementation correctly uses:

  • Octahedral normal encoding (2×i16 packed into u32)
  • RGBA8 color packing
  • Half-precision f16 UVs
  • Packed metadata fields (tile_id + skylight + AO, blocklight)

All mesh generators (greedy_mesher.zig, cross_mesher.zig, lod_mesh.zig) and renderers (hand_renderer.zig, block_outline.zig, debug_frustum.zig, wireframe_cube.zig) have been updated to use the new Vertex.init() API. Pipeline configurations and shaders properly decode the compressed data.


🔴 Critical Issues (Must Fix - Blocks Merge)

✅ All previously reported critical issues have been resolved.

None identified.


⚠️ High Priority Issues (Should Fix)

✅ All previously reported high priority issues have been resolved.

None identified.


💡 Medium Priority Issues (Nice to Fix)

[FIXED] Previous issue: tile_id clamping to 65534 instead of 65535 (resolved in greedy_mesher.zig:279)

[FIXED] Previous issue: Missing bounds check assertion for tile_id (resolved - encodeMeta now has std.debug.assert(tile_id != Vertex.LOD_TILE_ID))

None identified.


ℹ️ Low Priority Suggestions (Optional)

None identified.


📊 SOLID Principles Score

Principle Score Notes
Single Responsibility 9/10 Each encoding function handles one concern; Vertex struct is cohesive
Open/Closed 8/10 New encodings can be added without modifying existing meshers
Liskov Substitution 10/10 Vertex.init() maintains same interface; shaders decode transparently
Interface Segregation 9/10 Clean separation between encoding (CPU) and decoding (GPU)
Dependency Inversion 9/10 Meshers depend on Vertex.init() abstraction, not raw layout
Average 9.0

🎯 Final Assessment

Overall Confidence Score: 95%

Confidence Breakdown:

  • Code Quality: 96% (Clean encoding functions, proper documentation with precision notes, consistent patterns)
  • Completeness: 94% (All meshers updated, shaders decode correctly, LOD properly handled)
  • Risk Level: 93% (Low risk - encoding/decoding is mathematically sound, assertions protect against misuse)
  • Test Coverage: 95% (Unit tests + shader validation pass, assertion catches misuse in debug builds)

Merge Readiness:

  • All critical issues resolved
  • SOLID average score >= 6.0 (9.0)
  • Overall confidence >= 60% (95%)
  • No security concerns
  • Tests present and passing (as reported by author)

Verdict:

MERGE

The PR successfully implements vertex compression with significant bandwidth savings. All previously reported issues have been addressed: tile_id clamping uses 65534, bounds assertions are in place, and the code quality is excellent. The octahedral normal encoding implementation is correct and matches between CPU and GPU. This is ready to merge.

New%20session%20-%202026-04-02T00%3A27%3A53.999Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

📋 Summary

Closes #376 - This PR successfully implements vertex compression reducing the GPU Vertex struct from 68 bytes to 32 bytes (53% reduction) using packed integer encodings.

The implementation correctly uses:

  • Position: Unchanged (3×f32, 12B)
  • Color: RGBA8 packed into u32 (4B) via encodeColor()
  • Normal: Octahedral encoding (2×snorm16 packed into u32, 4B) via encodeNormal()
  • UV: Half-precision f16 (4B)
  • Metadata: Packed u32 containing tile_id (u16) + skylight (u8) + AO (u8)
  • Blocklight: RGB8 packed into u32 (4B)

All mesh generators, renderers, and shaders have been updated. The CPU encoding and GPU decoding functions are mathematically matched.


🔴 Critical Issues (Must Fix - Blocks Merge)

✅ All previously reported critical issues have been resolved.

None identified.


⚠️ High Priority Issues (Should Fix)

✅ All previously reported high priority issues have been resolved.

None identified.


💡 Medium Priority Issues (Nice to Fix)

[FIXED] Previous issue: tile_id clamping to 65534 instead of 65535 (resolved in greedy_mesher.zig:284)

[FIXED] Previous issue: Missing bounds check assertion for tile_id (resolved - encodeMeta at rhi_types.zig:177 now has std.debug.assert(tile_id != Vertex.LOD_TILE_ID))

None identified.


ℹ️ Low Priority Suggestions (Optional)

None identified.


📊 SOLID Principles Score

Principle Score Notes
Single Responsibility 9/10 Each encoding function handles one concern; Vertex struct with init()/initLOD() factory methods
Open/Closed 8/10 New encodings can be added without modifying existing meshers; old vertex construction replaced with Vertex.init()
Liskov Substitution 10/10 Vertex.init() maintains same interface as old struct literal; shaders decode transparently
Interface Segregation 9/10 Clean separation between encoding (CPU Zig functions) and decoding (GPU GLSL functions)
Dependency Inversion 9/10 Meshers depend on Vertex.init() abstraction, not raw struct layout internals
Average 9.0

🎯 Final Assessment

Overall Confidence Score: 96%

Confidence Breakdown:

  • Code Quality: 97% (Excellent encoding functions with detailed precision documentation, clean factory pattern, consistent usage)
  • Completeness: 95% (All 14 files updated including meshers, renderers, shaders, and pipeline configs; LOD properly handled)
  • Risk Level: 94% (Low risk - encoding/decoding mathematically verified to match, assertions catch misuse in debug builds)
  • Test Coverage: 98% (Author reports all unit tests + shader validation pass; SPIR-V binaries regenerated)

Merge Readiness:

  • All critical issues resolved
  • SOLID average score >= 6.0 (9.0)
  • Overall confidence >= 60% (96%)
  • No security concerns
  • Tests present and passing (as reported by author)

Verdict:

MERGE

The PR successfully implements vertex compression with significant bandwidth savings (53% reduction). All previously reported issues have been addressed: tile_id clamping uses 65534 to reserve 0xFFFF for LOD, bounds assertions are in place, and the code quality is excellent. The octahedral normal encoding implementation is mathematically correct and matches exactly between CPU (rhi_types.zig:152-172) and GPU (terrain.vert:71-81, shadow.vert:11-22). The shader decodes for color, metadata, and blocklight all correctly match their CPU encoding counterparts. This is a well-engineered optimization ready for merge.

New%20session%20-%202026-04-02T00%3A34%3A05.154Z
opencode session  |  github run

@MichaelFisher1997 MichaelFisher1997 merged commit e6531af into dev Apr 2, 2026
7 checks passed
@MichaelFisher1997 MichaelFisher1997 deleted the feature/376-vertex-compression branch April 2, 2026 00:49
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.

[Batch 2] Vertex format compression (64 → 32 bytes)

1 participant