- Engine.Physics (JoltPhysicsSharp) integrated into CortexEngine.App
- 4 cubes: DynamicBox, mass proportional to scale, fall from height 5-10
- 3 spheres: DynamicSphere, mass 1.5, fall from height 7-12
- Floor: StaticBox (20x0.5x20), friction 0.8
- Torus knot + grid: no physics (visual only)
- Physics bodies initialized lazily on first frame (IsInitialized flag)
- e.Set() moved outside world.Each to avoid mutation during iteration
- physicsWorld.Update + SyncTransforms every frame
- Removed manual rotation — physics handles movement now
- Camera at (0, 5, -15) for better view of falling objects
- 0 validation errors, ~1300 FPS
- 10 entities in ECS World with Transform + Mesh components
- Torus knot (center, scale 1.5) rotating around Y
- 4 cubes (left/right/front/back) rotating at different speeds
- 3 spheres (procedural, 32x16) rotating around X
- Floor (20x20 flat cube) + grid (ProceduralMesh.CreateGrid)
- Camera at (0, 3, -12) looking at (0, 0.5, 0)
- Mesh cache: buffers created lazily per entity ID on first frame
- Diffuse lighting on all objects
- 0 validation errors, ~2300 FPS
- Vertex shader: pass normal through mat3(model) to fragment
- Fragment shader: directional light (0.5, 0.8, 0.3) with 0.25 ambient
+ 0.75 diffuse = faces now have depth and 3D form
- Torus knot has smooth normals (vn in OBJ), cubes have face normals
- 0 validation errors, ~2300 FPS
- VulkanRenderer: mesh cache by entity ID, lazy buffer creation
- RenderWorld iterates entities with Transform+Mesh, draws each with
per-entity model matrix via push constants
- Program.cs: creates scene with 1 torus knot (center, scale 1.5) +
6 cubes at different positions, all rotating around Y at different speeds
- Camera at (0, 2, -8) looking at origin, FreeFly controls
- 0 validation errors, ~2100 FPS with 7 objects
Root cause: System.Numerics.Matrix4x4 uses row-major storage with row-vector
convention (v * M). GLSL with layout(row_major) does column-vector (M * v),
which effectively transposes the matrix, breaking the projection.
Fix: remove row_major from layout qualifiers. GLSL then interprets the
row-major data as column-major (effectively transposing it), so M * v in
GLSL equals v * M in System.Numerics — correct result.
The Y-flip (M22 *= -1) is still needed for Vulkan's Y-down clip space.
System.Numerics.CreatePerspectiveFieldOfView uses DirectX convention (Y up).
Vulkan clip space has Y down. Negating M22 corrects the projection.
Depth is already [0,1] in System.Numerics (DirectX-style), no remap needed.
- Program.cs: create Camera entity in ECS World, FreeFlyCameraController
with WASD movement, Q/E up/down, Shift boost, right-click mouse look
- VulkanRenderer.RenderWorld: queries Camera from World, computes VP
from Camera.GetViewMatrix() * GetProjectionMatrix()
- Camera aspect ratio auto-updated on window resize
- Fallback to hardcoded VP if no Camera entity found
- Torus knot still rotating via push constant model matrix
- 0 validation errors
- Push constant changed from float angle (4B) to mat4 model (64B)
- Two draw calls with different model matrices (x=-1.5 and x=+1.5)
- Both cubes rotate around Y axis at 0.2 rad/s
- 0 validation errors
- Load Content/cube.obj via ObjLoader (36 vertices, 36 indices)
- Depth buffer re-enabled: depth test + write, CompareOp=Less
- Depth image transition + depth attachment in rendering info
- Push constant angle=0 (no rotation)
- Camera at z=-2, perspective FOV 45°
- UINT32 index type (fixed in previous commit)
- Face normals computed by ObjLoader (no vn in cube.obj)
- 0 validation errors
New test files:
- VulkanStructSizeTests.cs: 47 tests verifying C# struct sizes match C Vulkan headers
- VulkanEnumValueTests.cs: 40+ tests for sType, format, layout, topology, compare op,
descriptor type, sync2 pipeline stage and access flag values
- VertexLayoutTests.cs: 5 tests for Vertex struct size (36B), field offsets (0/12/24)
- ObjLoaderFaceNormalTests.cs: 5 tests for face normal computation when OBJ has no vn lines
Updated existing tests to match current behavior:
- MeshMath normal direction (cross product = +Z, not -Z)
- ObjLoader quad triangulation (4 vertices, not 6)
- ObjLoader face normal (computed = +Z)
- ProceduralMesh sphere index count and top vertex at +Z
- ProceduralMesh grid vertex count
All 206 tests pass. Tests would have caught:
- VkPhysicalDeviceMemoryProperties size (was 264, should be 520)
- VkPhysicalDeviceLimits fields (size_t fields were uint, not ulong)
- Synchronization2Features sType (was 1000257000, should be 1000314007)
- Access flag values (COLOR_ATTACHMENT_WRITE was 0x800, should be 0x100)
- Index type mismatch (UINT16 vs UINT32)
- vkCmdBindIndexBuffer indexType changed from 0 (UINT16) to 1 (UINT32)
to match uint[] index data — this was causing black screen
- Removed depth stencil state, depth attachment format, and depth
rendering attachment from pipeline/renderer (depth buffer still
created in swapchain but unused)
- ObjLoader.ParseFace: if no normals in OBJ, compute face normal from
first triangle using MeshMath.ComputeFaceNormal and apply to all face vertices
- cube.obj has no vn lines, so all faces previously had normal (0,1,0)
- Now each face has correct outward-facing normal for proper lighting
- Fragment shader: directional light dot(normal, lightDir) with 0.2 ambient
- Vertex shader: pass normal through mat3(model) to fragment
- Same-color faces now have different shading based on orientation
- Remove ALWAYS_ON_TOP and debug print hacks.
- Pump SDL events after SDL_ShowWindow so the Wayland compositor maps the window.
- Clean run.sh: do not force DISPLAY or SDL_VIDEODRIVER, let SDL3 auto-detect.
1. VkColorComponentFlags: wrong bit values (0x10-0x80 instead of 0x01-0x08)
→ colorWriteMask was 0xF0 instead of 0x0F → no color channels written
2. VkClearValue: was Sequential layout instead of Explicit (union)
→ depthStencil clear value written at wrong offset → depth buffer
cleared to 0.0 instead of 1.0 → all fragments failed depth test
3. Vulkan clip space correction in vertex shader:
gl_Position.y = -gl_Position.y (Vulkan Y-down vs OpenGL Y-up)
gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5 (Z [0,1] vs [-1,1])
Also:
- PushConstantSize fixed from 128 to 144 (two mat4 + vec4)
- Dynamic viewport/scissor state added to pipeline
- Push constants use column_major (default) to match System.Numerics row-major layout
- Camera.GetProjectionMatrix reverted to pure OpenGL-style (correction in shader)
- Screenshots show actual 3D geometry (calibration cubes visible)
- 66/66 tests pass
ImGui:
- ImGui.NET 1.91.6.1 NuGet package
- VulkanImGui: font texture upload, ImGui pipeline (blending, no depth), vertex/index buffer streaming
- GLSL 450 ImGui shaders (vert: scale/translate push constants, frag: font texture sampler)
- Compiled to SPIR-V via @webgpu/glslang
- Debug overlay: FPS, camera position, entity count, light list
- 1600+ FPS with ImGui active
Screenshot:
- vkCmdCopyImageToBuffer embedded in main command buffer
- Layout transition PresentSrcKHR→TransferSrcOptimal→PresentSrcKHR
- Deferred read: staging buffer read on next frame after fence
- BMP format written directly to FileStream (row by row)
- All 16 camera tour screenshots captured (1280x720x32bpp)
All 66 tests pass.
- Added vkCmdCopyImageToBuffer to P/Invoke layer
- Screenshot embedded in main command buffer (barrier PresentSrcKHR→TransferSrc, copy, barrier back)
- Deferred read: staging buffer read on next frame after fence signaled
- BMP format written directly to FileStream (row by row, avoids large heap allocations)
- BGRA→BGRA direct copy for B8G8R8A8 swapchain format
- All 16 camera tour screenshots captured successfully (1280x720x32bpp)
- 66/66 tests pass
OpenTK renderer has matrix/swap issues causing garbled output.
Raylib restored as default. OpenTK project preserved for debugging.
Normal PBR shader restored in OpenTKRenderer.
- GCHandle.Alloc(Pinned) for BufferData to prevent GC relocation
- void* null for VertexAttribPointer offsets
- ShouldSwapAutomatically=false, manual SwapBuffers
- Solid color debug shader active
- Still has rendering issues — needs visual debugging
System.Numerics.Matrix4x4 is row-major, OpenGL UniformMatrix4 with
transpose=false expects column-major. Changed all UniformMatrix4 calls
to transpose=true. Also explicitly enable depth test + disable cull face
for main pass. Fixes strobe/flickering issue.