Building Custom 2D/3D GUI Workflows with SkiaSharp
When off-the-shelf UI components cannot meet the rendering requirements of a complex workflow tool, you have to go lower level. Here is how we used SkiaSharp to build a fully custom interactive canvas.
There is a class of software, workflow editors, design tools, diagram builders, node-based systems, where standard UI component libraries hit a wall. The components are not designed for this. You need full control over rendering, input handling, and the visual model.
On a project requiring a custom 3D GUI workflow engine, we reached for SkiaSharp. Here is what that looked like in practice.
Why SkiaSharp
SkiaSharp is a .NET binding for Skia, the same 2D graphics library that powers Chrome, Android, and Flutter. It gives you a low-level drawing API covering paths, paints, shaders, and transforms that you can compose into anything.
The key advantage over other approaches is that you are rendering directly to a surface rather than manipulating a DOM or a retained-mode widget tree. This means you control every pixel, every frame, and every interaction. It also means you are responsible for every pixel, every frame, and every interaction.
The Canvas Model
The core of the system is a canvas representing the workflow space, an infinite 2D plane on which nodes, edges, and decorators are positioned using world-space coordinates.
The camera is a transform applied to the canvas before drawing: a combination of translation (pan) and scale (zoom). All input events arrive in screen space and need to be unprojected into world space before any hit testing can happen.
Getting the coordinate system right from the start is critical. Mixing up screen and world coordinates is the most common source of bugs in canvas-based systems and they are painful to track down once the codebase grows.
Node Rendering
Each node in the workflow is a data structure holding position, size, type, connection points, and state that the renderer knows how to draw. The renderer iterates over visible nodes each frame and issues draw calls for each one.
For a node this means: background rectangle, border, icon, label, input and output ports, and any decorators like selection handles or error indicators. SkiaSharp's paint system lets you control fills, strokes, shadows, and gradients per element.
The 3D effect on nodes was achieved through careful use of shadow offsets, subtle gradient fills, and border highlights rather than actual 3D geometry. True 3D in a 2D canvas is expensive and rarely worth it. The visual impression of depth is achievable through lighting simulation at the paint level.
Hit Testing
Because SkiaSharp has no concept of interactive elements (it just draws), you have to implement hit testing yourself. On every mouse event, you project the cursor into world space and check it against the bounding geometry of every node and edge in the viewport.
For a large graph with many nodes, a naïve linear scan is too slow. We partitioned the canvas using a simple grid-based spatial index: only nodes in cells that overlap the cursor region are tested. This kept hit testing fast even with complex graphs.
Handling Edges and Bezier Curves
Connections between nodes are drawn as cubic bezier curves. SkiaSharp's path API makes this straightforward to draw, but hit testing bezier curves is non-trivial since you cannot simply test a bounding box because the curve may pass far from the straight line between its endpoints.
We approximated the curve as a polyline for hit testing purposes: sample points along the bezier at fixed intervals and test proximity to the mouse position. Close enough to feel accurate, cheap enough to run every frame.
Performance
SkiaSharp is fast, but not free. At complex graph sizes with many nodes and edges, frame times can spike if you are not careful. The optimisations that mattered most for us:
- Only redraw when something changes (dirty flagging)
- Cull nodes outside the viewport before issuing any draw calls
- Cache rendered text as bitmaps where the text is static
- Batch draw calls where paints are identical
The resulting system handled graphs well into the hundreds of nodes without visible performance degradation on standard hardware.
When to Use This Approach
SkiaSharp is the right tool when you need a custom interactive canvas and you are already in a .NET environment. It is not the right tool for standard application UI. For anything that can be built with existing component libraries, use them.
The investment is real. You are building your own rendering engine, your own input system, and your own layout logic. But when the product demands it, there is no better way to get there in .NET.
Want to work with us?
Let us talk through your project.