Skia CPU Backend Architecture
This document outlines the major pieces of Skia's CPU backend. The CPU backend is responsible for rendering graphics on the CPU, without the use of a GPU.
Background and Definitions
- Bitmap: A grid of pixels representing an image, where each pixel has a color. This is the digital canvas Skia's CPU backend paints on. Common image file formats like PNG and JPEG store bitmap data.
- Blend Mode: A rule that defines how a source color (the one being drawn) is mathematically combined with a destination color (the one already on the canvas).
- Blitter: A component that writes the final pixel colors from the rendering pipeline into the destination bitmap. It's the "hand" that actually puts the colored pixels onto the canvas.
- Clip: A region that restricts drawing. Anything drawn outside the clip is not visible.
- Coverage Mask: An array of values (typically 8-bit alpha) that represents how much a geometric shape covers each pixel. A value of 255 means fully covered, 0 means not covered, and intermediate values represent partial coverage for anti-aliasing.
- Hairline: The thinnest visual line, typically one device pixel wide. A stroke width of 0 in a paint is interpreted as a hairline. Hairlines do not scale with the CTM.
- Matrix: A mathematical tool used to transform (translate, scale, rotate, skew) what is being drawn.
- Paint: An object holding stylistic information for drawing, like color, stroke width, and effects.
- Path: A sequence of lines and curves that describe a shape. Skia uses quadratic, cubic, and conic Bézier curves to create smooth, scalable curves. A quadratic curve has one control point, while a cubic curve has two for more complex shapes. A conic curve represents perfect circles etc using one control point and one weight.
- Rasterization: Converting vector graphics (math-based shapes) into a grid of pixels (a raster image) for display.
- Scanline: A single horizontal row of pixels. Complex shapes are often rendered one scanline at a time.
- Shader: A program that calculates the color of a pixel. Shaders can produce solid colors, gradients, textures, or procedural patterns.
- Tiling: An optimization strategy where a large drawing operation is broken into a series of smaller, tile-sized operations to keep memory usage (e.g., for intermediate buffers) bounded.
- Winding Number: An algorithm used to determine if a point is inside a complex or self-intersecting path. Imagine drawing a ray from the point in any fixed direction to infinity. The winding number is the count of how many times the path crosses the ray in one direction (e.g., clockwise) minus the number of times it crosses in the other direction (counter-clockwise). For a non-zero winding rule, any point with a non-zero winding number is considered "inside" the path. This correctly handles shapes with holes and overlapping sections.
Data Flow Walkthrough
The following sections describe the journey of a single drawing command (e.g.,
canvas.drawPath(...)) through the CPU backend, from the public API call to modifying raw pixel
memory.
See CPU.dot for a supplementary diagram.
1. The API Layer
The journey begins with the user-facing drawing API.
SkSurface: Represents the drawing destination. A CPU-backed surface, created viaSkSurfaces::Raster(...), allocates and owns pixel memory via anSkPixelRef. TheSkSurfacemanages anSkBitmapusing this memory and provides anSkCanvasfor drawing.SkImage: Represents an immutable snapshot of pixel data. It can be created from anSkSurface(viasurface->makeImageSnapshot()), from anSkBitmap, or from encoded data (e.g., a PNG file). On the CPU backend, this is implemented bySkImage_Raster, which holds anSkBitmapthat in turn points to the underlying pixels via anSkPixelRef. Because it is immutable, it can be cached and shared safely across threads. It can be drawn to a canvas viacanvas->drawImage(...).SkCanvas: The primary drawing interface with methods likedrawPath,drawImage, etc. The user provides geometry (e.g.,SkPath), images (SkImage), styling (SkPaint), and font information (SkFont) to the canvas. TheSkCanvasholds the current transformation matrix and clip. When a draw call is made, theSkCanvasforwards all the information to an internalSkDevicefor rendering.SkPath: A high-level object representing vector geometry (lines, curves). It is a lightweight, copy-on-write object that holds a shared pointer (sk_sp) to anSkPathRef.SkPathRef: The reference-counted object that actually stores the raw path data: arrays of points, verbs (move, line, quad, conic, cubic, close), and conic weights.SkPathRefobjects are always heap-allocated and managed bysk_sp. Internally,SkPathRefusesskia_private::STArrayfor its geometry data storage. ThisSTArrayis a small-object optimized array, meaning that for small paths, the geometry data is stored directly within theSTArray's internal buffer (part of the heap-allocatedSkPathRefobject). For larger paths, theSTArraydynamically allocates additional memory on the heap to store the geometry data.
Text Rendering
Drawing text is a specialized, but common, case. The process involves three key classes:
SkFontMgr: A top-level object that discovers and manages the fonts installed on the system. It is used to create anSkTypefaceby matching a font family name (e.g., "Roboto") or by loading font data directly from a file or stream. It also handles "fallback", which allows a user to find the data to draw a glyph from a collection of fonts and ordering heuristics (e.g. use this font for Latin characters and this font for emoji).SkTypeface: An immutable object representing the raw data of a single font face (e.g., "Roboto Bold"). It typically contains the vector paths for each glyph, but also supports bitmap glyphs. Vector glyphs are rasterized and stored in a glyph cache (SkStrikeCache).SkFont: A lightweight object that holds a reference to anSkTypefaceplus styling attributes like text size, scale, and skew.
When a user calls a method like canvas.drawSimpleText(...), they provide the text and an SkFont
to the SkCanvas. The backend then uses the SkFont to look up the individual glyphs from the
SkTypeface. Each glyph is subsequently treated as a standard SkPath to be rendered.
Note on Text Layout: Core Skia handles rendering individual glyphs, but it does not perform
complex text layout (e.g., line breaking, justification, or bidirectional text). For rich text
layout, Skia provides the SkParagraph module, which is a higher-level library built on top
of Skia's core components.
Paint Configuration
The SkPaint object holds a suite of optional components that control the styling and
pixel-processing pipeline.
SkShader: Generates the source color for the geometry. If no shader is present, the paint's color is used. Shaders can produce gradients, bitmap patterns, or procedural colors.SkColorFilter: Modifies the source color produced by the shader or paint. Common uses include tinting or applying a color matrix for color correction.SkMaskFilter: Operates on the shape's coverage mask, not its color. Its most common use is blurring the shape's edges, such as with a Gaussian blur fromSkMaskFilter::MakeBlur.SkPathEffect: Modifies the geometry of a shape before it is drawn. For example,SkPathEffect::MakeDashcreates an effect that turns solid lines into dashed lines. This happens before rasterization.SkBlender: Controls how the source color (from the shader) is blended with the destination color (already on the canvas). It is a more powerful, programmable version of the traditionalSkBlendModeenum. The blending logic is executed as a stage in theSkRasterPipeline.SkImageFilter: Applies a complex, multi-pass effect to the output of a drawing operation. Unlike other effects, an image filter can operate on the entire result of a draw as a texture. This often requires allocating a temporary, offscreen layer. For example, a drop shadow filter might draw the shape into a layer, blur it, offset it, and then draw the original shape again on top of the blurred shadow.
2. The Device Layer: SkBitmapDevice
For the CPU backend, the SkCanvas forwards draw calls to an SkBitmapDevice. This object is
the concrete target for all CPU-based drawing. It manages the destination SkBitmap and
initiates rendering by creating and dispatching to an SkDraw object.
A Note on Clipping: The device manages an SkRasterClipStack corresponding to the canvas's
save()/restore() calls. For each draw, it resolves this stack into a single SkRasterClip
and passes it to SkDraw, which is stateless regarding the clip.
A Note on Tiling: For large or complex draws, the device may use tiling. It breaks the operation into smaller, tile-sized chunks, adjusting the clip for each. This bounds the memory required for intermediate buffers by invoking the draw process once per tile.
3. The Orchestrator: SkDraw
The SkBitmapDevice creates an SkDraw object for each primitive. SkDraw orchestrates a
single draw, bundling the geometry (SkPath), styling (SkPaint), transform, clip, and
destination SkPixmap. It uses SkScan to convert the vector shape into raster operations.
4. The Rasterizer: SkScan
SkDraw passes the SkPath to SkScan, the core rasterizer. SkScan converts the vector
path into horizontal scanlines, calculating a coverage mask (alpha values) for each. It is
unaware of color or effects. SkDraw provides SkScan with a SkBlitter, which SkScan
invokes for each scanline to render the coverage data.
A Note on Scan Converters: The isAntiAlias() flag on the SkPaint selects the scan
converter:
- Aliased (
SkScan_Path.cpp): When anti-aliasing (AA) is off, it produces a binary (0% or 100%) coverage mask based on whether pixel centers are inside the path, resulting in hard edges. For each horizontal run of covered pixels, it callsblitter->blitH(x, y, width). - Analytic Anti-Aliased (
SkScan_AAAPath.cpp): When AA is on, it analytically calculates the exact geometric area of intersection with each pixel, producing fractional coverage values for smooth edges. For each horizontal run of partially-covered pixels, it callsblitter->blitAntiH(x, y, alphas, runs).
5. The Pixel Writer: SkRasterPipelineBlitter
The SkRasterPipelineBlitter is the primary SkBlitter in the CPU backend. It implements the
SkBlitter interface, but instead of writing pixels directly, it translates calls like blitH
or blitRect from SkScan into an execution of its internal SkRasterPipeline. It configures
the pipeline with the correct coverage information from the blitter call and then runs the
pipeline to compute and write the final pixel colors to those lines.
6. The Workhorse: SkRasterPipeline
The SkRasterPipeline calculates final pixel colors by chaining together single-purpose
stages. For example, a draw might use stages for loading coverage (load_a8), setting a
source color (uniform_color), loading the destination, blending (srcover), and storing the
result (store_8888). This stage-based design avoids a combinatorial explosion of functions.
The pipeline is assembled into a sequence of pre-compiled "stages", which are executed in a loop
over the covered area. These functions use SIMD (e.g., SSE, NEON) to process multiple pixels
at once for high performance. The pipeline gets memory layout information from an SkPixmap to
load and store pixels.
7. The Pixel Memory View: SkPixmap, SkBitmap, and SkPixelRef
This is the final stop where pixels are modified.
SkPixmap: A lightweight object with a pointer to pixel memory and its metadata (dimensions, color type). It provides theSkRasterPipelinewith the exact memory addresses for reading and writing.SkBitmap: Held bySkBitmapDevice, it pairs anSkImageInfowith the pixel storage.SkPixelRef: A smart pointer that owns the heap-allocated pixel memory, originally created by theSkSurface.
The SkRasterPipeline's final stage uses the address from the SkPixmap to write the new color
into the memory owned by the SkPixelRef, completing the draw.