Buffer & Commands
Every frame, your view function receives a RenderBuffer2D and populates it with commands. This page explains how to think about the buffer; the member-level details live in Draw DSL and the API reference.
The buffer lifecycle
// Your view function signature:
val view : GameContext -> 'Model -> RenderBuffer2D -> unit
The buffer is pre-cleared by the renderer each frame. Do not call Clear() yourself. Just add commands:
let myView (ctx: GameContext) (model: Model) (buffer: RenderBuffer2D) =
let player = SpriteState.create(tex, Rectangle(100f, 100f, 32f, 32f), Rectangle(0f, 0f, 32f, 32f))
buffer
.fillRect(0f, 0f, 800f, 600f, Color.SkyBlue)
.sprite(player)
.drop()
The .drop() at the end silences the unused-value warning. It does nothing.
Layers, not call order
Commands execute in layer order, not insertion order. Every 2D member takes an optional layer (default 0<RenderLayer>); within one layer, insertion order is preserved. This means you can write your view in whatever order reads best — backgrounds low, world in the middle, UI high:
buffer
.text(font, "HP 100", Vector2(10f, 10f), 20f, layer = 1001<RenderLayer>)
.fillCircle(worldPos, 20f, Color.Red, layer = 10<RenderLayer>)
.rectGradientV(0, 0, w, h, skyTop, skyBot, layer = -1000<RenderLayer>)
.drop()
Even though the text was added first, the sky gradient draws first, then the circle, then the UI.
Neutral inputs, backend records
The fluent DSL takes Mibo.Color, System.Numerics vectors, and float rectangle coordinates on both backends — the buffer converts for you. Backend state records (SpriteState, TextState, light records, particle arrays) pass through as the backend's own types: raylib Rectangle (four float32 fields) vs MonoGame Microsoft.Xna.Framework.Rectangle (int fields). See MonoGame type quirks.
Sprites and text have builder-equipped state records when you want to carry them around:
let sprite = SpriteState.create(tex, Rectangle(100f, 100f, 32f, 32f), Rectangle(0f, 0f, 32f, 32f))
let redSprite = { sprite with Color = Color.Red; Layer = 10<RenderLayer> }
let spinning = { sprite with Rotation = 0.785f } // ~45 degrees
buffer.sprite(sprite).drop()
For text, the parts form is usually shortest — size maps to each backend's sizing model (raylib: font size in pixels; MonoGame: uniform scale):
buffer
.text(font, "Score: 100", Vector2(10f, 10f), 20f, tint = Color.Yellow, layer = 100<RenderLayer>)
.drop()
State commands (blend, scissor, viewport)
State commands affect subsequent draws within the same layer range:
buffer
.setBlend(BlendMode.Additive)
.fillCircle(center, 20f, Color.Red, layer = 10<RenderLayer>)
.setBlend(BlendMode.AlphaBlend)
.drop()
Blend mode, scissor rect, line width, and viewport are reset at the start of each frame.
Sampler state (MonoGame only)
.setSamplerState(sampler, ?layer) sets the sampler used for subsequent sprites and flushes/restarts the sprite batch. It is MonoGame-only: calling it on the raylib buffer is a compile error, because the raylib buffer has no corresponding operation.
buffer
.setSamplerState(SamplerState.PointClamp, layer = 9<RenderLayer>)
.sprite(SpriteState.create(atlas, dest, src))
.drop()
The sampler defaults to SamplerState.LinearClamp and is reset each frame, alongside blend mode, scissor, line width, and viewport.
raylib has no equivalent command — a texture's filter is a property of the texture, not the batch. Override the load-time default with the
Texture.filterhelper:assets.Texture "tiles.png" |> Texture.filter TextureFilter.Point(apply once at load/init, not per frame). See Assets.
Cameras
Wrap world-space content between .beginCamera(...) and .endCamera(...):
let camera = Camera2D.create (Vector2(400f, 300f)) 1.0f viewportSize
buffer
.beginCamera(camera)
.fillCircle(worldPos, 20f, Color.Red, layer = 10<RenderLayer>)
.endCamera(layer = 1000<RenderLayer>)
// After endCamera, draws are in screen space:
.text(font, "HUD", Vector2(10f, 10f), 20f, layer = 1001<RenderLayer>)
.drop()
See Camera for details.
Next steps
- Draw DSL — the full fluent surface with defaults
- Custom Commands — the
.drawImmediate(...)escape hatch - Performance — writing efficient rendering code
Mibo