Expand description
§Impeller
Impeller is a 2D vector graphics renderer used in Flutter. Impeller can also be used standalone (without flutter) with its C API. This crate provides a safe rust wrapper around the C API (and also the raw bindings).
§What can it do?
- draw 2D shapes like paths (lines/curvies), rectangles, circles, etc.
- draw AND layout text.
- draw effects like blurs, shadows, color blending etc.
- clipping using any shape.
§Where do I want to use it?
- UI libraries are the best use-case.
- 2D games? easy to embed Impeller into any opengl/vk/metal app.
§Docs
The docs and examples of this crate are good enough for really basic drawing (eg: drawing a rect). But this is nowhere enough for real world usage. They will tell you how to use a object, but now why or where you would use this.
For example, pretty much NONE of the enums are documented. We will slowly improved the situation, but until then, the below resources should help cover the gaps.
§Dart:Ui Docs
Impeller is actually designed for dart/flutter, so, it is the best place to find documentation. Most of the object and function names are same in rust/dart, so you can easily translate the docs from dart to rust. eg: StrokeCap Enum.
It has the best explanation for BlendMode docs with lots of pictures.
DisplayListBuilder
is the combination of PictureRecorder
and Canvas
in dart. DisplayList
is Picture
in dart.
§React Native Skia Docs
Technically, this is documenting Skia. But both Impeller and Skia have such a large overlap in terminology, design and functionality that a lot of learning transfers over seamlessly.
It also provides a lot of images showing the different options (eg: MaskFilters Blur), and a jpeg screenshot is worth a thousand lines of text documentation.
§SkiaSharp docs
Again, skia. But lots of guide-level docs for people who are starting out with Vector graphics.
§Why Impeller?
- Blazingly? Fast - It is used in Flutter, so, you know it will be maintained and improved continuously. The focus is also on consistency to keep everything running smooth.
- Great text rendering AND layout - The rust ecosystem is severely lacking when it comes to text. Impeller is basically production grade when it comes to text.
- Simple Object Model: The object model is very simple and takes like 5 minutes to learn.
- Easy to Embed - Any (opengl/vk/mtl)-based app/game can embed Impeller in less than 10 lines.
- Fast compilation - The only bottleneck is network speed to download the prebuilt libs.
And even that can be avoided with the
cache_libs
feature. - Easy Bindings - The C API is really easy and allows us to “auto-generate” bindings. So, if we are trying to generate lua or wasm bindings, this is a huge QoL feature.
§Why not Impeller?
- Impeller is written in C++. Unfortunately, there’s no pure rust library that can do everything that Impeller/Skia can.
- no support for building from source. We use pre-built static/shared libraries for fast compile times.
- We only support opengl/vk/metal. no d3d and no fallback software renderer.
- Impeller is a light-weight Skia. If you need more powerful features like custom shaders, use Skia-rs.
- Impeller is not thread-safe (It can be in future, but not today).
§How to use the crate
For libraries who are just “using” the API, all you need to do is just use the crate with no features.
The final binary/executable should enable the prebuilt_libs
feature to download the prebuilt libraries from github releases and link them.
NOTE: We use curl to download and tar (or unzip on linux) to extract the archives. linux, mac and windows (10+) will have these by default.
§Features
prebuilt_libs
- Downloads the prebuilt libraries from github releases and links them to your project.static_link
- If enabled, we will link static libraries. only available on linux/windows. All other platforms will need to use shared libraries.debug_static_link
- If enabled, we will use the unstripped static libs with debug info (useful for debugging).cache_libs
- If enabled, we will cache the libs in.impeller_cache
directory inside your project directory (parent oftarget
). Add/.impeller_cache
to.gitignore
for convenience.- caching avoids redownloading after
cargo clean
saving bandwidth and this in turns also makes the builds faster. - You also get to inspect the downloaded archives in the cache to debug any errors.
- caching avoids redownloading after
§Object Model
We have roughly four kinds of objects:
§Immutable Handles : like Arc
- thread-safe -
Sync
andSend
- shallow ref-counted -
Clone
- immutable
eg: Paragraph
, DisplayList
, Texture
, ImageFilter
etc..
§Mutable Handles : like Rc<Cell<T>>
- NOT thread-safe -
!Sync
and!Send
. You create, use and destroy these in the same thread - shallow ref-counted -
Clone
. the cloned handle still refers (mutates) the same object. - mutable - via interior mutability (can be mutated via any cloned handle)
eg: Paint
, DisplayListBuilder
, PathBuilder
, TypographyContext
etc..
A special case here is
Context
that is thread-safe with vk/metal, but not opengl.
§Linear Handles
- NOT thread-safe -
!Sync
and!Send
- NOT ref-counted -
!Clone
- consumed by move (
self
)
eg: Surface
is the only one at this moment.
§Value Types
These are simply your normal plain-old-data c structs and enums. These implement Copy
and have no special semantics.
eg: Rect
, ISize
, Color
, BlendMode
etc..
I try to keep the bindings sound, but graphics programming is just really really unsafe. Especially around the handling of context/surface lifetimes.
§Safety
Objects like textures/contexts are inherently linked to platform resources (like openGL context or vulkan device). So, they must ALL be destroyed before you destroy the underlying window or opengl context.
§API
The API surface is small and easy to understand.
§Context and Surface
The main entry point into the library is the Context struct. You create a context using Context::new_opengl_es.
The context allows you to do only 2 things:
- wrap an opengl framebuffer into a Surface or create a vulkan VkSwapChain to acquire a surface or create a metal Surface (using a drawable).
- create a texture from raw pixel data or adopt an opengl texture
The Surface allows you to draw things on it i.e. Surface::draw_display_list and preset it using Surface::present .
§Display Lists
The DisplayListBuilder is used to record drawing commands like DisplayListBuilder::draw_rect. Then, you call DisplayListBuilder::build to create a DisplayList. DisplayList is an immutable and reusable copy of draw commands that you can execute on to a surface (or add it to another DisplayListBuilder).
The builder maintains an internal “stack” of transformation matrices and clip rects.
You can push and pop them using DisplayListBuilder::save and DisplayListBuilder::restore.
You can use these to scale, translate, rotate and clip your draw commands.
You can use DisplayListBuilder::clip_path to do custom clipping. eg: A star shaped clip or a circle clip (for profile pictures)
§Paint
A Paint is used to configure the details of draw commands.
For example, if you draw a rectangle using DisplayListBuilder::draw_rect, the paint argument controls the details of the command like:
- Color, Transparency, Blend mode
- Draw style (filled rect or a stroked rect), width of the stroke
- Filters for blurs, color tinting etc..
To affect sizes/positions etc., use DisplayListBuilder::transform
§Paragraphs
Unlike simple shapes, text requires a lot of work. Just like DisplayListBuilder is used to record a bunch of commands and then, build an immutable and reusable DisplayList, a ParagraphBuilder is used to record text and then, build an immutable and reusable Paragraph.
- You need a TypographyContext to hold your fonts.
- You need a ParagraphBuilder to record text (with different parts using different styles like colors/fonts/sizes etc..)
- You layout text with ParagraphBuilder using some max width and build a Paragraph
- You draw text with DisplayListBuilder::draw_paragraph.
ParagraphBuilder also maintains an internal stack of text styles. So, you can push a style, add some text which will be rendered using that style and then, pop the style to go back to previous style.
§Textures
You can create a Texture from raw pixel data or adopt an opengl texture.
While you can just draw a texture (image) directly using DisplayListBuilder::draw_texture, you can also use ImageFilter or ColorFilter of the Paint object to apply fancy effects.
§Filters/Sources etc..
The other objects are there mostly to add fancy effects like blur, color tint, color gradients etc.. Read the respective docs for more details.
§LifeCycle
For your average program, you usually have the phases of initialization, event loop and cleanup.
The initialization phase is usually the one where you create the Context and Surface.
- Create a Context using opengl/vk/metal
- Create a Surface from the context (usually wrapping the default framebuffer)
- Create a TypographyContext (Most apps use a fixed set of fonts)
Inside the event loop, you can do things in two ways:
- Create a DisplayListBuilder, record commands and build a DisplayList. draw it on to surface and drop it.
- OTOH, you can create a DisplayListBuilder, record commands and build a DisplayList But, instead of dropping it, just keep it around in a cache to draw multiple times (across different frames). Finally, drop it and rebuilt a new one if something changes (eg: layout or animations)
During the cleanup phase, you drop the all the objects including Surface and Context. Then,you destroy the window (or opengl context/surface).
NOTE: The crate currently has very little API for some structs like matrices or rects. Contributions welcome :)
Structs§
- Color
- Color
Filter - Color filters are functions that take two colors and mix them to produce a single color. This color is then merged with the destination during blending.
- Color
Matrix - A 4x5 matrix using row-major storage used for transforming color values.
- Color
Source - Color sources are functions that generate colors for each texture element covered by a draw call. The colors for each element can be generated using a mathematical function (to produce gradients for example) or sampled from a texture.
- Context
- An Impeller graphics context. Contexts are platform and client-rendering-API specific.
- Display
List - Display lists represent encoded rendering intent (draw commands). These objects are immutable, reusable, thread-safe, and context-agnostic.
- Display
List Builder - Display list builders allow for the incremental creation of display lists.
- Image
Filter - Image filters are functions that are applied regions of a texture to produce a single color. Contrast this with color filters that operate independently on a per-pixel basis. The generated color is then merged with the destination during blending.
- Impeller
Version - The current Impeller API version.
- Mask
Filter - Mask filters are functions that are applied over a shape after it has been drawn but before it has been blended into the final image.
- Paint
- Paints control the behavior of draw calls encoded in a display list.
- Paragraph
- An immutable, fully laid out paragraph.
- Paragraph
Builder - Paragraph builders allow for the creation of fully laid out paragraphs (which themselves are immutable).
- Paragraph
Style - Specified when building a paragraph, paragraph styles are managed in a stack with specify text properties to apply to text that is added to the paragraph builder.
- Path
- Represents a two-dimensional path that is immutable and graphics context agnostic.
- Path
Builder - Path builders allow for the incremental building up of paths.
- Rounding
Radii - Surface
- A surface represents a render target for Impeller to direct the rendering intent specified the form of display lists to.
- Texture
- A reference to a texture whose data is resident on the GPU. These can be referenced in draw calls and paints.
- Typography
Context - Typography contexts allow for the layout and rendering of text.
- VkSwap
Chain - The primary form of WSI when using a Vulkan context, these swapchains use
the
VK_KHR_surface
Vulkan extension.
Enums§
- Blend
Mode - https://api.flutter.dev/flutter/dart-ui/BlendMode.html
- Blur
Style - https://api.flutter.dev/flutter/dart-ui/BlurStyle.html
- Clip
Operation - https://api.flutter.dev/flutter/dart-ui/ClipOp.html
- Color
Space - https://api.flutter.dev/flutter/dart-ui/ColorSpace.html
- Draw
Style - https://api.flutter.dev/flutter/dart-ui/PaintingStyle.html
- Fill
Type - https://api.flutter.dev/flutter/dart-ui/PathFillType.html
- Font
Style - https://api.flutter.dev/flutter/dart-ui/FontStyle.html
- Font
Weight - https://api.flutter.dev/flutter/dart-ui/FontWeight-class.html
- Pixel
Format - Layout of color components of the pixels
- Stroke
Cap - https://api.flutter.dev/flutter/dart-ui/StrokeCap.html
- Stroke
Join - https://api.flutter.dev/flutter/dart-ui/StrokeJoin.html
- Text
Alignment - https://api.flutter.dev/flutter/dart-ui/TextAlign.html
- Text
Direction - https://api.flutter.dev/flutter/dart-ui/TextDirection.html
- Texture
Sampling - The sampling mode to use when drawing a texture.
- Tile
Mode - https://api.flutter.dev/flutter/dart-ui/TileMode.html
Functions§
- flutter_
mip_ count - based on the size, it will calculate a suitable mipcount. This function skips 1x1 mip levels because that’s what flutter does.