Rendering Pipeline
Frame structure, culling, world vs. screen space and the export path
Rendering goes through RenderingHelper, which owns the frame structure. DrawingManager.Draw(SKCanvas) is a one-line forwarder, and the same helper serves the export path with a different configuration.
Table of Contents
Frame Structure
Every frame runs the same ordered passes:
- Update and partition — each component's
Update(DrawingContext)runs once, then it is partitioned into dynamic (world space) or static (screen space,IsStatic) lists. - World transform —
canvas.Translate(PanOffset)followed bycanvas.Scale(Zoom). - Grid — drawn in world space when
ShowGridis on. - Components — each dynamic component's
Draw(canvas, drawingContext)runs with a context whoseBoundsiscanvas.DeviceClipBounds. - Connection lines — every line draws; invisible lines are skipped. A line being dragged is drawn last as a preview.
- Selection adorners — marquee rectangle, resize handles, then the host's
WorldOverlaycallback (comment pins use this). - Static overlays — after
canvas.Restore(), screen-space components (palette, property editor, minimap) draw without pan/zoom.
Overlay Hook
DrawingManager.WorldOverlay is an Action<SKCanvas> invoked inside the world transform after selection adorners. Use it for world-space annotations that must not appear in exports.
World vs. Screen Space
The split is explicit and drives correctness:
| Space | Contents | Transform |
|---|---|---|
| World | Diagram components, connection lines, grid, selection adorners, world overlay | Pan offset + zoom applied |
| Screen | Palette, property editors, minimap, context menus (components with IsStatic = true) | No transform; drawn after restore |
Input follows the same convention: the host converts pointer coordinates into world space before calling HandleMouseDown/Move/Up, so hit testing never deals with zoom.
Culling and Update
The DrawingContext passed to components carries Zoom, PanOffset and Bounds (the device clip rectangle). Components use Bounds to skip work that cannot be visible — that is why a component drawn with an empty viewport produces no ink, and why tests draw through a manager with a real surface.
Exceptions during Update or Draw are caught per component and written to the debug trace: one misbehaving component cannot take down the frame.
// The frame entry point
public void Draw(SKCanvas canvas) => _renderingHelper.DrawAll(canvas);
// What a component receives
protected override void DrawContent(SKCanvas canvas, DrawingContext context)
{
if (!context.Bounds.IntersectsWith(Bounds)) return; // culled
// ... draw
}
Export Path
Exports reuse the pipeline with everything interactive disabled: no pan/zoom, no grid, no selection adorners and no static overlays. That produces a clean diagram at 1:1 scale regardless of the current viewport.
| Call | Configuration |
|---|---|
DrawAll(canvas) | applyPanZoom: true, grid, selection and static overlays on. |
DrawForExport(canvas) | All off, zoom: 1, pan: (0,0). |
The host wraps this for files: ExportToPng(path, scale) renders to an offscreen surface at the requested scale, ExportToSvg and ExportToPdf use the same draw pass with their respective Skia backends.
Measured Cost
The repository tracks render cost in RenderPerformanceTests (median of five runs on the CI machine):
| Scenario | Frame time | Allocations |
|---|---|---|
| 200 components | ~3.1 ms | ~40.7 KB |
| 200 components + 199 connection lines | ~4.5 ms | ~40.8 KB |
The line-heavy case is nearly allocation-neutral because ConnectionLine reuses its SKPaint across frames (the paint-reuse pass removed ~17 KB per frame and roughly a millisecond). Text goes through TypefaceCache, whose cached lookups are about 13× faster than resolving typefaces per draw.
// Reuse paints across frames; never allocate in DrawContent
private readonly SKPaint _fill = new SKPaint { IsAntialias = true };
// Cache typefaces instead of resolving them per frame
var typeface = TypefaceCache.Get("Segoe UI", SKFontStyle.Normal);
Console.WriteLine(TypefaceCache.CachedCount); // entries currently cached
See the Performance Guide for the full optimization checklist.