Skip to content

Commit 3e6bb96

Browse files
authored
vello_hybrid: Add support for setting a transparency hint for external textures (#1812)
As per description. This requires making the opaque pass aware of external textures. An unfortunate consequence of this is that opaque external images that are not aligned to integer coordinates will need to be drawn twice: Once during the opaque pass, and another time for the fractional edges during the alpha pass. But at least they can now be drawn with depth occlusion, which should be a huge win. This PR was done with assistance of GPT 5.6 Sol.
1 parent 016342d commit 3e6bb96

12 files changed

Lines changed: 406 additions & 172 deletions

File tree

sparse_strips/vello_example_scenes/src/spritesheet.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,7 @@ impl ExampleScene for SpritesheetScene {
104104
* Affine::scale(scale)
105105
* Affine::translate((-half_w, -half_h));
106106

107-
rects.push(SampleRect {
108-
source_region: sprite,
109-
transform,
110-
});
107+
rects.push(SampleRect::new(sprite, transform));
111108
}
112109
}
113110

sparse_strips/vello_hybrid/src/draw.rs

Lines changed: 189 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,11 @@ impl Draw {
3939
gpu_strip: GpuStrip,
4040
external_texture_id: Option<TextureId>,
4141
) {
42-
if let Some(texture_id) = external_texture_id {
43-
let needs_new_run = self
44-
.external_texture_runs
45-
.last()
46-
.is_none_or(|run| run.texture_id != texture_id);
47-
48-
if needs_new_run {
49-
let strips_start = if self.external_texture_runs.is_empty() {
50-
0
51-
} else {
52-
self.strip_ranges.len()
53-
};
54-
55-
self.external_texture_runs.push(ExternalTextureRun {
56-
strips_start,
57-
texture_id,
58-
});
59-
}
60-
}
42+
push_external_texture_run(
43+
&mut self.external_texture_runs,
44+
self.strip_ranges.len(),
45+
external_texture_id,
46+
);
6147

6248
strips.push_ranged(&mut self.strip_ranges, gpu_strip);
6349
}
@@ -71,15 +57,67 @@ impl Clear for Draw {
7157
}
7258
}
7359

60+
/// Root-level opaque strips and the external texture bindings needed to render them.
61+
#[derive(Debug, Default)]
62+
pub(crate) struct OpaqueDraw {
63+
strips: Vec<GpuStrip>,
64+
external_texture_runs: Vec<ExternalTextureRun>,
65+
}
66+
67+
impl OpaqueDraw {
68+
fn push(&mut self, strip: GpuStrip, external_texture_id: Option<TextureId>) {
69+
push_external_texture_run(
70+
&mut self.external_texture_runs,
71+
self.strips.len(),
72+
external_texture_id,
73+
);
74+
75+
self.strips.push(strip);
76+
}
77+
78+
/// Reverse opaque strips for front-to-back rendering.
79+
pub(crate) fn reverse(&mut self) {
80+
self.strips.reverse();
81+
82+
// We also need to reassign the indices for external textures.
83+
let mut original_end = self.strips.len();
84+
for run in self.external_texture_runs.iter_mut().rev() {
85+
let original_start = run.strips_start;
86+
run.strips_start = self.strips.len() - original_end;
87+
original_end = original_start;
88+
}
89+
self.external_texture_runs.reverse();
90+
}
91+
92+
pub(crate) fn is_empty(&self) -> bool {
93+
self.strips.is_empty()
94+
}
95+
96+
pub(crate) fn strips(&self) -> &[GpuStrip] {
97+
&self.strips
98+
}
99+
100+
pub(crate) fn external_texture_runs(&self) -> &[ExternalTextureRun] {
101+
&self.external_texture_runs
102+
}
103+
}
104+
105+
impl Clear for OpaqueDraw {
106+
fn clear(&mut self) {
107+
self.strips.clear();
108+
self.external_texture_runs.clear();
109+
}
110+
}
111+
74112
/// Appends recorded draws to a scheduled [`Draw`] and its shared buffers.
75113
#[derive(Debug)]
76114
pub(crate) struct DrawBuilder<'a, T: DrawTarget> {
77115
/// Draw whose ranges and binding state are being built.
78116
draw: &'a mut Draw,
79117
/// Shared buffer receiving alpha-blended strips.
80118
strips: &'a mut Vec<GpuStrip>,
81-
/// Shared buffer receiving root-level opaque strips.
82-
opaque: &'a mut Vec<GpuStrip>,
119+
/// Root-level opaque draw receiving fully covered strips.
120+
opaque: &'a mut OpaqueDraw,
83121
/// Target and depth state used to encode strips.
84122
state: &'a mut DrawState<T>,
85123
}
@@ -93,7 +131,7 @@ impl<'a, T: DrawTarget> DrawBuilder<'a, T> {
93131
Self {
94132
draw,
95133
strips: &mut draw_buffers.strips,
96-
opaque: &mut draw_buffers.opaque_strips,
134+
opaque: &mut draw_buffers.opaque,
97135
state,
98136
}
99137
}
@@ -112,12 +150,12 @@ impl<'a, T: DrawTarget> DrawBuilder<'a, T> {
112150
}
113151
}
114152

