2D Rendering
The 2D rendering pipeline is a deferred command system: each frame, your view function populates a RenderBuffer2D with Command2D values, and the Renderer2D<'Model> sorts them by layer and executes them in order.
What and Why
A deferred renderer means you describe what to draw without worrying about when to draw it. The renderer handles:
- Layer ordering — Commands are sorted by
int<RenderLayer>so backgrounds draw before foregrounds. - Camera transforms —
Draw.beginCamera/Draw.endCamerabracket world-space content. - Shader modes —
Draw.beginShader/Draw.endShaderenable per-section effects. - Post-processing — Screen-space shader passes applied after the scene renders.
- GPU batching — raylib auto-batches standard draw calls; the renderer never interferes.
This is especially useful for:
- 2D lighting — Light commands and sprites are interleaved in the same buffer and processed together.
- UI overlays — Draw UI on a higher layer (and optionally a separate camera) above your game world.
- Debug visualization — Toggle debug shapes on/off by adding or removing commands.
- Portability — The same view function works with any renderer configuration.
When to use deferred vs immediate
Situation |
Approach |
|---|---|
Sprites, text, shapes, tiles |
Use |
Custom rlgl meshes, instancing |
Use |
One-off GPU operations |
Prefer deferred; use immediate only when raylib lacks the API |
How it works
Program.mkProgram init update
|> Program.withRenderer (fun () -> Renderer2D.create myView)
Each frame, the runtime calls myView ctx model buffer. Your view adds commands, the renderer sorts and executes:
buffer.Clear()— wipe previous frame's commandsmyView ctx model buffer— populate with this frame's draw commandsbuffer.Sort()— sort by layer (ascending)- Execute in order, managing camera/shader state transitions
Command API layers
Two ways to add commands to the buffer:
Layer |
When to use |
|---|---|
|
Everyday use — pipe-friendly, supports partial application |
|
When you need to store or reuse commands without a buffer |
Lighting
The 2D lighting system (Mibo.Elmish.Graphics2D.Lighting) provides point lights, directional lights, ambient light, and SDF soft shadows — all GPU-driven with no extra render passes.
buffer
|> LightDraw.setAmbient lightingCtx (5<RenderLayer>, { Color = gray })
|> LightDraw.addDirectionalLight lightingCtx 6<RenderLayer> { Direction = sunDir; ... }
|> LightDraw.addPointLight lightingCtx 7<RenderLayer> { Position = torchPos; ... }
|> LightDraw.litSprite lightingCtx playerSprite
|> LightDraw.endLighting lightingCtx 999<RenderLayer>
See Lighting & Shadows for details.
Multi-camera rendering
Use Camera2DConfig for viewport-based rendering, split-screen, or overlay cameras:
// Split-screen left/right
let left = Camera2D.splitScreenLeft cam1 Color.CornflowerBlue
let right = Camera2D.splitScreenRight cam2 Color.DarkGreen
buffer
|> Draw.beginCameraWith 0<RenderLayer> left
|> // ... left viewport ...
|> Draw.endCamera 100<RenderLayer>
|> Draw.beginCameraWith 200<RenderLayer> right
|> // ... right viewport ...
|> Draw.endCamera 300<RenderLayer>
Camera2DConfig controls viewport (normalized 0–1 coordinates) and clear color. See Camera for the full API.
Next steps
- Buffer & Commands — How to build every type of draw command
- Lighting & Shadows — Point, directional, ambient lights + SDF shadows
- Particles — Batched textured quads
- Custom Commands — Custom rendering with DrawImmediate
- Performance — Writing efficient rendering code
- Camera — Cameras and coordinate transforms
Mibo.Raylib