Skip to content

About

A GPU accelerated 2D vector graphics engine

Resources

Stars

349 stars

Watchers

3 watching

Forks

Repository files navigation

Rasterizer

A GPU vector graphics renderer for Apple platforms, 2–5× faster than Vello and Skia on real-world documents.

Platforms Swift Package License: MIT

Rasterizer turns vector paths into reference-quality anti-aliased pixels with Metal. It draws maps, engineering drawings and illustrations with hundreds of thousands of paths at interactive frame rates, and loads SVG and PDF files directly.

Screenshot

Performance

Wall time per frame in milliseconds, fitting each file to a 2048×2048 view on a MacBook Air with an M4. Lower is better.

File Draws Rasterizer Vello Skia Graphite Skia Ganesh
tiger 301 1.35 1.43 0.86 3.56
london-rail 10,643 1.76 3.90 7.83 8.36
Paris 30K 50,791 4.94 28.09 34.58 66.08
spoorkaart 84,287 9.87 44.34 66.27 327.85
6000-DRG 414,657 20.04 129.98 224.56 233.40
pathological 580,473 18.14 106.74 267.01 324.32

Across all 20 test files, Vello takes 2.2× as long as Rasterizer, Skia Graphite 2.4× and Skia Ganesh 5.0× (geometric means). The advantage grows with the number of paths: on the 7 documents with more than 40,000 draws, Vello takes 2–6.5× as long and Skia 6–33×. Small scenes, under about 1,000 draws, take under 2 ms in Rasterizer, Vello and Skia Graphite, and Graphite is fastest on 5 of the 6. On an M4 Max, with about four times as many GPU cores, Rasterizer's lead is wider.

The benchmark is a single self-contained script that builds every library and replays the same geometry in each. See Benchmarks for the method, all the results and the caveats.

Features

  • Fills with even-odd and non-zero fill rules, following the PostScript model
  • Strokes with butt, square or round caps, miter or round joins, and widths in user or device space
  • Dashes, by making a dashed copy of a path
  • Linear and radial gradients, and images
  • Clipping to rectangles, or to anti-aliased clip paths with either fill rule
  • Blend modes: all of Core Graphics' separable and non-separable modes
  • Text laid out by Core Text
  • SVG import via NanoSVG
  • PDF import, with a parallel content-stream parser that spreads each page across all CPU cores
  • Editable scenes: change a draw's transform, color or width in place, without rebuilding the scene
  • Up to 120Hz on ProMotion displays
  • A CPU reference renderer using Core Graphics, for comparison

Quick start

Rasterizer is a Swift package, with its sources in Package/Sources, and three libraries:

Library What it is
RasterizerCpp The C++17 renderer core
RasterizerObjC The API used from Swift and Objective-C, and RasterizerView, a Metal-backed UIView or NSView
RasterizerSwift Swift helpers, such as fitting a scene to a view

In Xcode, choose File ▸ Add Package Dependencies…, enter https://github.com/mindbrix/Rasterizer, choose Up to Next Minor Version from 0.1.0, and link RasterizerObjC, plus RasterizerSwift if you want the helpers. Or, in a Package.swift:

dependencies: [
    .package(url: "https://github.com/mindbrix/Rasterizer", .upToNextMinor(from: "0.1.0")),
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "RasterizerObjC", package: "Rasterizer"),
        .product(name: "RasterizerSwift", package: "Rasterizer"),
    ]),
]

Until 1.0.0, minor versions may make breaking changes, so this takes fixes (0.1.x) but not the next minor version. See the changelog for what's in each release. To work on Rasterizer alongside your app, add your clone with Add Local… instead.

A RasterizerView asks its delegate each frame whether to redraw, and for the list of scenes to draw:

import UIKit
import RasterizerObjC

class ViewController: UIViewController, RASceneListDelegate {
    let list = RASceneList()
    var redraw = true

    override func loadView() {
        let view = RasterizerView()
        view.listDelegate = self
        self.view = view
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        let scene = RAScene()
        let orange = RAPaint(red: 1, green: 0.45, blue: 0.1, alpha: 1)
        scene.addFill(RAPath(ellipse: CGRect(x: 40, y: 40, width: 240, height: 240)), ctm: .identity, color: orange, evenOdd: false)
        scene.addStroke(RAPath(rect: CGRect(x: 20, y: 20, width: 280, height: 280)), ctm: .identity,
                        color: RAPaint(gray: 0.1, alpha: 1), width: 8, capStyle: .capRound, joinStyle: .joinRound)
        list.add(scene, ctm: .identity, clip: .zero)
        list.clearColor = RAPaint(gray: 0.95, alpha: 1)
    }

