Architecture¶
SimVX is a Godot-inspired game engine in pure Python: node-based scene hierarchy, Vulkan GPU-driven rendering, and NumPy-based math. This page covers the key internal pipelines.
Node Lifecycle¶
Every node progresses through these stages:
construction --> enter_tree --> ready --> [process / physics_process] --> exit_tree
Construction –
__init__()sets properties andPropertydefaults. No tree access yet.Enter tree –
add_child()inserts the node.Childdescriptors are instantiated. The node receives a reference to itsSceneTree.Ready – Called bottom-up (children first, then parent).
OnReadydescriptors resolve. Safe to access children by path:self["Camera"].Process –
process(dt)runs every frame with wall-clock delta.physics_process(dt)runs at a fixed rate (default 60 Hz) with accumulator-based catch-up.Exit tree – Triggered by
destroy(). Signals are disconnected, children are recursively removed, and the node is dequeued from theSceneTree.
Children added during ready() go through the full lifecycle before the parent’s ready() returns. This guarantees that self["Player/Camera"] is valid inside ready().
Scene Tree¶
SceneTree owns the root node and drives the frame loop:
SceneTree.process(dt) # walk tree, call process(dt) on every node
SceneTree.physics_process(dt) # walk tree, call physics_process(dt)
SceneTree.draw(Draw2D) # 2D draw pass -- nodes emit draw commands
Groups allow batch queries: tree.get_group("enemies") returns all nodes tagged with that group. The tree also manages deferred deletions – destroy() queues removal until the end of the current frame to avoid mutation during iteration.
Render Pipeline¶
SimVX uses a GPU-driven forward renderer. No per-object draw calls from Python.
Scene submission --> SSBO upload --> Multi-draw indirect --> Swapchain present
Frame flow¶
Scene submission –
SceneAdapterwalks the node tree, collectingMeshInstance3Dtransforms, materials, and lights into flat NumPy arrays.Frustum culling – CPU-side bounding-sphere test per viewport. Culled instances are excluded from the draw command buffer.
SSBO upload – Transform, material, and light arrays are
memcpy’d to Shader Storage Buffer Objects. One upload per frame.Draw command build – A
VkDrawIndexedIndirectCommandarray is built (one entry per visible mesh instance).Pre-render – Shadow map passes and SSAO compute dispatch run before the main render pass.
Main render pass – A single
vkCmdDrawIndexedIndirectcall draws all opaque geometry. Transparent objects follow in a separate sorted pass.2D overlay – Text, UI widgets, and
Draw2Dgeometry are rendered in an orthographic pass over the 3D scene.Present – The swapchain image is presented. Frame capture (for headless testing) happens here if requested.
Key data structures¶
Buffer |
Contents |
Update frequency |
|---|---|---|
Transform SSBO |
4x4 model matrices (float32) |
Every frame |
Material SSBO |
Color, roughness, metallic, texture IDs |
On change |
Light SSBO |
Position, color, intensity, type |
Every frame |
Indirect draw buffer |
|
Every frame |
Bindless textures are stored in a single descriptor array (up to 4096 slots). Materials reference textures by integer index – no per-material descriptor set switching.
Input Flow¶
GLFW key/mouse event --> input_adapter --> Input singleton --> InputMap --> game code
GLFW callback –
key_callback_with_uireceives raw key events from GLFW.Input adapter – Translates GLFW key codes to
Key/MouseButtonenums, updates theInputsingleton’s pressed/released state, and routes events to the UI system.Input singleton – Stores per-frame state.
Input.is_key_pressed(Key.W)checks instantaneous state;Input.is_action_just_pressed("jump")checks action bindings.InputMap – Maps named actions to typed key bindings:
InputMap.add_action("jump", [Key.SPACE, JoyButton.A]). Actions are queried by name inprocess(dt).
UI widgets receive input first. If a focused widget consumes the event, it does not propagate to game nodes.
Performance Notes¶
Node counts: The tree walk is pure Python, so keep node counts under ~5,000 for 60 fps process ticks. Use groups and
find()to avoid deep recursive searches.Draw call batching: All opaque geometry is drawn in a single indirect draw call regardless of material count. Only transparent objects require sorting.
SSBO capacity: Default buffers support 16,384 instances and 256 lights. Exceeding these triggers a buffer resize (one-time stall).
Physics tick: Fixed at 60 Hz by default. Multiple physics steps per frame occur when the frame rate drops below 60 fps (capped at 100 ms accumulation to prevent spiral-of-death).
Headless mode:
App(visible=False)creates a real Vulkan surface with an invisible GLFW window. Rendering is identical to visible mode – useful for automated visual tests in CI.