Render routes
bibui separates recording a frame from drawing it. The runtime turns your build into a DrawList. The list holds fills, borders, shadows, paths, textures and glyphs. A window provides a target, and a renderer draws the list onto it. There are three renderers and two windows. You choose the combination in code. Nothing is discovered at run time.
Steps 1 to 3 are identical on every route. That is why a frame is a pure function of tree, theme, surface size and scale factor. Changing route changes only steps 4 and 5.
The renderers
| Renderer | Package | Draws on | Text | Notes |
|---|---|---|---|---|
SkiaGlRenderer | Bibui.Raster.Skia | The GPU, through OpenGL | Skia | The fastest on whole-window redraws. Needs a GlTarget, so it runs on the Silk window only. |
SkiaCpuRenderer | Bibui.Raster.Skia | The CPU | Skia | Draws into a CpuTarget, an SKSurface or RasterPixels. The verifier draws its reference pictures with it. |
ManagedRenderer | Bibui.Raster.Managed.Backend | The CPU, in C# | FreeType + HarfBuzz | Uses no Skia and no native code of its own. Clean under Native AOT, and has damage tracking. |
Every renderer has the same calls: Draw, CreateTexture, UpdateTexture and ReleaseTexture. Build code never sees the renderer. An app names its renderer in one place:
global using Renderer = Bibui.Raster.Skia.SkiaGlRenderer; // or Bibui.Raster.Managed.Backend.ManagedRendererWindow × renderer
Which renderer can draw into which window, and how the pixels get to the screen.
Skia, GPUSkiaGlRenderer | Skia, CPUSkiaCpuRenderer | Managed, CPUManagedRenderer | |
|---|---|---|---|
| Silk windowGLFW + OpenGL | DefaultSilkSkiaGl |
Yesvia GlCpuFramebuffer · SilkSkiaCpu |
Yesvia GlCpuFramebuffer · SilkManaged |
| Wayland windowwl_shm buffers | Plannedno GL on this host yet | YesWaylandSkiaCpu · AOT beside libSkiaSharp |
DefaultWaylandManaged · one AOT file |
| Headlessmemory you own | — | YesRasterPixels, SKSurface |
YesRasterPixels |
The two complete routes
Silk + Skia
Bibui.Platform.Silk · Bibui.Raster.Skia- Dependencies
- SkiaSharp, Silk.NET (GLFW), an OpenGL driver, fontconfig
- Native AOT
- No. Silk.NET finds its platform by reflection
Wayland + managed
Bibui.Platform.Wayland · Bibui.Raster.Managed.Backend- Dependencies
- No NuGet packages. libwayland-client, libxkbcommon, FreeType, HarfBuzz
- Native AOT
- Yes, one file of about 5 MB
Choosing a route
The easy way is a Bibui.App package. It names the window and the renderer for you. Swap the package to swap the route, with no change to your code. To choose in code, open the window. Then construct the renderer, bound to one of the window's targets:
using var window = SilkWindow.Open(runtime, new("My app", 960, 640));
using var skia = new SkiaGlRenderer(window.Gl);
// in the loop, between BeginDraw and EndDraw:
skia.Draw(runtime.DrawList);using var window = SilkWindow.Open(runtime, new("My app", 960, 640));
using var shown = new GlCpuFramebuffer(window.Gl); // memory shown on the GL framebuffer
using var skia = new SkiaCpuRenderer(shown.Cpu); // or new ManagedRenderer(shown.Cpu)
// in the loop, between SilkWindow.BeginDraw and SilkWindow.EndDraw:
shown.BeginDraw();
skia.Draw(runtime.DrawList);
shown.EndDraw(); // upload, then blit onto the framebufferusing var window = WaylandWindow.Open(runtime, new("My app", 960, 640),
new WaylandHostOptions { AppId = "dev.example.app" });
using var managed = new ManagedRenderer(window.Cpu) { DamageTracking = true };
// in the loop, between BeginDraw and EndDraw:
managed.Draw(runtime.DrawList);Falling back from the GPU
Nothing falls back on its own. SkiaGlRenderer.ForGl returns null where Skia cannot drive the window's GL context. The app then decides what to do instead. Usually that means the CPU through GlCpuFramebuffer, as in the second tab above. Textures belong to the renderer that made them. If the app swaps renderer while it runs, the textures are made again.
Text
bibui holds, measures and draws text as UTF-16. Skia shapes and draws text on the Skia routes. On the managed route, FreeType rasterises the glyphs and HarfBuzz shapes them. Text from FreeType and HarfBuzz measures exactly as Skia's does. Its pixels match Skia's too, at SSIM 1.0. So a layout does not move when you change route.
Damage tracking
The managed rasterizer can repaint only what changed. Turn it on with new ManagedRenderer { DamageTracking = true } or BIBUI_DAMAGE=1. It then compares each DrawList with the previous one, draw by draw. It rasterises only the rectangles that differ. It draws them onto a surface that still holds an earlier frame.
- The result is byte-identical to a full repaint.
- If the changes cover more than half the surface, the whole frame is drawn.
- Nothing above the rasterizer changes. Every frame is still built, laid out and recorded in full.
- A typical frame changes only a hover colour or a caret. That is how a CPU-only stack holds 60 Hz.
BIBUI_DAMAGE_DEBUG=1tints what each frame repainted, so you can see it.
Skia always draws whole frames.
Pixel parity
Each component has one reference picture, rendered by Skia. Every route is compared with it by SSIM. The managed rasterizer matches it except in gradients and wide strokes. Within one build of bibui, the same tree, theme, surface size and scale factor give the same image, byte for byte, every time.
| Check | Skia CPU | Skia GPU | Managed |
|---|---|---|---|
| Component fixtures (87) | Reference | Not published | Matches, except gradients and wide strokes |
| Text, shaped and drawn | Reference | Not published | SSIM 1.0 |
| Damage-tracked frame against a full repaint | — | — | Byte-identical |
Planned. A parity table per component for every route, giving each route's SSIM against the reference. The verifier will generate it on each release.
Environment variables
These are diagnostics. They are not part of the API promise (see Versions).
| Variable | Effect |
|---|---|
BIBUI_DAMAGE=1 | Turns on damage tracking in ManagedRenderer. |
BIBUI_DAMAGE_DEBUG=1 | Tints the region each frame repainted. |
BIBUI_DAMAGE_TRACE=1 | Logs what damage tracking decided, frame by frame. |
BIBUI_ALWAYS_RENDER=1 | Draws a frame every refresh, even when nothing asked for one. Useful for timing. |
BIBUI_PROFILE=1 | Prints, for every frame, the time EndBuild spent building, laying out and recording. |
BIBUI_REDUCED_MOTION=1 | Every eased value arrives in one step, hover included. |
BIBUI_TEST_SERVER=1 | Starts the test server in a BibuiApp. |
Custom drawing and textures
A box can paint itself. Set Box.Draw to a callback. Whatever it draws is recorded into the same DrawList as the built-in nodes. So every renderer draws it, and damage tracking sees it. Images and video frames are textures, made on the renderer the app chose. The formats are RGBA8, BGRA8, R8, RG8 and A8. R8 suits a camera's NIR frame, for example. A Stream texture is updated in place each frame. Concepts covers both.