    // RasterizerView asks every frame, & draws when this returns true
    func shouldRedraw(atTime time: Double, scale: Double, width: Double, height: Double) -> Bool {
        redraw
    }

    func getListAtTime(_ time: Double, scale: Double, width: Double, height: Double) -> RASceneList {
        redraw = false
        return list
    }
}

On macOS, it's the same code with NSViewController.

Load an SVG or a PDF page, and fit it to the view:

import RasterizerSwift

let scene = RAScene()
let ctm = scene.addSvg(from: svg)   // Or scene.addPdf(from: pdf, pageIndex: 0)
let list = RASceneList()
list.add(scene, ctm: ctm, clip: .zero)
list.ctm = view.bounds.fitTransform(b: list.bounds)     // Fit it to the view

Edit draws in place, for example to animate them:

scene.updateDraws(in: NSRange(location: 0, length: scene.count)) { index, draw in
    draw.color = RAPaint(hue: Double(index) / Double(scene.count), saturation: 0.8, value: 0.9, alpha: 1)
    return true     // Changed, so it's redrawn
}

Changing a draw's transform, color or stroke width is cheap. Changing its path, its visibility, or turning a stroke into a fill means the scene is prepared again.

Demo apps

Both build out of the box, as all dependencies are included.

iOS

Open RasterizeriOS/RasterizeriOS.xcodeproj.

  • Tap to step through the bundled SVGs and your own files.
  • Drag and pinch to pan and zoom.
  • The folder button imports SVG and PDF files, which are kept between launches. The trash button removes the one showing.
  • PDFs with more than one page get a page stepper.
  • The grid button animates every draw into a grid of cells, and back.
  • Files can also be sent to the app with Share in the Files app.

macOS

Open Rasterizer.xcworkspace and run the Run Rasterizer scheme for the best performance, or Debug Rasterizer for debugging.

  • Open SVG and PDF files, and use the - and + keys to change PDF page.
  • Drag and zoom or rotate with the trackpad or mouse. Hold Shift to zoom or rotate around the pointer.
  • The HUD, top left, lists the keys. 0 switches between Rasterizer and Core Graphics, O shows path outlines, and T shows an animated clock built from text.
  • Set the font with the fonts panel (⌘T).

How it works

Path objects follow the PostScript model. Scene objects group paths with their draw parameters: color, transform, stroke width, flags, and an optional clip. SceneList objects group scenes, each with its own transform and clip, for rendering.

  • Filled paths are rasterized in two stages: first to a float mask buffer, then to the color buffer.
  • Small screen-space fills, such as glyphs, are the fastest case, as their path geometry can be copied straight into GPU memory.
  • Larger fills use a fat scanlines algorithm on clipped, device-space geometry.
  • Pixel coverage is calculated with a novel windowed inverse-lerp algorithm.
  • Strokes are rasterized straight to the color buffer, using GPU triangulation.
  • Quadratic Bézier curves are solved on the GPU, so curve geometry can stay coarse.
  • Clip paths are rendered to an anti-aliased coverage mask within the same render pass.
  • The CPU stages use simple batch parallelism.

The GPU holds no state. Each frame is written afresh to double-buffered shared memory. In practice, copying is cheaper than managing GPU-resident paths in a parallel context.

Background

Inspired by my love of Adobe Flash, I started work on a GPU-accelerated 2D vector graphics engine for the original iPhone, and then the Mac. Three iterations and many years later, Rasterizer is the result.

The long gestation came from endlessly iterating over one core problem: turning vector paths into reference-quality anti-aliased pixels efficiently. Some of the solutions may extend the state of the art.

Credits

Many thanks to the creators of:

License

Rasterizer is licensed under the MIT license. NanoSVG and xxHash keep their own licenses.

Tips

You can show appreciation for my work here.

About

A GPU accelerated 2D vector graphics engine

Resources

Stars

349 stars

Watchers

3 watching

Forks

Used by

Contributors

Languages