| # 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. |