blob: cd725265e70d4eb0cd85679487f5679fb815f3a6 [file] [edit]
// Copyright 2026 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Draw construction for strip render passes.
use crate::GpuStrip;
use crate::paint::{
COLOR_SOURCE_LAYER, COLOR_SOURCE_SHIFT, EXTERNAL_TEXTURE_SLOT_SHIFT, PaintResolver,
TextureSourceId,
};
use crate::rect::{RectPart, split_rect};
use crate::scene::{RecordedDraw, RecordedPath};
use crate::target::{DrawTarget, LayerTextureRegion};
use crate::util::{Ranges, VecExt, pack_opacity, pack_u16_pair};
use alloc::vec::Vec;
use vello_common::geometry::RectU16;
use vello_common::kurbo::Rect;
use vello_common::paint::Paint;
use vello_common::record::LayerClip;
use vello_common::strip::{StripAlphaFillSegment, StripFillSegment, visit_strip_fill_segments};
use vello_common::strip_generator::StripStorage;
use vello_common::tile::Tile;
use vello_common::util::Clear;
/// Strip ranges and texture-binding state for one scheduled draw pass.
#[derive(Debug, Default, Clone)]
pub(crate) struct Draw {
/// Ranges selecting this draw's strips from [`DrawBuffers::strips`].
pub(crate) strip_ranges: Ranges,
/// Runs that require external texture bindings.
pub(crate) external_texture_runs: Vec<ExternalTextureRun>,
/// Whether any strip in this draw samples a child layer.
pub(crate) has_child_layer: bool,
}
impl Draw {
#[inline]
fn push(
&mut self,
strips: &mut Vec<GpuStrip>,
mut gpu_strip: GpuStrip,
texture_source: Option<TextureSourceId>,
) {
if let Some(slot) = assign_external_texture_slot(
&mut self.external_texture_runs,
self.strip_ranges.len(),
texture_source,
) {
gpu_strip.paint_and_rect_flag |= u32::from(slot) << EXTERNAL_TEXTURE_SLOT_SHIFT;
}
strips.push_ranged(&mut self.strip_ranges, gpu_strip);
}
}
impl Clear for Draw {
fn clear(&mut self) {
self.strip_ranges.clear();
self.external_texture_runs.clear();
self.has_child_layer = false;
}
}
/// Root-level opaque strips and the external texture bindings needed to render them.
#[derive(Debug, Default)]
pub(crate) struct OpaqueDraw {
strips: Vec<GpuStrip>,
external_texture_runs: Vec<ExternalTextureRun>,
}
impl OpaqueDraw {
fn push(&mut self, mut strip: GpuStrip, texture_source: Option<TextureSourceId>) {
if let Some(slot) = assign_external_texture_slot(
&mut self.external_texture_runs,
self.strips.len(),
texture_source,
) {
strip.paint_and_rect_flag |= u32::from(slot) << EXTERNAL_TEXTURE_SLOT_SHIFT;
}
self.strips.push(strip);
}
/// Reverse opaque strips for front-to-back rendering.
pub(crate) fn reverse(&mut self) {
self.strips.reverse();
// We also need to reassign the indices for external textures.
let mut original_end = self.strips.len();
for run in self.external_texture_runs.iter_mut().rev() {
let original_start = run.strips_start;
run.strips_start = self.strips.len() - original_end;
original_end = original_start;
}
self.external_texture_runs.reverse();
}
pub(crate) fn is_empty(&self) -> bool {
self.strips.is_empty()
}
pub(crate) fn strips(&self) -> &[GpuStrip] {
&self.strips
}
pub(crate) fn external_texture_runs(&self) -> &[ExternalTextureRun] {
&self.external_texture_runs
}
}
impl Clear for OpaqueDraw {
fn clear(&mut self) {
self.strips.clear();
self.external_texture_runs.clear();
}
}
/// Appends recorded draws to a scheduled [`Draw`] and its shared buffers.
#[derive(Debug)]
pub(crate) struct DrawBuilder<'a, T: DrawTarget> {
/// Draw whose ranges and binding state are being built.
draw: &'a mut Draw,
/// Shared buffer receiving alpha-blended strips.
strips: &'a mut Vec<GpuStrip>,
/// Root-level opaque draw receiving fully covered strips.
opaque: &'a mut OpaqueDraw,
/// Target and depth state used to encode strips.
state: &'a mut DrawState<T>,
}
impl<'a, T: DrawTarget> DrawBuilder<'a, T> {
pub(crate) fn new(
draw: &'a mut Draw,
draw_buffers: &'a mut DrawBuffers,
state: &'a mut DrawState<T>,
) -> Self {
Self {
draw,
strips: &mut draw_buffers.strips,
opaque: &mut draw_buffers.opaque,
state,
}
}
pub(crate) fn push_draw(
&mut self,
draw: &RecordedDraw,
strip_storage: &StripStorage,
paint_resolver: PaintResolver<'_>,
) {
match draw {
RecordedDraw::Path(path) => self.push_path(path, strip_storage, paint_resolver),
RecordedDraw::Rect(rect) => {
self.push_rect(&rect.rect, &rect.paint, paint_resolver);
}
}
}
fn push_opaque(&mut self, strip: GpuStrip, texture_source: Option<TextureSourceId>) -> bool {
if !self.state.use_depth_buffer || !self.state.target.enable_depth() {
return false;
}
self.opaque.push(strip, texture_source);
true
}
fn push_path(
&mut self,
path: &RecordedPath,
strip_storage: &StripStorage,
paint_resolver: PaintResolver<'_>,
) {
let strips = &strip_storage.strips[path.strips.clone()];
let paint = paint_resolver.pack(&path.paint);
// Note: This will also advance the depth index for layer root draws even though
// those _currently_ never use the depth buffer, but it's better to keep the
// condition simple, and it's not incorrect to do so.
let depth_index = self.state.depth_counter.next(paint.opaque);
let tile_bounds = self.state.target_bbox.to_tile_bounds();
let geometry_shift = self.state.target.geometry_shift();
// Note that this method will also take care of culling any strips to the active clip bbox.
visit_strip_fill_segments(
strips,
tile_bounds,
self,
|builder, segment| {
let shifted = segment.shift(geometry_shift);
let strip = GpuStrip::from_fill_segment(
shifted,
Some(segment.col_idx()),
// The paint needs to be sampled at the x/y of the _original_ segment, not
// the shifted one. The shifted accounts for where the segment needs to be
// placed based on the layer bbox and texture region, but the location of
// where the paint is sampled should not be affected by that!
paint.payload_at(segment.x0(), segment.y()),
paint.paint,
depth_index,
);
builder
.draw
.push(builder.strips, strip, paint.texture_source);
},
|builder, segment| {
let shifted = segment.shift(geometry_shift);
let strip = GpuStrip::from_fill_segment(
shifted,
None,
// See the comment above.
paint.payload_at(segment.x0(), segment.y()),
paint.paint,
depth_index,
);
if !paint.opaque || !builder.push_opaque(strip, paint.texture_source) {
builder
.draw
.push(builder.strips, strip, paint.texture_source);
}
},
);
}
fn push_rect(&mut self, rect: &Rect, paint: &Paint, paint_resolver: PaintResolver<'_>) {
// Recordings might contain geometry that exceeds the actual layer
// bounding box. This can happen when a clip path is associated with the layer.
// Recordings will not cull those for us, so we need to do this manually here.
// For normal paths, the `visit_strip_fill_segments` method takes care of doing this.
// For rectangles, we need to do this ourselves and can do a simple intersection.
let clipped_rect = rect.intersect(self.state.target_bbox.as_rect());
if clipped_rect.is_zero_area() {
return;
}
let paint = paint_resolver.pack(paint);
let depth_index = self.state.depth_counter.next(paint.opaque);
let split = split_rect(&clipped_rect);
for part in [
Some(split.main),
split.top,
split.bottom,
split.left,
split.right,
]
.into_iter()
.flatten()
{
let shifted = part.shift(self.state.target.geometry_shift());
let strip = GpuStrip::from_rect(
shifted,
// See the comment in `push_path`.
paint.payload_at(part.rect.x0, part.rect.y0),
paint.paint,
depth_index,
);
if !(paint.opaque && part.frac == 0 && self.push_opaque(strip, paint.texture_source)) {
self.draw.push(self.strips, strip, paint.texture_source);
}
}
}
pub(crate) fn push_layer_fill(
&mut self,
sample: LayerTextureRegion,
opacity: f32,
clip_path: Option<&LayerClip>,
strip_storage: &StripStorage,
) {
let sample_bbox = sample.layer_bbox.intersect(self.state.target_bbox);
if sample_bbox.is_empty() {
return;
}
let paint = (COLOR_SOURCE_LAYER << COLOR_SOURCE_SHIFT) | u32::from(pack_opacity(opacity));
if let Some(clip_path) = clip_path {
// If a clip path is associated with the layer, simply draw the strips and use the rendered
// layer as a fill.
let strips = &strip_storage.strips[clip_path.strip_range.clone()];
let depth_index = self.state.depth_counter.next(false);
let tile_bounds = sample_bbox.to_tile_bounds();
visit_strip_fill_segments(
strips,
tile_bounds,
self,
|builder, segment| {
builder.push_layer_fill_segment(
sample,
*segment,
Some(segment.col_idx()),
paint,
depth_index,
);
},
|builder, segment| {
builder.push_layer_fill_segment(sample, segment, None, paint, depth_index);
},
);
} else {
// Otherwise, a simple rect blit covering the whole layer is enough.
let depth_index = self.state.depth_counter.next(false);
let rect_part = RectPart {
rect: sample_bbox.shift(self.state.target.geometry_shift()),
frac: 0,
};
self.draw.has_child_layer = true;
self.draw.push(
self.strips,
GpuStrip::from_rect(
rect_part,
// See the comment in `push_path`.
sample.payload_at(sample_bbox.x0, sample_bbox.y0),
paint,
depth_index,
),
None,
);
}
}
fn push_layer_fill_segment(
&mut self,
sample: LayerTextureRegion,
segment: StripFillSegment,
col_idx: Option<u32>,
paint: u32,
depth_index: u32,
) {
self.draw.has_child_layer = true;
// See the comment in `push_path`.
let payload = sample.payload_at(segment.x0(), segment.y());
let shifted = segment.shift(self.state.target.geometry_shift());
let strip = GpuStrip::from_fill_segment(shifted, col_idx, payload, paint, depth_index);
self.draw.push(self.strips, strip, None);
}
}
/// Reusable strip storage shared by all draws in a schedule.
#[derive(Debug, Default)]
pub(crate) struct DrawBuffers {
/// Root-level opaque draw rendered in the early depth-writing pass.
pub(crate) opaque: OpaqueDraw,
/// Alpha-blended strips selected by each draw's ranges.
pub(crate) strips: Vec<GpuStrip>,
}
impl DrawBuffers {
pub(crate) fn clear(&mut self) {
self.opaque.clear();
self.strips.clear();
}
}
/// Target-specific state used while encoding a draw.
#[derive(Debug)]
pub(crate) struct DrawState<T: DrawTarget> {
/// Destination into which strips will be rendered.
pub(crate) target: T,
/// Whether opaque strips may use the target's depth buffer.
use_depth_buffer: bool,
/// Assigns depth values to opaque strips.
depth_counter: DepthCounter,
/// Scene-space bounds visible in the target.
pub(crate) target_bbox: RectU16,
}
impl<T: DrawTarget> DrawState<T> {
pub(crate) fn new(target: T, target_bbox: RectU16, use_depth_buffer: bool) -> Self {
Self {
target,
use_depth_buffer,
depth_counter: DepthCounter::default(),
target_bbox,
}
}
}
/// Bit 31 of [`GpuStrip::paint_and_rect_flag`] signals that the strip
/// represents a full rectangle.
const RECT_STRIP_FLAG: u32 = 1 << 31;
impl GpuStrip {
fn from_fill_segment(
rect: RectU16,
col_idx: Option<u32>,
payload: u32,
paint: u32,
depth_index: u32,
) -> Self {
let width = rect.width();
let (dense_width_or_rect_height, col_idx_or_rect_frac) = if let Some(col_idx) = col_idx {
(width, col_idx)
} else {
(0, 0)
};
Self {
x: rect.x0,
y: rect.y0,
width,
dense_width_or_rect_height,
col_idx_or_rect_frac,
payload,
paint_and_rect_flag: paint,
depth_index,
}
}
fn from_rect(part: RectPart, payload: u32, paint: u32, depth_index: u32) -> Self {
Self {
x: part.rect.x0,
y: part.rect.y0,
width: part.rect.width(),
dense_width_or_rect_height: part.rect.height(),
col_idx_or_rect_frac: part.frac,
payload,
paint_and_rect_flag: paint | RECT_STRIP_FLAG,
depth_index,
}
}
}
impl LayerTextureRegion {
fn payload_at(self, x: u16, y: u16) -> u32 {
let shift = self.geometry_shift();
// This should never fail. The shift itself can be negative if the layer bbox doesn't
// start at 0, but we only sample values that are within the layer bbox in the first place.
let source_x = u16::try_from(x as i32 + shift.0).unwrap();
let source_y = u16::try_from(y as i32 + shift.1).unwrap();
pack_u16_pair(source_x, source_y)
}
}
/// Number of external textures that can be sampled by one strip draw.
pub(crate) const EXTERNAL_TEXTURE_SLOT_COUNT: usize = 4;
/// External texture bindings for one strip draw.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct ExternalTextureBindings {
texture_sources: [Option<TextureSourceId>; EXTERNAL_TEXTURE_SLOT_COUNT],
}
impl ExternalTextureBindings {
pub(crate) const EMPTY: Self = Self {
texture_sources: [None; EXTERNAL_TEXTURE_SLOT_COUNT],
};
/// Return the existing slot for `texture_source`, or insert it into the first empty slot.
/// Returns `None` when all four slots are occupied by other textures.
#[inline]
fn get_or_insert(&mut self, texture_source: TextureSourceId) -> Option<u8> {
// This iteration order assumes slots are assigned without leaving "holes" in-between,
// i.e. if we hit `None`, any later slot is also guaranteed to be `None`.
for (slot, candidate) in self.texture_sources.iter_mut().enumerate() {
match *candidate {
Some(source) if source == texture_source => {
return Some(u8::try_from(slot).unwrap());
}
None => {
*candidate = Some(texture_source);
return Some(u8::try_from(slot).unwrap());
}
_ => {}
}
}
None
}
#[inline]
pub(crate) fn as_array(self) -> [Option<TextureSourceId>; EXTERNAL_TEXTURE_SLOT_COUNT] {
self.texture_sources
}
}
/// Specifies a run of strips that can be drawn with the same external texture bindings.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ExternalTextureRun {
/// External textures bound for the run.
pub(crate) bindings: ExternalTextureBindings,
/// Start index of the strip range for this run. The end is implicitly the start of the next
/// run, or, for the last run, the total number of strips in the pass.
pub(crate) strips_start: usize,
}
fn assign_external_texture_slot(
runs: &mut Vec<ExternalTextureRun>,
strips_len: usize,
texture_source: Option<TextureSourceId>,
) -> Option<u8> {
let texture_source = texture_source?;
if let Some(slot) = runs
.last_mut()
.and_then(|run| run.bindings.get_or_insert(texture_source))
{
return Some(slot);
}
let mut bindings = ExternalTextureBindings::EMPTY;
let slot = bindings.get_or_insert(texture_source).unwrap();
let strips_start = if runs.is_empty() { 0 } else { strips_len };
runs.push(ExternalTextureRun {
bindings,
strips_start,
});
Some(slot)
}
/// Assigns monotonically increasing depth values to opaque strips.
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct DepthCounter {
/// Number of opaque strips assigned so far.
count: u32,
}
impl DepthCounter {
#[inline(always)]
fn next(&mut self, opaque: bool) -> u32 {
self.count += opaque as u32;
self.count
}
}
pub(crate) trait RectU16Ext {
fn to_tile_bounds(self) -> RectU16;
}
impl RectU16Ext for RectU16 {
fn to_tile_bounds(self) -> RectU16 {
debug_assert!(
self.x0.is_multiple_of(Tile::WIDTH)
&& self.x1.is_multiple_of(Tile::WIDTH)
&& self.y0.is_multiple_of(Tile::HEIGHT)
&& self.y1.is_multiple_of(Tile::HEIGHT),
"draw bounding boxes must be tile-aligned"
);
Self::new(
self.x0 / Tile::WIDTH,
self.y0 / Tile::HEIGHT,
self.x1 / Tile::WIDTH,
self.y1 / Tile::HEIGHT,
)
}
}
trait StripAlphaFillSegmentExt {
fn col_idx(self) -> u32;
}
impl StripAlphaFillSegmentExt for StripAlphaFillSegment {
#[inline(always)]
fn col_idx(self) -> u32 {
self.alpha_idx / u32::from(Tile::HEIGHT)
}
}
#[cfg(test)]
mod tests {
use super::{Draw, DrawBuffers, DrawBuilder, DrawState, ExternalTextureRun, OpaqueDraw};
use crate::GpuStrip;
use crate::paint::{EXTERNAL_TEXTURE_SLOT_SHIFT, PaintResolver, TextureSourceId};
use crate::scene::{RecordedDraw, RecordedRect};
use crate::target::{
DrawTarget, LayerTextureId, LayerTextureRegion, RootTarget, TextureParity, TextureRegion,
};
use crate::util::VecExt;
use alloc::vec::Vec;
use vello_common::TextureId;
use vello_common::encode::{EncodedImage, EncodedPaint};
use vello_common::geometry::RectU16;
use vello_common::image_cache::ImageCache;
use vello_common::kurbo::{Affine, Rect, Vec2};
use vello_common::multi_atlas::{AtlasConfig, AtlasId};
use vello_common::paint::{ImageId, ImageSource, IndexedPaint, Paint, PremulColor};
use vello_common::peniko::color::palette::css::BLUE;
use vello_common::peniko::{Extend, ImageQuality, ImageSampler};
use vello_common::strip_generator::StripStorage;
use vello_common::util::Clear;
struct DrawCase<T: DrawTarget> {
buffers: DrawBuffers,
state: DrawState<T>,
strip_storage: StripStorage,
}
impl<T: DrawTarget> DrawCase<T> {
fn new(target: T, bbox: RectU16) -> Self {
Self {
buffers: DrawBuffers::default(),
state: DrawState::new(target, bbox, true),
strip_storage: StripStorage::default(),
}
}
fn rect(
&mut self,
draw: &mut Draw,
rect: Rect,
paint: Paint,
paint_resolver: PaintResolver<'_>,
) {
let recorded = RecordedDraw::Rect(RecordedRect { rect, paint });
DrawBuilder::new(draw, &mut self.buffers, &mut self.state).push_draw(
&recorded,
&self.strip_storage,
paint_resolver,
);
}
fn layer(&mut self, draw: &mut Draw, sample: LayerTextureRegion) {
DrawBuilder::new(draw, &mut self.buffers, &mut self.state).push_layer_fill(
sample,
1.0,
None,
&self.strip_storage,
);
}
}
fn solid(alpha: f32) -> Paint {
Paint::Solid(PremulColor::from_alpha_color(BLUE.with_alpha(alpha)))
}
fn indexed(index: usize) -> Paint {
Paint::Indexed(IndexedPaint::new(index))
}
fn rect(x: f64) -> Rect {
Rect::new(x, 0.0, x + 4.0, 4.0)
}
fn no_paints() -> PaintResolver<'static> {
PaintResolver::new(&[], &[])
}
fn gpu_strip(x: u16) -> GpuStrip {
GpuStrip {
x,
y: 0,
width: 1,
dense_width_or_rect_height: 0,
col_idx_or_rect_frac: 0,
payload: 0,
paint_and_rect_flag: 0,
depth_index: 0,
}
}
fn strip_xs(draw: &OpaqueDraw) -> Vec<u16> {
draw.strips().iter().map(|strip| strip.x).collect()
}
fn external_texture_slots(draw: &OpaqueDraw) -> Vec<u32> {
draw.strips()
.iter()
.map(|strip| (strip.paint_and_rect_flag >> EXTERNAL_TEXTURE_SLOT_SHIFT) & 0x3)
.collect()
}
fn texture_ids<const N: usize>() -> [TextureId; N] {
core::array::from_fn(|index| TextureId(index as u64))
}
fn external_sources<const N: usize>() -> [TextureSourceId; N] {
texture_ids().map(TextureSourceId::External)
}
fn run_states(runs: &[ExternalTextureRun]) -> Vec<([Option<TextureSourceId>; 4], usize)> {
runs.iter()
.map(|run| (run.bindings.as_array(), run.strips_start))
.collect()
}
fn external(texture_id: TextureId) -> EncodedPaint {
EncodedPaint::Image(EncodedImage {
source: ImageSource::external_texture(texture_id, RectU16::new(0, 0, 8, 8), true),
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
alpha: 1.0,
},
may_have_transparency: true,
transform: Affine::IDENTITY,
x_advance: Vec2::new(1.0, 0.0),
y_advance: Vec2::new(0.0, 1.0),
tint: None,
})
}
fn atlas_image(image_id: ImageId) -> EncodedPaint {
EncodedPaint::Image(EncodedImage {
source: ImageSource::opaque_id(image_id),
sampler: ImageSampler {
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
alpha: 1.0,
},
may_have_transparency: true,
transform: Affine::IDENTITY,
x_advance: Vec2::new(1.0, 0.0),
y_advance: Vec2::new(0.0, 1.0),
tint: None,
})
}
fn layer(layer_bbox: RectU16) -> LayerTextureRegion {
LayerTextureRegion {
texture: TextureRegion {
target: LayerTextureId::new(TextureParity::Even, 0),
rect: RectU16::new(0, 0, layer_bbox.width(), layer_bbox.height()),
},
layer_bbox,
}
}
#[test]
fn texture_runs() {
let [texture_a, texture_b] = texture_ids();
let encoded = [external(texture_a), external(texture_b)];
let offsets = [0, 0];
let resolver = PaintResolver::new(&encoded, &offsets);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 32, 8));
let mut draw = Draw::default();
let mut other = Draw::default();
for (x, paint_index) in [(0.0, 0), (4.0, 0)] {
case.rect(&mut draw, rect(x), indexed(paint_index), resolver);
}
case.rect(&mut other, rect(8.0), solid(0.5), no_paints());
for (x, paint_index) in [(12.0, 1), (16.0, 1), (20.0, 0)] {
case.rect(&mut draw, rect(x), indexed(paint_index), resolver);
}
assert_eq!(
run_states(&draw.external_texture_runs),
[(
[
Some(TextureSourceId::External(texture_a)),
Some(TextureSourceId::External(texture_b)),
None,
None,
],
0,
)]
);
}
#[test]
fn fifth_external_texture_starts_a_fresh_run() {
let textures: [TextureId; 5] = texture_ids();
let encoded = textures.map(external);
let offsets = [0; 5];
let resolver = PaintResolver::new(&encoded, &offsets);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 32, 8));
let mut draw = Draw::default();
for (draw_index, paint_index) in [0, 1, 2, 3, 4, 0].into_iter().enumerate() {
case.rect(
&mut draw,
rect(draw_index as f64 * 4.0),
indexed(paint_index),
resolver,
);
}
assert_eq!(
run_states(&draw.external_texture_runs),
[
(
[
Some(TextureSourceId::External(textures[0])),
Some(TextureSourceId::External(textures[1])),
Some(TextureSourceId::External(textures[2])),
Some(TextureSourceId::External(textures[3]))
],
0
),
(
[
Some(TextureSourceId::External(textures[4])),
Some(TextureSourceId::External(textures[0])),
None,
None,
],
4,
),
]
);
assert_eq!(
case.buffers
.strips
.iter()
.map(|strip| (strip.paint_and_rect_flag >> EXTERNAL_TEXTURE_SLOT_SHIFT) & 0x3)
.collect::<Vec<_>>(),
[0, 1, 2, 3, 0, 1]
);
}
#[test]
fn texture_runs_coalesce_distinct_paints_for_same_texture() {
let [texture] = texture_ids();
let encoded = [external(texture), external(texture)];
let resolver = PaintResolver::new(&encoded, &[0, 3]);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 8, 8));
let mut draw = Draw::default();
case.rect(&mut draw, rect(0.0), indexed(0), resolver);
case.rect(&mut draw, rect(4.0), indexed(1), resolver);
assert_eq!(
run_states(&draw.external_texture_runs),
[(
[Some(TextureSourceId::External(texture)), None, None, None],
0,
)]
);
}
#[test]
fn texture_runs_include_atlas_images() {
let [texture] = texture_ids();
let mut image_cache = ImageCache::new_with_config(AtlasConfig::default());
let image_id = image_cache.allocate(1, 1, 0).unwrap();
let encoded = [external(texture), atlas_image(image_id)];
let resolver = PaintResolver::new(&encoded, &[0, 0]).with_image_cache(&image_cache);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 16, 8));
let mut draw = Draw::default();
for (x, paint_index) in [(0.0, 0), (4.0, 1), (8.0, 0)] {
case.rect(&mut draw, rect(x), indexed(paint_index), resolver);
}
assert_eq!(draw.strip_ranges.len(), 3);
assert_eq!(
draw.external_texture_runs[0].bindings.as_array(),
[
Some(TextureSourceId::External(texture)),
Some(TextureSourceId::Atlas(AtlasId::new(0))),
None,
None,
]
);
assert_eq!(draw.external_texture_runs[0].strips_start, 0);
}
#[test]
fn opaque_reverse_rebases_texture_runs() {
let textures: [TextureSourceId; 8] = external_sources();
let mut draw = OpaqueDraw::default();
for (x, texture_id) in [
(0, None),
(1, Some(textures[0])),
(2, Some(textures[0])),
(3, None),
(4, None),
(5, None),
(6, Some(textures[1])),
(7, Some(textures[1])),
(8, Some(textures[2])),
(9, None),
(10, Some(textures[2])),
(11, None),
(12, None),
(13, Some(textures[0])),
(14, None),
(15, Some(textures[1])),
(16, Some(textures[3])),
(17, None),
(18, Some(textures[0])),
(19, Some(textures[4])),
(20, None),
(21, Some(textures[4])),
(22, Some(textures[0])),
(23, Some(textures[5])),
(24, Some(textures[5])),
(25, None),
(26, Some(textures[6])),
(27, None),
(28, Some(textures[7])),
(29, Some(textures[7])),
(30, Some(textures[1])),
(31, None),
] {
draw.push(gpu_strip(x), texture_id);
}
assert_eq!(
run_states(draw.external_texture_runs()),
[
(
[
Some(textures[0]),
Some(textures[1]),
Some(textures[2]),
Some(textures[3]),
],
0,
),
(
[
Some(textures[4]),
Some(textures[0]),
Some(textures[5]),
Some(textures[6]),
],
19,
),
([Some(textures[7]), Some(textures[1]), None, None], 28,),
]
);
let original_slots = external_texture_slots(&draw);
draw.reverse();
assert_eq!(strip_xs(&draw), (0..32).rev().collect::<Vec<_>>());
assert_eq!(
external_texture_slots(&draw),
original_slots.into_iter().rev().collect::<Vec<_>>()
);
assert_eq!(
run_states(draw.external_texture_runs()),
[
([Some(textures[7]), Some(textures[1]), None, None], 0,),
(
[
Some(textures[4]),
Some(textures[0]),
Some(textures[5]),
Some(textures[6]),
],
4,
),
(
[
Some(textures[0]),
Some(textures[1]),
Some(textures[2]),
Some(textures[3]),
],
13,
),
]
);
}
#[test]
fn opaque_reverse_without_texture_runs() {
let mut draw = OpaqueDraw::default();
for x in 0..3 {
draw.push(gpu_strip(x), None);
}
draw.reverse();
assert_eq!(strip_xs(&draw), [2, 1, 0]);
assert!(draw.external_texture_runs().is_empty());
}
#[test]
fn draw_clear() {
let [texture_id] = texture_ids();
let encoded = [external(texture_id)];
let offsets = [0];
let resolver = PaintResolver::new(&encoded, &offsets);
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 8, 8));
let mut draw = Draw::default();
case.rect(&mut draw, rect(0.0), indexed(0), resolver);
case.layer(&mut draw, layer(RectU16::new(0, 0, 8, 8)));
assert_eq!(draw.strip_ranges.len(), 2);
assert_eq!(draw.external_texture_runs.len(), 1);
assert!(draw.has_child_layer);
draw.clear();
assert_eq!(draw.strip_ranges.len(), 0);
assert!(draw.external_texture_runs.is_empty());
assert!(!draw.has_child_layer);
case.rect(&mut draw, rect(0.0), indexed(0), resolver);
assert_eq!(draw.strip_ranges.len(), 1);
assert_eq!(draw.external_texture_runs[0].strips_start, 0);
}
#[test]
fn opaque_routing() {
let mut user_case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 16, 8));
let mut user_draw = Draw::default();
user_case.rect(&mut user_draw, rect(0.0), solid(1.0), no_paints());
user_case.rect(
&mut user_draw,
Rect::new(8.25, 0.0, 12.0, 4.0),
solid(1.0),
no_paints(),
);
assert_eq!(user_case.buffers.opaque.strips().len(), 1);
assert_eq!(user_draw.strip_ranges.len(), 1);
let mut atlas_case = DrawCase::new(RootTarget::AtlasLayer, RectU16::new(0, 0, 8, 8));
let mut atlas_draw = Draw::default();
atlas_case.rect(&mut atlas_draw, rect(0.0), solid(1.0), no_paints());
assert!(atlas_case.buffers.opaque.is_empty());
assert_eq!(atlas_draw.strip_ranges.len(), 1);
}
#[test]
fn child_binding() {
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 8, 8));
let mut draw = Draw::default();
case.layer(&mut draw, layer(RectU16::new(0, 0, 8, 8)));
assert!(draw.has_child_layer);
assert_eq!(draw.strip_ranges.len(), 1);
}
#[test]
fn depth_progression() {
let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 64, 8));
let mut draw = Draw::default();
let opacity = [0.5, 0.5, 1.0, 0.5, 0.5, 1.0, 0.5];
let positions = [0.0, 8.0, 16.0, 24.0, 32.0, 40.0, 48.0];
for (x, alpha) in positions.into_iter().zip(opacity) {
case.rect(&mut draw, rect(x), solid(alpha), no_paints());
}
assert_eq!(
case.buffers
.strips
.ranged(&draw.strip_ranges)
.iter()
.map(|strip| strip.depth_index)
.collect::<Vec<_>>(),
[0, 0, 1, 1, 2]
);
assert_eq!(
case.buffers
.opaque
.strips()
.iter()
.map(|strip| strip.depth_index)
.collect::<Vec<_>>(),
[1, 2]
);
}
}