This guide walks you from a blank project to a working canvas window that draws anti-aliased shapes using Prowl.Quill and the OpenTK backend. You will install the NuGet package, implement the rendering loop, and see real output on screen — all in a handful of steps. No prior experience with Quill is required, but familiarity with C# and basic .NET project creation is assumed.Documentation 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.
Add
Prowl.Quill to your project. The library targets net6.0 through net10.0 and netstandard2.1, so it fits any modern .NET project.Prowl.Quill’s single transitive dependency is Prowl.Scribe, which is pulled in automatically. You do not need to add it separately.
Prowl.Quill.Canvas does not talk to the GPU directly. Instead it produces tessellated geometry and hands it to an ICanvasRenderer backend at the end of each frame. The interface has four members:public interface ICanvasRenderer : IDisposable
{
// Allocate a GPU texture (e.g. for the font atlas).
object CreateTexture(uint width, uint height);
// Return the pixel dimensions of a texture.
Int2 GetTextureSize(object texture);
// Upload RGBA pixel data to a sub-region of a texture.
void SetTextureData(object texture, IntRect bounds, byte[] data);
// Submit all accumulated draw calls to the GPU.
void RenderCalls(Canvas canvas, IReadOnlyList<DrawCall> drawCalls);
// Optional: advertise backdrop-blur support (default false).
bool SupportsBackdropBlur => false;
}
Use a sample backend
Copy the
CanvasRenderer.cs from the OpenTK sample in the repository (Samples/OpenTKExample/CanvasRenderer.cs). It is a self-contained OpenGL 3.3 Core backend with no extra dependencies beyond OpenTK. Write your own
Implement
ICanvasRenderer for any graphics API (DirectX, Vulkan, Metal, your engine’s native renderer). See the Backends Overview for a detailed walkthrough.Once you have a renderer instance, create a
Canvas and call BeginFrame at the start of each render loop iteration. BeginFrame clears the accumulated geometry and sets the logical dimensions and DPI scale for the new frame.using Prowl.Quill;
using Prowl.Scribe;
using Prowl.Vector;
// Instantiate your ICanvasRenderer implementation.
var renderer = new CanvasRenderer();
renderer.Initialize(windowWidth, windowHeight, whiteTexture);
// Create the canvas. FontAtlasSettings controls glyph atlas dimensions.
Canvas canvas = new Canvas(renderer, new FontAtlasSettings());
// --- inside your render loop ---
float dpiScale = (float)framebufferWidth / windowWidth; // e.g. 2.0 on Retina
canvas.BeginFrame(windowWidth, windowHeight, dpiScale);
Pass the ratio of physical framebuffer pixels to logical window pixels as
framebufferScale. On standard displays this is 1.0f; on HiDPI / Retina displays it is typically 2.0f. Quill uses this value internally to sharpen anti-aliasing and text rendering.After
BeginFrame, issue drawing commands using the Canvas API. When you are done, call canvas.Render() to flush all geometry to the GPU via your backend.// --- continued inside your render loop ---
// ── Filled rectangle ───────────────────────────────────────────────────────
canvas.RectFilled(10, 10, 100, 50, Color32.FromArgb(200, 255, 100, 100));
// ── Stroked circle ─────────────────────────────────────────────────────────
canvas.SetStrokeColor(Color32.FromArgb(255, 255, 255, 255));
canvas.SetStrokeWidth(2.0f);
canvas.Circle(200, 100, 40);
canvas.Stroke();
// ── Filled rounded rectangle ───────────────────────────────────────────────
canvas.SetFillColor(Color32.FromArgb(200, 100, 255, 150));
canvas.RoundedRect(300, 100, 120, 60, 15, 15, 15, 15);
canvas.Fill();
// ── Custom path (triangle) ─────────────────────────────────────────────────
canvas.BeginPath();
canvas.MoveTo(50, 200);
canvas.LineTo(150, 200);
canvas.LineTo(100, 300);
canvas.ClosePath();
canvas.SetFillColor(Color32.FromArgb(180, 100, 100, 255));
canvas.SetStrokeColor(Color32.FromArgb(255, 255, 255, 255));
canvas.SetStrokeWidth(3.0f);
canvas.FillAndStroke();
// ── Bézier curve ───────────────────────────────────────────────────────────
canvas.BeginPath();
canvas.MoveTo(50, 350);
canvas.BezierCurveTo(100, 250, 200, 450, 250, 350);
canvas.SetStrokeColor(Color32.FromArgb(255, 100, 200, 255));
canvas.Stroke();
// Flush geometry to the GPU.
canvas.Render();
Complete OpenTK Example
The following is the full minimal working example drawn from the repository’sSamples/OpenTKExample directory. It creates an OpenTK 4.x window, initialises the canvas renderer, and draws a set of shapes on every frame.
Program.cs
OpenTKWindow.cs (render loop)
The
whiteTexture reference above is a 1×1 white RGBA texture required by the OpenTK backend as a fallback when no texture is set on a draw call. See TextureTK.cs in the sample for the full texture helper used by the backend.What to Explore Next
Installation
Full installation details: supported frameworks, the Prowl.Scribe dependency, and backend-specific packages.
Canvas Concepts
Understand the path model, state stack, transforms, scissor regions, and brush types.
Backends Overview
Browse the available backends or learn to write your own
ICanvasRenderer.Canvas API Reference
Complete method-by-method reference for every drawing command on
Canvas.