← Trevor Waldorf

Flatland

Canvas Performance Wins

Summary

The flatland “mats”–the primary drafting and layout workspaces, as well as pattern piece representations within the rest of the interface–are all rendered using Canvas. Making Canvas crisp, responsive, and silky smooth is of critical importance for the usability and usefulness of Flatland. To that end, using smarter buffers and managing vectors has improved performance from ~15FPS to 40-60 on my older Macbook in Safari, Chrome, and Firefox.

[ diagram of buffers and their indices TBD ]

Indexed offscreen buffers

Each geometry is rendered to its own buffer. When adding infinite pan and zoom I changed all geometry representations to be normalized by default. This means all types of geometry, whether an incomplete bezier path or a pencil mark or a complete boundary-represented shape has a local coordinate system and a place within the global 1x scale coordinate system. The local coordinate system also makes it trivial to create and index a new offscreen canvas buffer for each geometry.

When rendering (60 times a second as a frequency target), the render loop looks for geometries with positions nearby the current canvas viewport and applies those buffers to the main canvas at the global 1x coordinate. Only if the specific geometry buffer is outdated compared to the last state does the renderer have to go through and complete vector drawing operations like arcTo, lineTo, etc. I believe this means that almost no operations are being done on the CPU during most render loops and my evidence for this is that both CPU time and memory footprint is mostly gone when comparing the resource profiler before and after buffers.

With the extra CPU availability, Flatland can render better action previews and expose more affordances–things like more accurate bezier placement previews, path intersections and path snapping, etc.

Acceleration structures

In the future, as more geometries are present and performance degrades for preview operations, I’ll use a space partitioning structure like a quad tree to cut out the number of points that must be checked by the CPU. However, the fact that each geometry has a global position already allows a cheap spatial partition just by checking the viewport radius against the geometry position and skipping geometries which are excessively far away.

There is also an opportunity to use a level-of-detail system with tiles to optimize panning when zoomed way out, but that is for another blog post.

[diagram of placements and their radii] [cute symbol design system in grid]

Symbol buffers

Flatland has to draw a lot of circles, and while arcTo() isn’t so expensive, once you finish setting up the styles and strokes you find that quite a bit of state has been written to the canvas for a single circle–of which there may be hundreds for a single geometry, of which there also may be hundreds all selected at once! So I have offscreen canvas buffers for each of the common symbols and icons like points, selected points, moving points, temporary points, old points, snap points, etc., which allows Flatland to simply apply the symbol buffer to the right canvas and continue with the render loop. Even more CPU saved! Thanks GPU.

[vector pool]

Prefer new vectors over canvas state changes

Working on soft body simulation made me very wary of creating new vectors within a render loop. Fortunately Three.js handles memory pooling for its vectors which means that it is much faster to create a few vectors within render loops rather than try to reset canvas state and rely on a fresh canvas each time. I also hide quite a bit of vector work inside of click actions–it feels better for the user to have 50ms of work hidden in an animation in the moment following a mouse click than it is to have a 50ms hang in the midst of panning around or editing a shape detail. I find it often feels more convincing if the click takes a second unless it is an information-gathering action (like checking alignment or exposing properties).

All of these and many more contribute to a more responsive feel in the Flatland editor. In short, move more off the CPU, don’t re-do work that involves changing the state of the canvas unless it is reapplication of buffers. See also Boris Smus’ piece from a long time ago: Canvas Performance.

Home