Skip to content

sparse strips: Lazily push wide tile layer buffers#1414

Open
tomcur wants to merge 7 commits intolinebender:mainfrom
tomcur:lazy-push-buf
Open

sparse strips: Lazily push wide tile layer buffers#1414
tomcur wants to merge 7 commits intolinebender:mainfrom
tomcur:lazy-push-buf

Conversation

@tomcur
Copy link
Member

@tomcur tomcur commented Feb 3, 2026

This continues on #1403, and is based on/closes #1383.

In coarse.rs we are doing O(viewport_width x viewport_size) work for pushing and popping buffers for each layer needing buffers. PR #1403 already ensures we aren't actually sending commands for buffer blending through to rendering anymore for empty buffers. This PR makes command generation lazy so that we also don't do the push_buf work for the entire viewport anymore, but only for the wide tiles that actually get drawn on.

Some operations still fall back to pushing buffers for the full viewport, like filter layers and destructive blends. We may be able to do away with some of that in the future, but for now, those operations require buffers even if they don't have content (doing away or tightening the condition causes various tests to fail or asserts to be hit).

@tomcur tomcur added the C-sparse-strips Applies to sparse strips variants of vello in general label Feb 3, 2026
This continues on linebender#1403,
and is based on/closes linebender#1383.

In `coarse.rs` we are doing `O(viewport_width x viewport_size)` work for
pushing and popping buffers for each layer needing buffers. PR linebender#1403
already ensures we aren't actually sending commands for buffer blending
through to fine rendering for empty buffers anymore. This PR makes
command generation lazy so that we also don't do the `push_buf` work for
the entire viewport anymore, but only for the wide tiles that actually
get drawn on.

Some operations still fall back to pushing buffers for the full
viewport, like filter layers and destructive blends. We may be able to
do away with some of that in the future, but for now, those operations
require buffers even if they don't have content (doing away or
tightening the condition causes various tests to fail or asserts to be
hit).
Copy link
Member Author

@tomcur tomcur left a comment

Choose a reason for hiding this comment

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

I've done a bit of micro-optimizing to keep the cost low for the worst-case where we're actually drawing a (nearly) dense layer.

Benchmarks in the case of layers look as follows. The first group times wide when drawing the content three layers deep at the content's natural size. Ghostscript Tiger is nearly completely dense, whereas Paris-30k is quite sparse. The tiger regresses as we're now doing the ensure_layer_stack_bufs and end up doing the work for almost all tiles, so it would've been faster to just push all the buffers beforehand, but Paris-30k is much improved.

The second group does the same but draws at 4k. That's much bigger than Tiger's natural size, and so this case also improves a lot. (Paris-30k is still better, but becomes relatively more dense than at its natural, larger size).

coarse_with_layer/Ghostscript_Tiger
                        time:   [5.8025 µs 5.8357 µs 5.8685 µs]
                        change: [+5.3922% +5.8851% +6.4095%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 2 outliers among 50 measurements (4.00%)
  2 (4.00%) high mild
coarse_with_layer/paris-30k
                        time:   [620.76 µs 623.32 µs 626.41 µs]
                        change: [-54.967% -52.845% -50.795%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 4 outliers among 50 measurements (8.00%)
  1 (2.00%) high mild
  3 (6.00%) high severe

coarse_with_layer_4k/Ghostscript_Tiger
                        time:   [107.11 µs 107.33 µs 107.58 µs]
                        change: [-73.934% -73.739% -73.575%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 6 outliers among 50 measurements (12.00%)
  5 (10.00%) high mild
  1 (2.00%) high severe
coarse_with_layer_4k/paris-30k
                        time:   [340.78 µs 342.86 µs 345.49 µs]
                        change: [-40.644% -39.706% -38.685%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 4 outliers among 50 measurements (8.00%)
  1 (2.00%) high mild
  3 (6.00%) high severe

In case there are no layers at all, we end up never having to push buffers, so there we also needlessly pay for every call. In the sparse Paris-30k case it's not too bad.

coarse/Ghostscript_Tiger
                        time:   [3.2046 µs 3.2230 µs 3.2462 µs]
                        change: [+3.2502% +4.2128% +5.1102%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 3 outliers among 50 measurements (6.00%)
  2 (4.00%) high mild
  1 (2.00%) high severe
coarse/paris-30k        time:   [356.05 µs 357.00 µs 358.15 µs]
                        change: [+0.5005% +1.5403% +2.4935%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 3 outliers among 50 measurements (6.00%)
  2 (4.00%) high mild
  1 (2.00%) high severe

@tomcur
Copy link
Member Author

tomcur commented Feb 19, 2026

(Getting rid of the subtraction (n_bufs - n_clip) makes no substantial difference.)

@LaurenzV
Copy link
Collaborator

Hmm, I guess some of those gains do look pretty nice. If we decide to merge some variant of #1454, the "no layer" case would also become irrelevant since we would completely skip coarse rasterization (at least for vello_hybrid).

However, would like to hear what @taj-p or @grebmeg think before reviewing this PR more deeply, as it does add some more complexity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-sparse-strips Applies to sparse strips variants of vello in general

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lazy tile push_buf for blending

2 participants

Comments