Skip to content

vello_common: Generalize the command recorder abstraction and add more metadata#1746

Merged
LaurenzV merged 23 commits into
mainfrom
laurenz/vello_hybrid_shift_part3
Jul 20, 2026
Merged

vello_common: Generalize the command recorder abstraction and add more metadata#1746
LaurenzV merged 23 commits into
mainfrom
laurenz/vello_hybrid_shift_part3

Conversation

@LaurenzV

@LaurenzV LaurenzV commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

This PR is based on #1745. It changes the semantics of the command recorder, turning it into something more akin to a scene graph (we should probably rename it in the future!).

Up until now, the command recorder was written in a way that is only "useful" to Vello CPU. Most notably, normal layers would be folded into their parent stream, and only filter layers had an explicit representation as an independent unit. For Vello Hybrid, this is not a suitable representation, because all layers need to be represented explicitly and be rendered independently from the other layers.

To achieve this goal, this PR implements some changes, split up into two commits.

The first commit represents a refactor of the overall representation. Most notably:

  • It is generic over an unspecified draw type. We need this because, in addition to normal path fills as used by Vello CPU, Vello Hybrid also supports fast path rects, which have a different inner representation. Therefore, the actual draw type need to be able to be specified by each backend independently.
  • Instead of only treating filter layers as having their own root commands, now every kind of layer has their own command stream. Vello CPU will internally still ensure that they are inlined, but the crucial point is that this assumption is not baked into the generic representation anymore, so Vello Hybrid can do something different here.
  • Instead of having four commands "Fill`, "PushLayer", "FilterLayerFill" and "PopLayer" , we represent the whole scene as a tree of nodes, where each node represent a number of batched draw commands, followed by an optional layer invocation. Basically, we interleave normal draws and layer fills in a way such that they can be iterated over more easily. The fact that we represent a batch of draw commands as a simple range of draw commands instead of a flat array of inlined draw commands helped tremendously when building the new Vello Hybrid scheduler. Also, all draw commands now end up in a global buffer which we can reuse more easily across frames. The only downside is one more layer of indirection per batch for Vello CPU, which is completely negligible.
  • Since each layer now only stores a Vec<Node>, which is likely to be very small, I decided to remove the VecPool for this. I don't think it's worth it trying to reuse those across frames.

The second commit ensures that we store some more metadata about the scene, which is needed for Vello Hybrid. Yes, we do pay some additional cost for Vello CPU here, but it's basically nothing.

PR train

  1. sparse_strips: Move some code from vello_cpu to vello_common #1744 — Move some code from vello_cpu to vello_common
  2. sparse_strips: Add more new/refactored code into vello_common #1745 — Add more new/refactored code into vello_common
  3. vello_common: Generalize the command recorder abstraction and add more metadata #1746 — Generalize the command recorder abstraction and add more metadata 👈
  4. sparse_strips: Introduce Padding type and streamline clip handling #1751 — Introduce Padding and streamline clip handling

@laurenz-canva
laurenz-canva force-pushed the laurenz/vello_hybrid_shift_part3 branch from e868164 to ba64412 Compare July 14, 2026 15:51
@LaurenzV
LaurenzV requested a review from grebmeg July 14, 2026 16:04
@laurenz-canva
laurenz-canva force-pushed the laurenz/vello_hybrid_shift_part2 branch from 86cb568 to d10a407 Compare July 15, 2026 11:12
@laurenz-canva
laurenz-canva force-pushed the laurenz/vello_hybrid_shift_part3 branch 2 times, most recently from 0cf5501 to 2ee9cff Compare July 15, 2026 11:18
@LaurenzV LaurenzV changed the title vello_common: Generalize the command recorder abstraction vello_common: Generalize the command recorder abstraction and add more metadata Jul 15, 2026
@laurenz-canva
laurenz-canva force-pushed the laurenz/vello_hybrid_shift_part2 branch from d10a407 to 3f61c40 Compare July 16, 2026 08:14
Comment thread sparse_strips/vello_common/src/record.rs Outdated

@grebmeg grebmeg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM! I especially like the Node abstraction — it makes draw and layer ordering much clearer.

Comment thread sparse_strips/vello_common/src/record.rs Outdated
/// Tile-aligned dimensions of the root scene.
pub scene_size: SizeU16,
/// The nodes of the root layer.
pub nodes: Vec<Node>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Have you considered representing the root as a separate layer containing its own nodes? That would make the recording a more uniform tree-like structure, with the root as the tree root and all other layers as children. It might simplify traversal and remove some root-specific handling, although the root’s distinct rendering semantics may outweigh that benefit.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, in Vello Hybrid, the root requires some specific handling (because of blending). In my rewrite branch, the special casing hasn't really been a problem, so I think it's better to keep it!

