Donner
C++20 SVG rendering library
|
Donner uses a data-oriented Entity Component System (ECS) design pattern to store and manipulate the SVG document. This is a common pattern in game development, since it optimizes for performance via cache-friendly data structures and parallelism.
Entities are the primary data structure in ECS. They are simply a unique identifier for a set of components. In Donner, entities are represented by a 32-bit unsigned integer, using a typedef for entt::entity
.
An entity is created by calling registry.create()
without any components. Here is how the Entity
is created for SVG elements.
Components are the data associated with an entity, which are efficiently stored to allow for both fast lookup and iteration.
Each component may only have one instance per entity, and each entity has an independent list of components. For example, it's easy to add a component to an entity to tag them or add data.
For a real-world example, here is how the ViewboxComponent
is created for SVG elements.
The same system is used to implement the tree structure, where a TreeComponent is added to each entity that contains Entity
references to its parent and children.
Systems are singletons within the Registry
, and are used to hold global state and operate on the component-system. Donner calls these contexts, for example SVGDocumentContext and RenderingContext.
There are also systems that are stateless, such as LayoutSystem, which is instantiated on-demand to manipulate the ECS state.
For example:
A list of systems can be found at ECS Systems.
In Donner, each Entity corresponds to a single SVGElement.
Donner groups its components into categories, based on transformations that occur as the document is parsed and rendered.
The rendering pipeline performs a series of operations:
Each operation saves its state through a set of components. For example, during Style propagation in StyleSystem::computeAllStyles, these transformations occur:
User component | Styled component |
---|---|
StyleComponent | ComputedStyleComponent |
SizedElementComponent | ComputedSizedElementComponent |
While StyleComponent stores user-provided style information, it does not apply the inherited properties or the CSS stylesheet. ComputedStyleComponent stores the final style information after applying all the rules.
This continues for the Computed tree and Render tree.
PathComponent and shape-specific components such as RectComponent store the user-provided shape information. These are transformed at the Computed tree stage into ComputedPathComponent, which contains the PathSpline for the shape.
This step must happen after the styling phase to ensure SVG2 presentation attributes are properly propagated.
For example, shape properties can be specified entirely in CSS:
This is a new feature in SVG2 which strongly influences this pipeline.
The details above have been simplified, see RenderingContext as the source of truth and full list of operations.
The final transformation occurs to instantiate the render tree, which occurs within RenderingContext::instantiateRenderTree.
This step traverses the tree and produces a sorted list of RenderingInstanceComponent corresponding to the draw order.
This list is then processed by donner::svg::RendererSkia to produce the final output.
On top of the ECS model the user-facing API is implemented as a thin wrapper. For example, SVGPathElement proxies calls to the components layer with minimal logic:
This enables API objects to be zero-cost and stateless. The only object held within these objects is the EntityHandle
, which is a lightweight wrapper around the Entity
and Registry
(~16 bytes with alignment).