Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Vello Parley Rendering

ASecurity

Render GPU-accelerated 2D vector graphics (bezier paths, fills, strokes, gradients) and high-quality shaped text in Rust with the Linebender stack: Vello (compute-based vector renderer on wgpu) + Parley (text shaping/layout) + kurbo (geometry) + peniko (brushes/color). Covers winit+wgpu surface setup, building a Scene every frame, feeding Parley glyph runs into Vello, the retained-vs-immediate question, vsync/frame pacing, and the version-pinning minefield between vello/wgpu/parley. Activate ...

2 stars
0 votes
0 copies
0 views
Added 9/24/2026
toolsrustgobashnodeawsapifrontendbackend

Works with

api

Security Analysis

A100/100

Pro scans all 8 files and shows the line behind each finding

Scanned 9/24/2026

$npx -y skills add curiositech/port-daddy --skill vello-parley-rendering --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Vello Parley Rendering?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Vello Parley Rendering
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/curiositech-vello-parley-rendering-port-daddy/badge)](https://www.skillsdirectory.com/skills/curiositech-vello-parley-rendering-port-daddy)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
license: Apache-2.0
name: vello-parley-rendering
description: |
  Render GPU-accelerated 2D vector graphics (bezier paths, fills, strokes,
  gradients) and high-quality shaped text in Rust with the Linebender stack:
  Vello (compute-based vector renderer on wgpu) + Parley (text shaping/layout)
  + kurbo (geometry) + peniko (brushes/color). Covers winit+wgpu surface setup,
  building a Scene every frame, feeding Parley glyph runs into Vello, the
  retained-vs-immediate question, vsync/frame pacing, and the version-pinning
  minefield between vello/wgpu/parley. Activate on: "Vello", "Parley",
  "Linebender", "GPU vector rendering in Rust", "render text with wgpu",
  "kurbo bezier", "peniko brush", "draw_glyphs", "Scene::fill", "custom 2D GPU
  canvas". NOT for: 3D rendering (use wgpu directly or bevy), Metal shader
  authoring (use metal-text-pipeline / metal-shader-expert), web canvas
  (use Canvas2D/WebGL), or native OS widgets (use gpui/egui/iced).
allowed-tools: Read,Write,Edit,Bash,Glob,Grep,WebSearch,WebFetch
metadata:
  category: Frontend & UI
  tags:
    - vello
    - parley
    - wgpu
    - rust
    - gpu
    - vector-graphics
    - text-rendering
    - linebender
    - kurbo
    - peniko
  pairs-with:
    - skill: metal-text-pipeline
      reason: The bare-metal counterpart — what Vello hides, and when pure Metal is worth it.
    - skill: gpui-shaders
      reason: For custom GPU shader work inside gpui, when you're below Vello's abstraction on a different UI stack.
    - skill: rust-gpui-motion
      reason: Widget-tree animation timing/easing patterns, for the gpui side of a decision between retained widgets and a bespoke Vello canvas.
    - skill: rust-app-distribution
      reason: Shipping the signed/notarized desktop binary that hosts the renderer.
  provenance:
    kind: first-party
    owners: [port-daddy]
  authorship:
    maintainers: [port-daddy]
  io-contract:
    kind: deliverable
    consumes:
      - kind: bespoke-2d-canvas-requirement
        format: markdown
      - kind: vello-render-dependency-plan
        format: json
    produces:
      - kind: render-loop-implementation-guide
        format: markdown
      - kind: render-plan-audit-report
        format: json
---

# Vello + Parley GPU Vector Rendering (Rust)

Vello is a **compute-shader 2D vector renderer**: you hand it a `Scene` (a list
of filled/stroked paths, images, glyph runs) and it rasterizes the whole thing
on the GPU in a few compute passes. Parley is the **text** half: it shapes and
lays out Unicode runs (via HarfRust + Swash/Skrifa) and yields positioned
glyphs you stream straight into the same Vello `Scene`. Together they give you a
**pixel-perfect, fully custom 2D canvas** with no widget model in the way —
which is the entire reason you'd reach for them over gpui/egui.

This skill is the worked, version-pinned, gotcha-annotated path from "blank
window" to "anti-aliased beziers + crisp shaped text at 120fps." The worked
example is real: `core/pd-timeline-proto/` in this repo renders a scrubable
timeline (tracks, event markers, causal bezier threads, a playhead) this way.

## When To Use

- You need a **bespoke 2D visualization** (timeline, node graph, waveform, map,
  diagram) where every pixel is yours and a retained widget tree gets in the way.
- You want **resolution-independent vector** output (smooth zoom, crisp on HiDPI)
  without managing your own tessellation/rasterization.
- You need **high-quality text** (real shaping, ligatures, variable fonts, RTL)
  rather than a bitmap-font hack.
- You're already on `wgpu` and want a 2D layer that composes with your 3D, or you
  want one renderer that runs on Metal/Vulkan/DX12/GL from one codebase.

## NOT For

- 3D scenes, meshes, lighting → use `wgpu` directly, or `bevy`.
- Authoring the shaders themselves → `metal-shader-expert` (MSL) or wgpu WGSL.
- The "should I even leave gpui?" decision and pure-Metal costs →
  `metal-text-pipeline`.
- Native OS controls, accessibility trees, text input widgets → `gpui`, `egui`,
  `iced`. Vello draws; it does not give you a button or a focus ring.
- Web targets → Canvas2D / WebGL / WebGPU bindings, not native Vello (Vello does
  have a web path, but if web is your primary target start there).

## Decision Points

### 1. Pick the stack layer you actually need
```
What are you drawing?
├─ Arbitrary 2D vector + text, custom layout
│   └─ Vello (Scene) + Parley (text) + kurbo (geom) + peniko (brush)   ← this skill
├─ 2D but you want widgets/layout/events handed to you
│   └─ Xilem (Linebender's UI framework, built on Vello+Parley) or egui/iced
├─ Just static SVG → raster once
│   └─ resvg / usvg (CPU) is simpler; skip the GPU entirely
└─ 3D / GPGPU
    └─ wgpu directly; Vello is the wrong tool
```

### 2. Windowing + GPU surface
```
Need a window?
├─ Cross-platform desktop  → winit 0.30 (ApplicationHandler model)
│   └─ GPU via wgpu; Vello ships `vello::util::RenderContext` to manage
│      Instance/Adapter/Device/Surface for you — USE IT, don't hand-roll wgpu init
├─ Embedding in an existing wgpu app
│   └─ Construct `Renderer` against your existing Device; render to your texture
└─ Headless render-to-PNG (tests, thumbnails, CI)
    └─ Vello can render to a wgpu texture you read back; no surface needed
```

### 3. Retained vs immediate (Vello's real model)
Vello is **immediate from your side, retained on the GPU side**. You rebuild a
`Scene` each frame with `scene.reset()` then re-`fill`/`stroke`/`draw_glyphs`.
This sounds wasteful; it isn't — scene encoding is CPU-cheap (microseconds for
thousands of primitives) and the GPU does the heavy raster.
```
Rebuilding the scene every frame is the NORMAL pattern.
├─ Static content, rare changes → still rebuild; it's fine. Don't over-engineer caching.
├─ Huge scenes (100k+ paths) → consider sub-scenes / append() of cached fragments
└─ NEVER try to "diff" or mutate a Scene in place — that's not the API.
```

### 4. Anti-aliasing config
```
RendererOptions.antialiasing_support + RenderParams.antialiasing_method must AGREE.
├─ Cheapest, great for UI/vector   → AaSupport::area_only() + AaConfig::Area
├─ MSAA8/MSAA16 → heavier, only if Area shows artifacts on thin strokes
└─ Mismatch (support says Area, params ask Msaa16) → panic at render time.
```

### 5. Frame pacing
```
PresentMode?
├─ AutoVsync (default, recommended) → tear-free, caps to display (60/120 ProMotion)
├─ AutoNoVsync → uncapped; use ONLY to benchmark raw GPU headroom, not to ship
└─ On ProMotion you get 120fps free IF you request a redraw every frame.
    winit only redraws on request — call window.request_redraw() to keep animating.
```

## Failure Modes

### 1. Version skew between vello / wgpu / parley (the #1 time sink)
- **Symptoms**: `expected wgpu::Device, found wgpu::Device` (two wgpu majors in
  the tree); `RenderSurface` field/method not found; trait bound errors on
  `Brush`.
- **Root cause**: `vello` re-exports a specific `wgpu` major. If your `Cargo.toml`
  pulls a different `wgpu`, you have two incompatible `wgpu` crates.
- **Fix**: Let `vello` own the wgpu version. Either depend on `vello` only and use
  `vello::wgpu::…`, or pin your direct `wgpu` to **exactly** the major vello uses
  (vello 0.3 → wgpu 22; check `cargo tree -i wgpu`). Same discipline for parley:
  vello 0.3 pairs with parley 0.2 / peniko 0.2 / kurbo 0.11.
- **Detect**: `cargo tree -d` (the `-d` finds duplicate crates). Two `wgpu`
  entries = you will fail to compile or get cryptic type errors.

### 2. Glyph normalized-coords type mismatch
- **Symptoms**: `expected &[F2Dot14], found &[i16]` when calling
  `.normalized_coords(...)` on `draw_glyphs`.
- **Root cause**: Parley yields variation coords as `&[i16]`; Vello/Skrifa wants
  `&[NormalizedCoord]` (a `#[repr(transparent)]` wrapper over the same i16 bits,
  F2Dot14).
- **Fix**: reinterpret the slice — they are layout-identical:
  `let coords: &[skrifa::raw::types::F2Dot14] = unsafe { std::slice::from_raw_parts(raw.as_ptr().cast(), raw.len()) };`
  (Both are 2-byte; this is the idiom the parley examples use.)

### 3. API churn across minor versions
- **Symptoms**: `Layout::align` "takes 2 arguments but 3 supplied";
  `with_alpha_factor` deprecated; `RenderContext::create_surface` signature drift.
- **Root cause**: Pre-1.0 Linebender crates move fast.
- **Fix**: pin exact minors and read THAT version's `examples/` (clone the tagged
  repo or open docs.rs at the pinned version). Don't trust a blog post for a
  different minor. `peniko::Color::with_alpha_factor` → `multiply_alpha`;
  `Layout::align(width, alignment)` dropped its trailing bool.

### 4. Borrow-checker fights in the winit ApplicationHandler
- **Symptoms**: "cannot borrow `*self` as immutable because it is also borrowed
  as mutable" inside `window_event`.
- **Root cause**: holding `let state = self.state.as_mut()` across calls to other
  `&self`/`&mut self` helpers and disjoint fields.
- **Fix**: pull the `Arc<Window>` + dims out up front into locals, drop the
  `state` binding, then take short, scoped borrows. Access **disjoint struct
  fields directly** (e.g. `&self.context`, `self.renderers[i]`, `&self.scene`) so
  the borrow checker can split the `&mut self` — don't funnel them through one
  intermediate `&mut state`.

### 5. Black / empty window
- **Symptoms**: window opens, renders nothing.
- **Checklist**: (a) did you `request_redraw()` after resume/resize? winit won't
  redraw on its own. (b) `RenderParams.width/height` must match the surface
  config, not the logical size. (c) surface format from
  `surface.config.format` must be what the `Renderer` was created with. (d) you
  called `surface_texture.present()`. (e) `scene.reset()` then actually pushed
  primitives.

### 6. HiDPI text/geometry at half/double size
- **Root cause**: mixing logical and physical pixels. winit gives physical
  surface sizes; your layout is usually in logical points.
- **Fix**: pass `window.scale_factor()` into Parley's layout (`ranged_builder`
  takes a `scale`) AND into your geometry transform, consistently. Decide one
  coordinate space and convert at the boundary.

## Worked Example

A minimal-but-complete loop (the shape `core/pd-timeline-proto` uses). Pinned to
vello 0.3 / wgpu 22 / parley 0.2 / peniko 0.2 / kurbo 0.11 / winit 0.30.

```rust
// Cargo.toml: vello="0.3"  wgpu="22"  winit="0.30"  parley="0.2"
//             peniko="0.2"  kurbo="0.11"  pollster="0.4"

use vello::util::{RenderContext, RenderSurface};
use vello::{AaConfig, AaSupport, Renderer, RendererOptions, RenderParams, Scene};
use vello::peniko::{Color, Fill};
use vello::wgpu;                  // <- use vello's re-exported wgpu, never a second one
use kurbo::{Affine, Circle, Point, Stroke, Line};

// 1. Surface (winit Window -> wgpu Surface), let Vello's RenderContext drive wgpu init.
let mut cx = RenderContext::new();
let surface: RenderSurface =
    pollster::block_on(cx.create_surface(window.clone(), w, h, wgpu::PresentMode::AutoVsync))?;

// 2. One Renderer per device. AA support must match the AA you ask for at render time.
let mut renderer = Renderer::new(
    &cx.devices[surface.dev_id].device,
    RendererOptions {
        surface_format: Some(surface.format),
        use_cpu: false,
        antialiasing_support: AaSupport::area_only(),   // pairs with AaConfig::Area below
        num_init_threads: None,
    },
)?;

// 3. Per frame: rebuild the scene from scratch.
let mut scene = Scene::new();
scene.reset();
scene.stroke(&Stroke::new(1.0), Affine::IDENTITY, Color::rgb8(0x2a,0x32,0x3c), None,
             &Line::new(Point::new(0.0, 100.0), Point::new(800.0, 100.0)));
scene.fill(Fill::NonZero, Affine::IDENTITY, Color::rgb8(0x4f,0xd1,0x6b), None,
           &Circle::new(Point::new(120.0, 100.0), 5.0));
// (text via Parley -> scene.draw_glyphs(...); see references/parley-glyph-runs.md)

// 4. Render to the surface.
let tex = surface.surface.get_current_texture()?;
let dev = &cx.devices[surface.dev_id];
renderer.render_to_surface(&dev.device, &dev.queue, &scene, &tex, &RenderParams {
    base_color: Color::rgb8(0x0d,0x11,0x17),
    width: w, height: h,
    antialiasing_method: AaConfig::Area,                // must agree with AaSupport above
})?;
tex.present();
window.request_redraw();   // keep animating; winit won't redraw on its own
```

**Text (the Parley half), the part everyone gets stuck on:** build a `Layout`,
`break_all_lines`, `align`, then for each `PositionedLayoutItem::GlyphRun`, call
`scene.draw_glyphs(font).brush(..).transform(..).font_size(..).normalized_coords(coords).draw(Fill::NonZero, glyphs)`
where `glyphs` is an iterator of `vello::Glyph { id, x, y }` you accumulate by
advancing `x += g.advance`. See `references/parley-glyph-runs.md` for the exact,
compile-checked function.

## Anti-Patterns

### Anti-Pattern 1: Pulling your own `wgpu` next to Vello's
```toml
# ❌ two wgpu majors in the tree -> type mismatches everywhere
vello = "0.3"
wgpu  = "0.20"   # vello 0.3 wants wgpu 22
```
```toml
# ✅ either use vello::wgpu, or pin the SAME major vello uses
vello = "0.3"
wgpu  = "22"     # matches; verify with `cargo tree -d`
```

### Anti-Pattern 2: Caching/diffing the Scene to "save work"
Rebuilding the scene each frame is the intended model and costs microseconds.
Building a retained scene-graph diff layer on top is wasted complexity and fights
the API. Only reach for `Scene::append` of cached sub-scenes when you have
genuinely huge (100k+ primitive) static regions, measured.

### Anti-Pattern 3: Bitmap fonts / texture-atlas-by-hand for text
Vello + Parley already do GPU glyph rendering with real shaping. Hand-rolling a
bitmap atlas throws away ligatures, hinting, variable fonts, RTL/complex scripts,
and HiDPI crispness. If you think you need a manual atlas, you're below the right
layer — see `metal-text-pipeline` for when that's actually justified.

### Anti-Pattern 4: Ignoring `request_redraw` and wondering why it's static
winit is event-driven. No redraw request → no frame. Animations, scrubbing, and
auto-play all require you to request the next redraw (from `resumed`, after input,
and at the end of a frame while animating).

### Anti-Pattern 5: Trusting a tutorial for a different crate version
These crates are pre-1.0 and rename things between minors. A snippet for vello
0.2 will not compile against 0.3. Always read the `examples/` in the *tagged*
release matching your pin.

## References

- `references/version-matrix.md` — known-good vello/wgpu/parley/peniko/kurbo/winit
  version combinations and how to verify with `cargo tree -d`.
- `references/parley-glyph-runs.md` — the exact, compile-checked
  `render_glyph_run` function that feeds Parley output into a Vello `Scene`,
  including the `i16 -> F2Dot14` coords reinterpret.

## Provenance

Worked example and gotchas verified against `core/pd-timeline-proto/` in this
repo (a winit+wgpu+Vello+Parley "Voyage Timeline" window), built and run on
Apple M4 Max (Metal backend) at sub-millisecond per-frame GPU cost.

<!-- BEGIN BUNDLE INDEX (auto: index_references.py) -->

## Skill Bundle Index

*Every file in this skill, and when to open it. Auto-generated; run `scripts/index_references.py --fix`.*

**`examples/`**
- [`examples/sample-input-bad.json`](examples/sample-input-bad.json) — sample input bad (data/schema)
- [`examples/sample-input.json`](examples/sample-input.json) — sample input (data/schema)

**`references/`**
- [`references/parley-glyph-runs.md`](references/parley-glyph-runs.md) — Feeding Parley glyph runs into a Vello Scene — The single function everyone gets stuck writing.
- [`references/version-matrix.md`](references/version-matrix.md) — Vello / wgpu / Parley version matrix — Pre-1.0 Linebender crates move in lockstep clusters.

**`schemas/`**
- [`schemas/render-plan.schema.json`](schemas/render-plan.schema.json) — render plan.schema (data/schema)

**`scripts/`**
- [`scripts/render_plan_audit.mjs`](scripts/render_plan_audit.mjs)

<!-- END BUNDLE INDEX -->

Attribution

curiositechcuriositech
View sourceSee grades on GitHubMore from curiositech →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

ucoz-landing-skill

Create and edit uCoz homepage landing pages via MCP: custom templates, hero sections, lead forms, navigation menus, SEO, and responsive layout. Includes a visual design system (style selection, layout/grid, section recipes, typography/spacing, color tokens, component states, icons, modern CSS/JS, motion, imagery, social proof, copy/voice, accessibility). Uses ucoz-mcp tools for templates, site file uploads, and site modules.

107 votes

Paperclip

Interact with the Paperclip control plane API for task coordination and governance. Use when checking assignments, updating issue status, posting comments, delegating work, managing routines, or calling Paperclip API endpoints.

953191 votes

Pptx

Presentation toolkit (.pptx). Create/edit slides, layouts, content, speaker notes, comments, for programmatic presentation creation and modification.

471861 votes

Daw Music

Digital Audio Workstation usage, music composition, interactive music systems, and game audio implementation for immersive soundscapes.

761 votes

Instantly Rdsthomas Mission Control

Instantly.ai cold email outreach API - manage campaigns, leads, accounts, and analytics. Use for cold email automation, lead management, campaign creation/monitoring, and email account warmup.

761 votes
View all in tools →