pub root_cmds: Vec<RecordedCmd>,
/// Recorder for a scene description.
#[derive(Debug)]
pub struct CommandRecorder<D> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would SceneRecorder be a more accurate name now? CommandRecorder suggests a flat command stream, but this type records a structured scene containing root nodes, nested layers, draws, bounds, and rendering metadata. The existing doc comment already describes it as a "recorder for a scene description".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, as mentioned, I think we could even just name it SceneGraph! However, I don't want to load this PR more than necessary. I will add a TODO!

Comment thread sparse_strips/vello_cpu/src/coarse/bucketer.rs Outdated
Comment thread sparse_strips/vello_cpu/src/dispatch/mod.rs Outdated
Comment thread sparse_strips/vello_cpu/src/dispatch/single_threaded.rs Outdated
Comment on lines +318 to +325
self.bucket_commands(
&layer.nodes,
draws,
layers,
strips,
encoded_paints,
filter_ctx,
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we be concerned about the recursion here? Could deeply nested regular layers overflow the stack, and can we avoid that with an iterative work stack?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I suppose for adverserial scenarios. But I don't think this is urgent, let me add a TODO! You'd probably have to go hundreds (or at least multiple dozens) of layers deep to trigger this. But good catch!

Base automatically changed from laurenz/vello_hybrid_shift_part2 to main July 20, 2026 07:13
…shift_part3

# Conflicts:
#	sparse_strips/vello_common/src/clip.rs
#	sparse_strips/vello_common/src/geometry.rs
#	sparse_strips/vello_common/src/record.rs
#	sparse_strips/vello_common/src/strip.rs
#	sparse_strips/vello_common/src/transforms.rs
#	sparse_strips/vello_common/src/util.rs
#	sparse_strips/vello_common/src/viewport.rs
#	sparse_strips/vello_cpu/src/coarse/bucketer.rs
#	sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs
#	sparse_strips/vello_cpu/src/dispatch/single_threaded.rs
#	sparse_strips/vello_cpu/src/render.rs
#	sparse_strips/vello_hybrid/src/scene.rs
pull Bot pushed a commit to Mu-L/vello that referenced this pull request Jul 20, 2026
…ebender#1744)

This is the first PR in a series of three (perhaps four) PRs that serve
as a preparation for the Vello Hybrid rewrite.

This PR is easiest reviewed commit by commit, though there isn't that
much to review in the first place. Apart from adjusting a few
descriptions, it's mostly just moving some code around.

The first 7 commits were done by manually copy-pasting code into the
right location. The last commit that fixes compiler issues were done
with AI assistance.

### PR train

1. linebender#1744 — Move some code from `vello_cpu` to `vello_common` 👈
2. linebender#1745 — Add more new/refactored code into `vello_common`
3. linebender#1746 — Generalize the command recorder abstraction and add more
metadata
4. linebender#1751 — Introduce `Padding` and streamline clip handling
pull Bot pushed a commit to Mu-L/vello that referenced this pull request Jul 20, 2026
…bender#1745)

This PR is based on linebender#1744 and introduces some actual changes, once again
in preparation for the Vello Hybrid rewrite.

The first commit adds some utility types like `Offset` and `Size`, which
will layer on be used by Vello Hybrid (they are currently partially
duplicated there, but I will just remove them later on). In theory they
could just live in Vello Hybrid, but since we already have `RectU16` in
vello_common I figured this is the best place.

The second commit resolved a TODO and creates a new generic method that
allows generating fill/alpha-fill sequences from a slice of strips. We
also use this for the existing `strip_bbox` function now. I'm aware that
this will result in a slight slowdown since we will do the clipping even
though it isn't necessary to clip anything since we are just calculating
the bbox, but I think it's better to accept this cost for less code
duplication.

The third commit extracts all of the transforms we currently have into a
custom struct, so that, once again, they can more easily be used by
Vello Hybrid.

Fourth commit introduces a new `ViewportState` abstraction, that
abstracts all of the complexity of handling different viewport so that
Vello Hybrid can use this as well.

Fifth commit adds a way of querying whether an encoded paint has any
opacities directly.

### PR train

1. linebender#1744 — Move some code from `vello_cpu` to `vello_common`
2. linebender#1745 — Add more new/refactored code into `vello_common` 👈
3. linebender#1746 — Generalize the command recorder abstraction and add more
metadata
4. linebender#1751 — Introduce `Padding` and streamline clip handling
@LaurenzV
LaurenzV added this pull request to the merge queue Jul 20, 2026
Merged via the queue into main with commit dc23e93 Jul 20, 2026
19 checks passed
@LaurenzV
LaurenzV deleted the laurenz/vello_hybrid_shift_part3 branch July 20, 2026 08:03
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.

2 participants