Skip to main content

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.

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.
1
Install the NuGet package
2
Add Prowl.Quill to your project. The library targets net6.0 through net10.0 and netstandard2.1, so it fits any modern .NET project.
3
dotnet CLI
dotnet add package Prowl.Quill
PackageReference
<PackageReference Include="Prowl.Quill" Version="1.4.0" />
4
If you are following the OpenTK example below, also add the OpenTK package:
5
dotnet add package OpenTK --version 4.9.4
6
Prowl.Quill’s single transitive dependency is Prowl.Scribe, which is pulled in automatically. You do not need to add it separately.
7
Understand ICanvasRenderer
8
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:
9
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;
}
10
You have two options:
11
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.
12
Create the Canvas
13
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.
14
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);
15
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.
16
Draw shapes and call Render()
17
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.
18
// --- 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();
19
Always call canvas.BeginFrame(...) at the top of each frame before any drawing commands. Omitting it means geometry from the previous frame accumulates and produces incorrect output.

Complete OpenTK Example

The following is the full minimal working example drawn from the repository’s Samples/OpenTKExample directory. It creates an OpenTK 4.x window, initialises the canvas renderer, and draws a set of shapes on every frame.

Program.cs

using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;

namespace OpenTKExample
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var nativeWindowSettings = new NativeWindowSettings
            {
                ClientSize = new(1280, 720),
                Title = "OpenTK Quill Example",
                WindowBorder = WindowBorder.Resizable,
                API = ContextAPI.OpenGL,
                Profile = ContextProfile.Core,
                APIVersion = new Version(3, 3)
            };

            using var window = new OpenTKWindow(GameWindowSettings.Default, nativeWindowSettings);
            window.Run();
        }
    }
}

OpenTKWindow.cs (render loop)

using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using Prowl.Quill;
using Prowl.Scribe;
using Prowl.Vector;

namespace OpenTKExample
{
    public class OpenTKWindow : GameWindow
    {
        private Canvas _canvas;
        private CanvasRenderer _renderer;

        public OpenTKWindow(GameWindowSettings gameWindowSettings,
                            NativeWindowSettings nativeWindowSettings)
            : base(gameWindowSettings, nativeWindowSettings) { }

        protected override void OnLoad()
        {
            base.OnLoad();

            GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);

            // Initialise the OpenGL backend and the canvas.
            _renderer = new CanvasRenderer();
            _renderer.Initialize(ClientSize.X, ClientSize.Y, whiteTexture);
            _canvas = new Canvas(_renderer, new FontAtlasSettings());
        }

        protected override void OnRenderFrame(FrameEventArgs args)
        {
            base.OnRenderFrame(args);

            // 1. Begin a new frame — clears accumulated geometry.
            float dpiScale = (float)FramebufferSize.X / ClientSize.X;
            _canvas.BeginFrame(ClientSize.X, ClientSize.Y, dpiScale);

            // 2. Issue drawing commands.
            _canvas.RectFilled(10, 10, 200, 100, Color32.FromArgb(200, 255, 100, 100));

            _canvas.SetStrokeColor(Color32.FromArgb(255, 255, 255, 255));
            _canvas.SetStrokeWidth(3.0f);
            _canvas.Circle(400, 200, 80);
            _canvas.Stroke();

            _canvas.SetFillColor(Color32.FromArgb(180, 100, 200, 255));
            _canvas.RoundedRect(600, 50, 160, 80, 20, 20, 20, 20);
            _canvas.Fill();

            // 3. Flush geometry to the GPU.
            GL.Clear(ClearBufferMask.ColorBufferBit);
            _canvas.Render();

            SwapBuffers();
        }

        protected override void OnResize(ResizeEventArgs e)
        {
            base.OnResize(e);
            GL.Viewport(0, 0, ClientSize.X, ClientSize.Y);
            _renderer.UpdateProjection(ClientSize.X, ClientSize.Y);
        }

        protected override void OnUnload()
        {
            _renderer.Cleanup();
            base.OnUnload();
        }
    }
}
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.

Build docs developers (and LLMs) love