Skip to content

Conversation

@waywardmonkeys
Copy link
Contributor

@waywardmonkeys waywardmonkeys commented Nov 30, 2025

Add understory_imaging imaging IR crate

This PR adds understory_imaging, a backend‑agnostic imaging IR that sits between the presentation/display layer and concrete render backends (Vello CPU/Hybrid/Classic, Skia, etc.).

The crate defines:

  • Opaque resource handles: PathId, ImageId, PaintId, PictureId, FilterId with PathDesc, ImageDesc,
    PaintDesc, PictureDesc descriptors, managed via a ResourceBackend trait. IDs are small, stable handles reused
    across frames and recordings for the lifetime of each resource.
  • Imaging operations: StateOp (transform, paint, stroke, clip, blend, opacity, groups) and DrawOp (fill/
    stroke path/rect, draw image/picture), combined into ImagingOp as a POD‑friendly IR suitable for recording
    and caching.
  • Backend interface: an ImagingBackend trait that extends ResourceBackend with state/draw entry points and a
    basic recording API (begin_record/end_record), plus helpers record_ops and record_picture to capture short
    sequences as reusable recordings or pictures.
  • Recordings model: RecordedOps wraps an Arc<[ImagingOp]> plus optional backend‑specific acceleration and
    transform metadata (valid_under: TransformClass, original_ctm). Recordings are explicitly environment‑bound:
    all referenced resource IDs must exist and remain compatible when replayed.
  • Transform classification: TransformClass and transform_diff_class provide a conservative way to decide when
    cached/recorded content can be safely reused under a new transform (distinguishing exact, translate‑only, and
    general affine differences).

The crate is no_std and currently targets the needs of early Vello/Skia backends. The overall goals, architecture, and future directions are documented in docs/issue_understory_imaging.md; the crate‑level docs give a condensed overview and a minimal usage sketch for backend implementors.


/// Clip shape used by `StateOp::SetClip`.
///
/// This will likely grow variants for rounded rects and more complex regions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! ClipShape::RoundedRect is already in

let transform =
imaging::Affine::translate((tx, ty)) * imaging::Affine::scale_non_uniform(w, h);

if (*opacity - 1.0).abs() > f32::EPSILON {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if opacity needs to be reset (back to 1.0) to avoid leaking state across display-list entries...the conditional SetOpacity only fires when opacity != 1, so an image can drop opacity and nothing bumps it back, leaving later ops dimmer than intended.

@waywardmonkeys waywardmonkeys changed the title WIP understory_imaging Dec 3, 2025
@waywardmonkeys waywardmonkeys force-pushed the understory_imaging_0 branch 3 times, most recently from 6a0e267 to 0ea6c2a Compare December 22, 2025 09:00
Comment on lines +412 to +657
// If new formats are added upstream, prefer failing loudly until
// we define v0 behavior for them.
_ => sk::ColorType::RGBA8888,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should "failing loudly" be an explicit panic? Handling the format wrong would likely cause panics further down the road.

Comment on lines 219 to 220
/// Set the current clip shape.
SetClip(ClipShape),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want an operation that says whether clipping is to be anti-aliased or not, for backends that make a distinction (like Skia), or we may want to say that backends should use the highest-quality clipping available.

Comment on lines 508 to 513
let rect = sk::Rect::new(x0, y0, x1, y1);
let mut path = sk::Path::new();
path.add_round_rect(rect, (radius_x, radius_y), None);
self.canvas.save();
self.canvas.clip_path(&path, None, true);
self.clip_depth = self.clip_depth.saturating_add(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skia supports rounded rectangle clips, so I think this can be something like the following.

Suggested change
let rect = sk::Rect::new(x0, y0, x1, y1);
let mut path = sk::Path::new();
path.add_round_rect(rect, (radius_x, radius_y), None);
self.canvas.save();
self.canvas.clip_path(&path, None, true);
self.clip_depth = self.clip_depth.saturating_add(1);
let rect = sk::Rect::new(x0, y0, x1, y1);
let rrect = sk::RRect::new_rect_xy(&rect, radius_x, radius_y);
self.canvas.save();
self.canvas.clip_rrect(&rrect, None, true);
self.clip_depth = self.clip_depth.saturating_add(1);

@waywardmonkeys waywardmonkeys force-pushed the understory_imaging_0 branch 11 times, most recently from 3fa539d to cc00e12 Compare December 22, 2025 18:07
@waywardmonkeys waywardmonkeys force-pushed the understory_imaging_0 branch 8 times, most recently from 7b533e0 to d374a23 Compare January 7, 2026 05:28
@waywardmonkeys waywardmonkeys force-pushed the understory_imaging_0 branch 9 times, most recently from 3a2ff02 to f79a77f Compare January 10, 2026 20:29
Introduce a backend-agnostic, POD-friendly imaging IR and initial backends.

- Add `understory_imaging` crate: resources, state/draw ops, recordings, transform classes
- Add backends: `understory_imaging_vello`, `understory_imaging_vello_cpu`, `understory_imaging_skia`
- Add `understory_imaging_ref` for tests/experimentation
- Add `understory_text_imaging` helpers for lowering text/COLRv1/bitmaps into imaging ops
- Add imaging examples (basics/images/gradients/pictures) and repo-local font fixtures under `assets/`

This is an initial v0 drop (0.0.1) and is expected to evolve as we integrate display/presentation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants