TheDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/ProwlEngine/Prowl.Quill/llms.txt
Use this file to discover all available pages before exploring further.
Canvas class is the heart of Prowl.Quill. Every shape you draw, every gradient you paint, and every glyph you render flows through a single Canvas instance. Rather than issuing immediate GPU commands, Canvas works as an accumulator: it collects vertices, indices, and draw-call metadata throughout a frame, then hands everything to an ICanvasRenderer backend in one coordinated Render() call. This batching model keeps the CPU/GPU boundary thin and gives the backend full visibility into the frame’s geometry before any draw commands are issued.
Constructing a Canvas
ICanvasRenderer (your OpenGL, DirectX, Vulkan, or custom adapter), and a FontAtlasSettings struct that tells the internal text engine how to build its glyph atlas. The constructor throws ArgumentNullException when renderer is null.
The Frame Lifecycle
Every frame follows a three-step pattern: begin, draw, render.BeginFrame — open a new frame
Call
BeginFrame once at the start of each frame. It resets all accumulated geometry, clears the draw-call list, resets the state stack, and records the logical canvas size and HiDPI scale for the upcoming frame.width and height are the logical dimensions of your window (i.e. points or CSS pixels, not physical pixels). framebufferScale is the ratio of physical pixels to logical pixels — 1.0 for standard displays, 2.0 for Retina/HiDPI.Draw — issue drawing commands
Between
BeginFrame and Render, call any combination of path, shape, text, and image drawing methods. Each command appends vertices and indices to the internal buffers and groups them into batched DrawCall records based on the active brush, scissor, and shader state.Render — flush to the GPU
Call After
Render() once at the very end of the frame. It invokes ICanvasRenderer.RenderCalls, passing the accumulated vertex buffer, index buffer, and draw-call list. The backend is then responsible for uploading data and issuing GPU draw commands.Render() returns, the data is still alive until the next BeginFrame clears it — but you should not draw into the canvas between Render() and the next BeginFrame.Minimal Per-Frame Pattern
HiDPI and the Framebuffer Scale
Prowl.Quill separates logical units (what you author in) from physical pixels (what lands on screen). All coordinates you pass to drawing methods are in logical units; the canvas multiplies them byFramebufferScale internally before emitting pixel-space vertices.
| Property | Type | Description |
|---|---|---|
FramebufferScale | float | Physical pixels per logical unit. Set by BeginFrame. |
Width | float | Canvas width in logical units (framebufferWidth / FramebufferScale). |
Height | float | Canvas height in logical units. |
PixelFraction | float | Size of one physical pixel in logical units (1 / FramebufferScale). |
Font atlases are rasterised at
size × FramebufferScale physical pixels automatically, so text stays crisp on every display density without any manual scaling in your drawing code.Draw Call Batching
Canvas minimises GPU state changes by grouping geometry into as few draw calls as possible. Two consecutive shapes are merged into the same DrawCall when their brush state (gradient, texture, shader, scissor region) is identical. A new DrawCall is opened automatically whenever the state changes.
You can inspect the accumulated data at any point after drawing (and before the next BeginFrame):
DrawCall carries:
ElementCount— the number of index elements (triangleCount × 3) belonging to this call.Brush— gradient/texture/shader state snapshot captured at the time the call was opened.- Scissor transform and extent (retrieved via
GetScissor).
Disposing
Canvas implements IDisposable. Call canvas.Dispose() when you are done with it to release the underlying renderer backend: