In Spatial, you describe what your 3D scene should look like, not the sequence of GPU commands needed to draw it. This is the declarative model: you express intent, and the library figures out how to fulfill it. You place a cube at a position in world space, set its size in meters, and Spatial takes care of building the model matrix, binding the vertex buffer, setting shader uniforms, and issuing the draw call — none of which you ever write directly.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/danielitoCode/Spatial/llms.txt
Use this file to discover all available pages before exploring further.
From imperative to declarative
Traditional OpenGL programming is imperative: you issue commands step by step, in the right order, at the right time. A minimal cube render in raw OpenGL looks like:Modifier3D transforms are converted to model matrices internally, mesh buffers are registered once and reused, and every frame is scheduled through Android’s Choreographer so rendering stays in sync with the display refresh rate.
How re-rendering works
TheScene composable orchestrates the full pipeline from declarative description to pixels on screen. Here is what happens on each composition pass:
Scene graph collection
Scene calls rememberSceneGraph(content), which provides a LocalSceneContentScope via CompositionLocalProvider. Every Element.Cube, Element.Sphere, or Element.Plane inside the content lambda registers a SceneNode (a pairing of PrimitiveShape and Modifier3D) into the scope’s internal SceneBuilder.Node conversion
The resulting
List<SceneNode> is mapped to List<RenderableNode>. Each RenderableNode carries a meshId string, a 16-element FloatArray model matrix derived from the Modifier3D transforms, and a MaterialData RGBA color.Camera snapshot
A
CameraSnapshot is taken from CameraState. The snapshot is an immutable value type — it captures yaw, pitch, and zoom at the exact moment of composition, preventing tearing if the camera continues to animate between frames.Frame submission
Both the node list and the camera snapshot are handed to the render host via the
renderSceneFrame extension function:renderSceneFrame is a convenience wrapper that calls updateScene(nodes), updateCamera(cameraSnapshot), and then requestFrame() on the SceneRenderHost. DefaultSceneRenderHostFactory creates a SpatialRuntimeSceneRenderHost, which owns a SpatialRuntime. On requestFrame(), SpatialRuntime.requestFrame(nodes, cameraSnapshot) schedules a vsync-aligned callback via ChoreographerFrameScheduler, where OpenGL draw calls are issued on the GL thread.Scene composable recomposes, the rememberSceneGraph scope resets and re-collects nodes, and the updated node list is submitted for the next frame. No manual invalidation is required.
Reactive state
CameraState is annotated @Stable and its observable properties are backed by Compose mutableStateOf. This means any composable that reads cameraState.yaw, cameraState.pitch, or cameraState.zoom automatically subscribes to those values and recomposes when they change:
LaunchedEffect. Reading cameraState.yaw in a Text composable is all that’s needed:
yaw and zoom properties are mutableStateOf-backed, Compose tracks the read and re-executes only the composable that accessed them — not the entire Scene. This fine-grained reactivity is the same mechanism Jetpack Compose uses for all UI state.
Key principle: Spatial scenes describe state. The library decides when and how to redraw.