END_CLIP reads out-of-bounds draw data due to scene_offset mismatch
## Summary
`DrawTag::END_CLIP` is `0x21`, which encodes `scene_offset = (0x21 >> 2) & 7 = 8` u32s per the draw monoid. However, `encode_end_clip()` pushes **zero** words to `draw_data`. This means every END_CLIP's draw monoid `scene_offset` advances 8 words past the actual draw data boundary, causing the coarse shader to read blend mode and alpha from **uninitialized memory** (typically the transform stream).
## Details
In `vello_encoding/src/draw.rs`:
```rust
pub const END_CLIP: Self = Self(0x21);
// scene_offset = (0x21 >> 2) & 7 = 8
```
In `vello_encoding/src/encoding.rs`:
```rust
pub fn encode_end_clip(&mut self) {
if self.n_open_clips > 0 {
self.draw_tags.push(DrawTag::END_CLIP);
// No draw_data pushed!
self.path_tags.push(PathTag::PATH);
// ...
}
}
```
Compare with `BEGIN_CLIP = 0x49` which has `scene_offset = (0x49 >> 2) & 7 = 2` and correctly pushes 2 u32s (`DrawBeginClip` = blend_mode + alpha).
In `coarse.wgsl`, the END_CLIP case reads:
```wgsl
case DRAWTAG_END_CLIP: {
clip_depth -= 1u;
write_path(tile, tile_ix, draw_flags);
let blend = scene[dd]; // OOB read
let alpha = bitcast<f32>(scene[dd + 1u]); // OOB read
write_end_clip(CmdEndClip(blend, alpha));
}
```
Where `dd = config.drawdata_base + draw_monoids[drawobj_ix].scene_offset` — but `scene_offset` has been inflated by 8 per END_CLIP instead of the 0 actual words, so `dd` points past the draw data into the transform stream.
## Impact
- The blend mode and alpha for every clip/layer end are read from garbage memory. On most GPUs this "works" because the garbage blend value rarely equals `BLEND_CLIP` (32771), so `is_blend` is usually true, and the garbage alpha reinterpreted as `f32` from a transform value happens to be close to 1.0.
- This is likely the root cause (or a contributing factor) of #1061 — layer compositing uses wrong blend/alpha values.
- The `resolve.rs` sentinel END_CLIPs for unclosed clips also don't push draw data, compounding the offset drift.
## Suggested fix
Either:
1. **Change `END_CLIP` tag to `0x09`** (scene_offset = 2) and push the matching `DrawBeginClip` words in `encode_end_clip()` — so the coarse shader reads correct blend/alpha.
2. Or have the coarse shader read blend/alpha from the **matching BEGIN_CLIP's** draw data offset instead of the END_CLIP's own offset.
Option 1 is simpler and what we implemented in our fork (ekrano). The `resolve.rs` sentinel path also needs to emit matching draw data for unclosed clips.
0 条评论