blob: 4847d88895da25791eec51bec67ab2306d80a8a0 [file] [log] [blame] [view] [edit]
# 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](./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 via
**`SkSurfaces::Raster(...)`**, allocates and owns pixel memory via an `SkPixelRef`. The
`SkSurface` manages an `SkBitmap` using this memory and provides an `SkCanvas` for drawing.
- **`SkImage`**: Represents an immutable snapshot of pixel data. It can be created from an
`SkSurface` (via `surface->makeImageSnapshot()`), from an `SkBitmap`, or from encoded data
(e.g., a PNG file). On the CPU backend, this is implemented by **`SkImage_Raster`**, which holds
an `SkBitmap` that in turn points to the underlying pixels via an `SkPixelRef`. Because it is
immutable, it can be cached and shared safely across threads. It can be drawn to a canvas via
`canvas->drawImage(...)`.
- **`SkCanvas`**: The primary drawing interface with methods like `drawPath`, `drawImage`, etc. The
user provides geometry (e.g., `SkPath`), images (`SkImage`), styling (`SkPaint`), and font
information (`SkFont`) to the canvas. The `SkCanvas` holds the current transformation matrix
and clip. When a draw call is made, the `SkCanvas` forwards all the information to an internal
`SkDevice` for 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 an `SkPathRef`.
- **`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. `SkPathRef` objects are
always **heap-allocated** and managed by `sk_sp`. Internally, `SkPathRef` uses
`skia_private::STArray` for its geometry data storage. This `STArray` is a small-object optimized
array, meaning that for small paths, the geometry data is stored directly within the `STArray`'s
internal buffer (part of the heap-allocated `SkPathRef` object). For larger paths, the `STArray`
dynamically 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 an `SkTypeface` by 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 an `SkTypeface` plus 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 from `SkMaskFilter::MakeBlur`.
- **`SkPathEffect`**: Modifies the geometry of a shape before it is drawn. For example,
`SkPathEffect::MakeDash` creates 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 traditional
`SkBlendMode` enum. The blending logic is executed as a stage in the `SkRasterPipeline`.
- **`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 calls `blitter->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 calls
`blitter->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 the `SkRasterPipeline` with the exact memory addresses
for reading and writing.
- **`SkBitmap`**: Held by `SkBitmapDevice`, it pairs an `SkImageInfo` with the pixel storage.
- **`SkPixelRef`**: A smart pointer that owns the heap-allocated pixel memory, originally created
by the `SkSurface`.
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.