115-
fn push_opaque(&mut self, strip: GpuStrip) -> bool {
153+
fn push_opaque(&mut self, strip: GpuStrip, external_texture_id: Option<TextureId>) -> bool {
116154
if !self.state.use_depth_buffer || !self.state.target.enable_depth() {
117155
return false;
118156
}
119157

120-
self.opaque.push(strip);
158+
self.opaque.push(strip, external_texture_id);
121159
true
122160
}
123161

@@ -172,7 +210,7 @@ impl<'a, T: DrawTarget> DrawBuilder<'a, T> {
172210
depth_index,
173211
);
174212

175-
if !paint.opaque || !builder.push_opaque(strip) {
213+
if !paint.opaque || !builder.push_opaque(strip, paint.external_texture_id) {
176214
builder
177215
.draw
178216
.push(builder.strips, strip, paint.external_texture_id);
@@ -217,7 +255,10 @@ impl<'a, T: DrawTarget> DrawBuilder<'a, T> {
217255
depth_index,
218256
);
219257

220-
if !(paint.opaque && part.frac == 0 && self.push_opaque(strip)) {
258+
if !(paint.opaque
259+
&& part.frac == 0
260+
&& self.push_opaque(strip, paint.external_texture_id))
261+
{
221262
self.draw
222263
.push(self.strips, strip, paint.external_texture_id);
223264
}
@@ -309,15 +350,15 @@ impl<'a, T: DrawTarget> DrawBuilder<'a, T> {
309350
/// Reusable strip storage shared by all draws in a schedule.
310351
#[derive(Debug, Default)]
311352
pub(crate) struct DrawBuffers {
312-
/// Opaque root strips rendered in the early depth-writing pass.
313-
pub(crate) opaque_strips: Vec<GpuStrip>,
353+
/// Root-level opaque draw rendered in the early depth-writing pass.
354+
pub(crate) opaque: OpaqueDraw,
314355
/// Alpha-blended strips selected by each draw's ranges.
315356
pub(crate) strips: Vec<GpuStrip>,
316357
}
317358

318359
impl DrawBuffers {
319360
pub(crate) fn clear(&mut self) {
320-
self.opaque_strips.clear();
361+
self.opaque.clear();
321362
self.strips.clear();
322363
}
323364
}
@@ -403,17 +444,34 @@ impl LayerTextureRegion {
403444
}
404445
}
405446

406-
/// Specifies a run of strips inside a draw that can be drawn with the same external texture
407-
/// binding.
447+
/// Specifies a run of strips that can be drawn with the same external texture binding.
408448
#[derive(Debug, Clone, PartialEq, Eq)]
409449
pub(crate) struct ExternalTextureRun {
410450
/// External texture bound for the run.
411451
pub(crate) texture_id: TextureId,
412452
/// Start index of the strip range for this run. The end is implicitly the start of the next
413-
/// run, or, for the last run, the total number of strips.
453+
/// run, or, for the last run, the total number of strips in the pass.
414454
pub(crate) strips_start: usize,
415455
}
416456

457+
fn push_external_texture_run(
458+
runs: &mut Vec<ExternalTextureRun>,
459+
strips_len: usize,
460+
external_texture_id: Option<TextureId>,
461+
) {
462+
let Some(texture_id) = external_texture_id else {
463+
return;
464+
};
465+
if runs.last().is_some_and(|run| run.texture_id == texture_id) {
466+
return;
467+
}
468+
469+
runs.push(ExternalTextureRun {
470+
texture_id,
471+
strips_start: if runs.is_empty() { 0 } else { strips_len },
472+
});
473+
}
474+
417475
/// Assigns monotonically increasing depth values to opaque strips.
418476
#[derive(Debug, Default, Clone, Copy)]
419477
pub(crate) struct DepthCounter {
@@ -466,7 +524,8 @@ impl StripAlphaFillSegmentExt for StripAlphaFillSegment {
466524

467525
#[cfg(test)]
468526
mod tests {
469-
use super::{Draw, DrawBuffers, DrawBuilder, DrawState, ExternalTextureRun};
527+
use super::{Draw, DrawBuffers, DrawBuilder, DrawState, ExternalTextureRun, OpaqueDraw};
528+
use crate::GpuStrip;
470529
use crate::paint::PaintResolver;
471530
use crate::scene::{RecordedDraw, RecordedRect};
472531
use crate::target::{
@@ -540,6 +599,29 @@ mod tests {
540599
PaintResolver::new(&[], &[])
541600
}
542601

602+
fn gpu_strip(x: u16) -> GpuStrip {
603+
GpuStrip {
604+
x,
605+
y: 0,
606+
width: 1,
607+
dense_width_or_rect_height: 0,
608+
col_idx_or_rect_frac: 0,
609+
payload: 0,
610+
paint_and_rect_flag: 0,
611+
depth_index: 0,
612+
}
613+
}
614+
615+
fn strip_xs(draw: &OpaqueDraw) -> Vec<u16> {
616+
draw.strips().iter().map(|strip| strip.x).collect()
617+
}
618+
619+
fn run_starts(runs: &[ExternalTextureRun]) -> Vec<(TextureId, usize)> {
620+
runs.iter()
621+
.map(|run| (run.texture_id, run.strips_start))
622+
.collect()
623+
}
624+
543625
fn external(texture_id: TextureId) -> EncodedPaint {
544626
EncodedPaint::ExternalTexture(EncodedExternalTexture {
545627
texture_id,
@@ -603,21 +685,8 @@ mod tests {
603685
}
604686

605687
assert_eq!(
606-
draw.external_texture_runs,
607-
[
608-
ExternalTextureRun {
609-
texture_id: texture_a,
610-
strips_start: 0,
611-
},
612-
ExternalTextureRun {
613-
texture_id: texture_b,
614-
strips_start: 2,
615-
},
616-
ExternalTextureRun {
617-
texture_id: texture_a,
618-
strips_start: 4,
619-
},
620-
]
688+
run_starts(&draw.external_texture_runs),
689+
[(texture_a, 0), (texture_b, 2), (texture_a, 4)]
621690
);
622691
}
623692

@@ -636,15 +705,78 @@ mod tests {
636705
assert_eq!(draw.strip_ranges.len(), 3);
637706
// Images in the atlas are handled separately from external textures, so
638707
// it's fine to collapse them.
708+
assert_eq!(run_starts(&draw.external_texture_runs), [(texture, 0)]);
709+
}
710+
711+
#[test]
712+
fn opaque_reverse_rebases_texture_runs() {
713+
let texture_a = TextureId(10);
714+
let texture_b = TextureId(20);
715+
let texture_c = TextureId(30);
716+
let mut draw = OpaqueDraw::default();
717+
718+
for (x, texture_id) in [
719+
(0, None),
720+
(1, Some(texture_a)),
721+
(2, Some(texture_a)),
722+
(3, None),
723+
(4, None),
724+
(5, None),
725+
(6, Some(texture_b)),
726+
(7, Some(texture_b)),
727+
(8, Some(texture_c)),
728+
(9, None),
729+
(10, Some(texture_c)),
730+
(11, None),
731+
(12, None),
732+
(13, Some(texture_a)),
733+
(14, None),
734+
(15, Some(texture_b)),
735+
] {
736+
draw.push(gpu_strip(x), texture_id);
737+
}
639738
assert_eq!(
640-
draw.external_texture_runs,
641-
[ExternalTextureRun {
642-
texture_id: texture,
643-
strips_start: 0,
644-
}]
739+
run_starts(draw.external_texture_runs()),
740+
[
741+
(texture_a, 0),
742+
(texture_b, 6),
743+
(texture_c, 8),
744+
(texture_a, 13),
745+
(texture_b, 15),
746+
]
747+
);
748+
749+
draw.reverse();
750+
751+
assert_eq!(
752+
strip_xs(&draw),
753+
[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
754+
);
755+
assert_eq!(
756+
run_starts(draw.external_texture_runs()),
757+
[
758+
(texture_b, 0),
759+
(texture_a, 1),
760+
(texture_c, 3),
761+
(texture_b, 8),
762+
(texture_a, 10),
763+
]
645764
);
646765
}
647766

767+
#[test]
768+
fn opaque_reverse_without_texture_runs() {
769+
let mut draw = OpaqueDraw::default();
770+
for x in 0..3 {
771+
draw.push(gpu_strip(x), None);
772+
}
773+
774+
draw.reverse();
775+
776+
assert_eq!(strip_xs(&draw), [2, 1, 0]);
777+
assert_eq!(run_starts(draw.external_texture_runs()), []);
778+
}
779+
648780
#[test]
649781
fn draw_clear() {
650782
let texture_id = TextureId(10);
@@ -683,14 +815,14 @@ mod tests {
683815
no_paints(),
684816
);
685817

686-
assert_eq!(user_case.buffers.opaque_strips.len(), 1);
818+
assert_eq!(user_case.buffers.opaque.strips().len(), 1);
687819
assert_eq!(user_draw.strip_ranges.len(), 1);
688820

689821
let mut atlas_case = DrawCase::new(RootTarget::AtlasLayer, RectU16::new(0, 0, 8, 8));
690822
let mut atlas_draw = Draw::default();
691823
atlas_case.rect(&mut atlas_draw, rect(0.0), solid(1.0), no_paints());
692824

693-
assert!(atlas_case.buffers.opaque_strips.is_empty());
825+
assert!(atlas_case.buffers.opaque.is_empty());
694826
assert_eq!(atlas_draw.strip_ranges.len(), 1);
695827
}
696828

@@ -726,7 +858,8 @@ mod tests {
726858
);
727859
assert_eq!(
728860
case.buffers
729-
.opaque_strips
861+
.opaque
862+
.strips()
730863
.iter()
731864
.map(|strip| strip.depth_index)
732865
.collect::<Vec<_>>(),

0 commit comments

Comments
 (0)