- README.md: new — features, quick start, controls, MCP tools, requirements - AGENTS.md: rewritten for pure P/Invoke Vulkan 1.3 (was Raylib/OpenGL) - CORTEX_ENGINE_ARCHITECTURE.md: complete rewrite — current architecture, frame loop, UBO layout, push constants, shadow mapping, PBR, AI/MCP, physics, ImGui, video recording, content, testing - VULKAN_IMPLEMENTATION_PLAN.md: marked as COMPLETE with all 21 phases - scripts/run.sh: updated examples - .gitignore: added Videos/, imgui.ini, cortex.mp4 - Removed tracked imgui.ini and video files
11 KiB
11 KiB
CORTEX ENGINE — Technical Architecture
Project: AI-Native 3D Game Engine Language: C# (.NET 9) Render Backend: Pure P/Invoke Vulkan 1.3 Last Updated: June 2026
1. Overview
Cortex Engine is a 3D game engine built from scratch with a pure P/Invoke Vulkan 1.3 render backend. No wrapper libraries — direct Vulkan API calls via vkGetInstanceProcAddr/vkGetDeviceProcAddr.
The engine allows an AI to:
- See the engine state via rendered-frame screenshots and video recording
- Read the complete ECS world state through native JSON serialization
- Modify the running engine via declarative JSON commands through MCP (Model Context Protocol)
Key Technologies
- Graphics: Vulkan 1.3 (dynamic rendering, synchronization2, imageCubeArray)
- ECS: Flecs.NET 4.0.4
- Windowing: SDL3 (ppy.SDL3-CS 2026.520.0)
- Physics: JoltPhysicsSharp 2.21.0
- UI: ImGui.NET 1.91.6.1
- AI: MCP HTTP server (Kestrel + SSE), 7 tools
2. Project Structure
src/
├── Engine.Core/ # Core abstractions, windowing, ECS components
│ ├── IWindow.cs # Backend-agnostic window interface
│ ├── IInputState.cs # Input abstraction
│ ├── Sdl3Window.cs # SDL3 window with Vulkan surface
│ ├── Key.cs # Key enum
│ ├── InputMapping.cs # Input state implementation
│ ├── Timing.cs # Frame timing
│ ├── Vertex.cs # Vertex struct (Position, Color, Normal)
│ ├── FreeFlyCameraController.cs
│ ├── OrbitCameraController.cs
│ └── Components/
│ ├── Transform.cs # Position, Rotation, Scale
│ ├── Mesh.cs # Vertex[], uint[] indices
│ ├── Material.cs # Albedo, Roughness, Metallic, TexturePath
│ ├── Light.cs # Point/Directional light
│ ├── Camera.cs # Perspective camera
│ └── RigidBody.cs # Physics body (box/sphere)
│
├── Engine.Graphics/ # Graphics HAL + utilities
│ ├── IRenderContext.cs # Window, CreateRenderer, Resize
│ ├── IRenderer.cs # RenderWorld, Screenshot, ImGui hooks
│ ├── IScreenshotProvider.cs
│ ├── RenderBackendFactory.cs # Register/Create by name
│ ├── ObjLoader.cs # OBJ file parser
│ ├── MeshMath.cs # Face normal computation
│ ├── ProceduralMesh.cs # Sphere, Grid generators
│ └── SceneSerializer.cs # JSON scene save/load
│
├── Engine.Graphics.Vulkan/ # Pure P/Invoke Vulkan 1.3
│ ├── VulkanNative.cs # Library loading, vkGetInstanceProcAddr
│ ├── VulkanHandles.cs # Opaque pointer types
│ ├── VulkanEnums.cs # All Vulkan enums/flags
│ ├── VulkanStructs.cs # All Vulkan structs (verified sizes)
│ ├── Vk.cs # Function delegates + loaded pointers
│ ├── VulkanContext.cs # Instance, device, queue, surface, debug
│ ├── VulkanSwapchain.cs # Swapchain, image views, depth buffer
│ ├── VulkanPipeline.cs # Graphics pipeline (PBR + dynamic rendering)
│ ├── VulkanShadowMap.cs # Cubemap array shadows (4 lights × 6 faces)
│ ├── VulkanFrameResources.cs # Command buffers, fences, semaphores, UBO
│ ├── VulkanVertexBuffer.cs # Staging → device-local vertex buffer
│ ├── VulkanIndexBuffer.cs # Staging → device-local index buffer
│ ├── VulkanImGui.cs # ImGui font atlas + pipeline + render
│ ├── VulkanRenderer.cs # Main renderer: shadow passes + main pass
│ ├── VulkanRenderContext.cs
│ ├── VulkanBackendRegistrar.cs
│ └── Shaders/
│ ├── triangle.vert/frag # PBR + multi-light + shadow sampling
│ ├── shadow.vert/frag # Depth-only shadow pass
│ └── imgui.vert/frag # ImGui rendering
│
├── Engine.Physics/ # Jolt physics wrapper
│ └── PhysicsWorld.cs # Body creation, update, sync transforms
│
├── Engine.AI/ # AI command system + MCP server
│ ├── AiCommandProcessor.cs # 7 commands (spawn, transform, material, etc.)
│ ├── AiCommandQueue.cs # Thread-safe queue (MCP → main thread)
│ ├── Commands/ # Command DTOs
│ ├── Mcp/ # HTTP MCP server (Kestrel + SSE)
│ └── Stdio/ # Stdio MCP server
│
└── CortexEngine.App/ # Entry point
└── Program.cs # Main loop, scene, ImGui panels, video recording
3. Vulkan Backend
3.1 Initialization
- Load
libvulkan.so.1viaNativeLibrary.Load() vkGetInstanceProcAddr— only directly-loaded function- Create instance with SDL3 extensions +
VK_EXT_debug_utils(if validation available) - Pick physical device — prefer
DiscreteGpu - Create logical device with features:
VK_KHR_swapchainextensionVkPhysicalDeviceDynamicRenderingFeatures(dynamic rendering)VkPhysicalDeviceSynchronization2Features(sync2)VkPhysicalDeviceFeatures.imageCubeArray = VK_TRUE(cubemap array shadows)
- Create surface via
SDL_Vulkan_CreateSurface()
3.2 Frame Loop
Each frame:
1. WaitFrame (fence)
2. Read captured frame buffer (if recording video)
3. AcquireNextImageKHR
4. Begin command buffer
5. Shadow passes (numShadowLights × 6 faces):
- Transition shadow cubemap array → ColorAttachment + DepthAttachment
- For each shadow light, for each face:
- Begin rendering (color R32_SFLOAT + depth D32_SFLOAT)
- Bind shadow pipeline (depth-only, CULL_NONE, depth bias)
- Push constants: model + lightViewProj + lightPos + shadowParams (160B)
- Draw all shadow-casting objects
- End rendering
- Transition shadow cubemap array → ShaderReadOnlyOptimal
6. Main pass:
- Transition swapchain image → ColorAttachmentOptimal
- Transition depth image → DepthStencilAttachmentOptimal
- Begin rendering (color B8G8R8A8_UNORM + depth D32_SFLOAT)
- Bind pipeline, descriptor sets (SceneUBO + shadowCubeArray)
- For each object: push constants (model 64B), draw indexed
- [If recording] End rendering, capture frame (vkCmdCopyImageToBuffer)
- [If recording] Begin new rendering with loadOp=LOAD for ImGui
- ImGui render
- End rendering
- Transition swapchain → PresentSrcKHR
7. End command buffer
8. QueueSubmit2 (sync2)
9. QueuePresentKHR
3.3 SceneUBO Layout (464B)
Offset 0: mat4 vp (64B)
Offset 64: int numLights (4B)
Offset 68: int numShadowLights (4B)
Offset 72: vec2 padding (8B)
Offset 80: LightData lights[8] (256B) — 2 × vec4 per light
Offset 336: vec4 shadowParams[4] (64B) — bias, sampleRadius, farPlane
Offset 400: vec4 ambientColor (16B)
Total: 416B → padded to 464B (align 64)
3.4 Push Constants
Main pipeline: mat4 model (64B), Vertex|Fragment
Shadow pipeline: mat4 model + mat4 lightViewProj + vec4 lightPos + vec4 shadowParams (160B), Vertex|Fragment
3.5 Shadow Mapping
- Cubemap array: 1 image, 24 layers (4 lights × 6 faces),
CubeCompatibleflag - Color attachment: R32_SFLOAT (stores linear distance / farPlane)
- Depth attachment: D32_SFLOAT (depth test only)
- Sampling:
samplerCubeArray,texture(shadowArray, vec4(dir, lightIndex)) - Filtering: 16-tap Poisson disk PCF, slope-dependent bias
- Vulkan cubemap conventions: up=(0,-1,0) for X/Z faces, up=(0,0,1) for +Y, up=(0,0,-1) for -Y
3.6 PBR Shading
Cook-Torrance BRDF:
- D: Trowbridge-Reitz GGX distribution
- G: Smith geometry with Schlick-GGX
- F: Schlick Fresnel approximation
- Tonemapping: ACES filmic
- Gamma: 2.2 correction
- Multi-light loop in fragment shader
4. AI/MCP Integration
7 MCP Tools
| Tool | Description |
|---|---|
spawn_model |
Create entity with mesh + optional physics + shape (cube/sphere) |
set_transform |
Update position/rotation/scale by name |
set_material |
Update albedo/roughness/metallic/texture |
delete_entity |
Remove entity by name |
list_entities |
List all named entities |
get_world_state |
Full JSON state dump |
capture_screenshot |
Request screenshot |
Threading
- MCP server runs on
Task.Run(Kestrel thread pool) - Commands enqueued via
AiCommandQueue(thread-safeConcurrentQueue) - Main thread calls
ProcessPending()before render each frame CompletePendingScreenshots()after render
5. Physics
- JoltPhysicsSharp 2.21.0
PhysicsWorld: gravity (0, -9.81, 0), 2 object layers (NonMoving, Moving)RigidBodycomponent: DynamicBox, DynamicSphere, StaticBox, StaticPlane- Lazy initialization:
IsInitializedflag, bodies created on first frame SyncTransforms: pulls Jolt positions/rotations back into ECS Transforms- Pause/Resume via
physicsEnabledflag - Reset Scene:
RemoveBodybeforeDestructto clean Jolt references
6. ImGui
- Separate pipeline with alpha blending, no depth write
- Font atlas uploaded to R32_SFLOAT VkImage via staging buffer
- Mouse input from
IInputStatefed toImGui.GetIO()each frame - Debug panel: FPS, camera, entity count, physics toggle, reset scene
- Shadow & Light panel: per-light intensity/range/RGB, shadow bias/radius/farplane, ambient RGB
- Video recording panel: Start/Stop buttons
7. Video Recording
vkCmdCopyImageToBuffercopies swapchain image to HOST_VISIBLE buffer- BGRA pixel data piped to FFmpeg via stdin
- FFmpeg:
rawvideo bgra → libx264 yuv420p, 60 FPS input, 30 FPS output - Capture before ImGui (video without UI)
- Deferred read: buffer read at start of next frame (after fence wait)
8. Content
| File | Description |
|---|---|
cube.obj |
Unit cube, CCW winding |
sphere.obj |
UV sphere 24×16, CCW winding |
torusknot.obj |
Torus knot (3,2), 4800 verts, smooth normals |
torus.obj |
Donut, 24×16 segments, CCW winding |
pyramid.obj |
4-sided pyramid, CCW winding |
diamond.obj |
Octahedron (8 faces), CCW winding |
cone.obj |
24-sector cone with bottom cap, CCW winding |
9. Testing
227 xUnit tests:
- VulkanStructSizeTests (47): C# struct sizes match C Vulkan headers
- VulkanEnumValueTests (40+): sType, format, layout, topology, sync2 flags
- VertexLayoutTests (5): Vertex struct size (36B), field offsets
- ObjLoaderTests (8): OBJ parsing, face normals, winding
- ObjLoaderFaceNormalTests (5): Face normal computation
- ShadowMapTests (10): Face directions, FOV, up vectors, far plane
- ShadowShaderTests (5): SPIR-V shader existence
- CameraTests, TransformTests, TimingTests, CameraControllerTests, AiCommandProcessorTests, RenderBackendFactoryTests, MeshMathAndProceduralTests, SceneSerializerTests: Core functionality