Technical documentation of PyEngine's modular architecture and design decisions.
PyEngine is a modern Vulkan-based game engine built with C++20, following clean architecture principles and leveraging modern C++ idioms.
- Modularity: Clear separation between Core, Platform, Renderer, Scene, and Assets
- Modern C++20: Leveraging concepts, ranges, and smart pointers
- Data-Oriented: ECS architecture with EnTT for cache-friendly iteration
- Explicitness: Vulkan's explicit nature is embraced, not hidden
Purpose: Foundation utilities used across the engine
Components:
Log: spdlog wrapper with engine/client separationTimer: High-precision timing for delta timeFileSystem: Cross-platform file I/O utilitiesAssert: Debug assertions with logging integration
Dependencies: spdlog, fmt, C++20 filesystem
Purpose: Abstract windowing and input across platforms
Components:
Window: GLFW window wrapper with Vulkan surface creationInput: Polling-based input system with mouse delta tracking
Key Features:
- Framebuffer resize callbacks
- Vulkan surface compatible
- ESC to exit
Dependencies: GLFW, Vulkan
Purpose: Vulkan rendering backend
Architecture Flow:
Window → VulkanContext → Swapchain → RenderPass
↓
VMA Allocator → Buffers/Images
↓
Pipeline → Descriptors → Rendering
Components:
- Instance with validation layers (Debug only)
- Physical device selection (prefers discrete GPU)
- Logical device + queue families
- Surface management
- Image acquisition
- Presentation
- Depth buffer
- Framebuffers
- Automatic recreation on resize
- Shader module loading (SPIR-V)
- Vertex input layout (position, normal, texcoord)
- Rasterization state
- Depth testing
- Dynamic viewport/scissor
- VMA-backed buffers
- Vertex/index/uniform buffer types
- Staging buffer transfers
- Frame synchronization (fences/semaphores)
- Command buffer management
- Descriptor set allocation/updates
- Uniform buffer per-frame updates
- BeginFrame/EndFrame abstraction
Vulkan Objects Created:
- 1 VkInstance
- 1 VkDevice
- 1 VkSwapchainKHR
- 3 VkImage (swapchain - may vary)
- 1 VkRenderPass
- 1 VkPipeline
- 2 VkDescriptorSet (double buffered)
- N VkBuffer (vertex, index, uniform × 2)
Memory Management:
- VMA for all buffers and images
- Automatic host-visible mapping for uniform buffers
- GPU-only memory for vertex/index buffers
Purpose: Entity-Component-System game object representation
Components:
- EnTT entity wrapper
- Type-safe component access
- Fluent API
TransformComponent: Position, rotation (Euler), scaleCameraComponent: Projection, view matricesMeshRendererComponent: Mesh handle, color
- FPS controller
- WASD + Space/Ctrl movement
- Right-mouse-button look
- Configurable FOV, near/far planes
- EnTT registry ownership
- Entity creation/destruction
- Update loop
ECS Benefits:
- Cache-friendly iteration
- Easy serialization (future)
- Excellent performance for many entities
Purpose: Load and manage game assets
Components:
- Vertex buffer (position, normal, UV)
- Index buffer
- Procedural generation (cube)
- glTF loading support (tinygltf)
Vertex Layout:
struct Vertex {
glm::vec3 Position; // Offset: 0
glm::vec3 Normal; // Offset: 12
glm::vec2 TexCoord; // Offset: 24
}; // Stride: 32 bytesPurpose: Debug and editor UI
Features:
- Docking enabled
- Vulkan backend
- Custom dark theme
- FPS counter
- Camera telemetry
- GPU info
Integration:
- Separate descriptor pool
- Font texture upload
- Render in main render pass
-
BeginFrame
- Wait for fence
- Acquire swapchain image
- Reset command buffer
- Begin render pass
-
Render
- Update uniform buffers (MVP matrices)
- Bind pipeline
- Bind descriptor sets
- Draw mesh (bind vertex/index, issue draw call)
- ImGui rendering
-
EndFrame
- End render pass
- Submit command buffer
- Present swapchain image
- Handle resize if needed
- Fences: CPU-GPU sync (wait for frame completion)
- Semaphores: GPU-GPU sync (image available → render → present)
- Double buffering: 2 frames in flight for parallelism
GLSL → glslc → SPIR-V → VkShaderModule
- CMake custom command compiles on build
- Output:
build/bin/shaders/*.spv - Loaded at runtime via
FileSystem::ReadBinaryFile
layout(binding = 0) uniform UniformBufferObject {
mat4 model;
mat4 view;
mat4 proj;
} ubo;Updated per-frame with camera matrices.
Current: Single-threaded rendering Future: Multi-threaded command buffer recording
- Assertions: Debug-only with logging
- Validation Layers: Enabled in Debug, log warnings/errors
- VkResult checks: All wrapped with assertions
Root CMakeLists.txt
├── FetchContent (EnTT, ImGui, tinygltf, VMA)
├── engine/CMakeLists.txt (PyEngineLib static library)
└── sandbox/CMakeLists.txt (PyEngineSandbox executable)
cmake/CompileShaders.cmake defines compile_shaders() function for automatic SPIR-V generation.
- Render Graph: High-level rendering abstraction
- Multi-threading: Parallel command recording
- Asset Streaming: Async loading
- ECS Systems: Formalized system execution
- Material System: Shader permutations, PBR
- GPU Culling: Compute-based frustum/occlusion culling
See ROADMAP.md for details.
- VMA: Minimizes allocation overhead
- Descriptor Sets: Pre-allocated pool
- Command Buffers: Reused per-frame
- Swapchain: Mailbox mode preferred (triple buffering)
| Library | Purpose | Why |
|---|---|---|
| Vulkan | Graphics API | Explicit control, modern, cross-platform |
| GLFW | Windowing | Lightweight, Vulkan-native |
| GLM | Math | Header-only, Vulkan-compatible |
| EnTT | ECS | Header-only, fast, modern C++ |
| spdlog | Logging | Fast, customizable |
| ImGui | UI | Immediate mode, easy integration |
| VMA | Memory | Best Vulkan memory management |
| tinygltf | glTF | Header-only, complete glTF 2.0 |
┌─────────────────────────────────────────┐
│ Application │
│ (Sandbox Main Loop) │
└──────────────┬──────────────────────────┘
│
┌────────┴─────────┐
│ │
┌─────▼──────┐ ┌──────▼────────┐
│ Scene │ │ Renderer │
│ (ECS) │ │ (Vulkan) │
└─────┬──────┘ └──────┬────────┘
│ │
┌─────▼──────┐ ┌──────▼────────┐
│ Camera │ │ VulkanContext│
│ Mesh │ │ Swapchain │
│Components │ │ Pipeline │
└────────────┘ └───────────────┘
PyEngine prioritizes clarity, modularity, and modern best practices over premature optimization. The architecture is designed to evolve with features while maintaining clean interfaces.