mod allocate;
mod cursor;
pub(crate) mod execute;
pub(crate) mod round;
#[cfg(test)]
mod schedule_tests;
#[cfg(test)]
mod test_support;
use self::allocate::{Atlases, LayerAllocationRequest};
use self::cursor::Cursor;
pub(crate) use self::execute::{Backend, execute};
use self::round::{
BlendOp, FilterOp, FilterTextureRegions, Round, RoundStage, Rounds, SchedulePoint,
};
use crate::draw::{Draw, DrawBuffers, DrawBuilder, DrawState, RectU16Ext};
use crate::filter::{FilterContext, FilterPassPlan, PreparedGpuFilter};
use crate::paint::PaintResolver;
use crate::scene::RecordedDraw;
use crate::schedule::allocate::AllocatedTextureRegion;
use crate::target::{
DrawTarget, LayerTextureRegion, RootTarget, RoundBindings, TextureParity, TextureRegion,
};
use crate::{IntermediateTextureError, RenderError, Scene, blend::BlendStrip};
use alloc::vec::Vec;
use vello_common::filter::FilterLayerPlacement;
use vello_common::geometry::{RectU16, SizeU16};
use vello_common::peniko::BlendMode;
use vello_common::record::{CommandRecorder, LayerProps, Node, RecordedLayer, RecordedLayerKind};
use vello_common::strip::visit_strip_fill_segments;
use vello_common::strip_generator::StripStorage;
use vello_common::tile::Tile;
const REGULAR_LAYER_KIND: RecordedLayerKind = RecordedLayerKind::Regular;
#[derive(Debug)]
pub(crate) struct Schedule {
rounds: Rounds,
intermediate_textures: IntermediateTextureRequirements,
}
impl Schedule {
pub(crate) fn try_new(
storage: &mut ScheduleStorage,
scene: &Scene,
root_output_target: RootTarget,
use_depth_buffer: bool,
paint_resolver: PaintResolver<'_>,
texture_size: SizeU16,
backend_allocations: IntermediateTextureAllocations,
max_textures: Option<usize>,
) -> Result<Self, RenderError> {
storage.clear();
let strip_storage = scene.strip_storage.borrow();
let scene_bbox = RectU16::new(
0,
0,
scene.recorder.scene_size.width(),
scene.recorder.scene_size.height(),
);
let scheduler = Scheduler::new(
&scene.recorder,
scene_bbox,
&strip_storage,
root_output_target,
use_depth_buffer,
paint_resolver,
texture_size,
storage,
);
let schedule = scheduler.build()?;
schedule
.intermediate_textures
.validate(backend_allocations, max_textures)?;
Ok(schedule)
}
pub(crate) fn intermediate_texture_requirements(&self) -> IntermediateTextureRequirements {
self.intermediate_textures
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct IntermediateTextureRequirements {
pub(crate) size: SizeU16,
pub(crate) allocations: IntermediateTextureAllocations,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub(crate) struct IntermediateTextureAllocations {
pub(crate) layer_pages: [usize; 2],
pub(crate) scratch: bool,
}
impl IntermediateTextureAllocations {
pub(crate) fn combine(self, existing: Self) -> Self {
Self {
layer_pages: core::array::from_fn(|index| {
self.layer_pages[index].max(existing.layer_pages[index])
}),
scratch: self.scratch || existing.scratch,
}
}
fn texture_count(self) -> usize {
self.layer_pages.into_iter().sum::<usize>() + usize::from(self.scratch)
}
}
impl IntermediateTextureRequirements {
pub(crate) fn validate(
self,
existing: IntermediateTextureAllocations,
max_textures: Option<usize>,
) -> Result<(), IntermediateTextureError> {
let retained_textures = self.allocations.combine(existing).texture_count();
if let Some(max) = max_textures
&& retained_textures > max
{
return Err(IntermediateTextureError::LimitReached {
required: retained_textures,
max,
});
}
Ok(())
}
}
#[derive(Debug)]
struct Scheduler<'a, 'p> {
recorder: &'a CommandRecorder<RecordedDraw>,
scene_bbox: RectU16,
strip_storage: &'a StripStorage,
root_render_target: RootTarget,
use_depth_buffer: bool,
paint_resolver: PaintResolver<'a>,
cursor: Cursor,
texture_size: SizeU16,
storage: &'p mut ScheduleStorage,
}
impl<'a, 'p> Scheduler<'a, 'p> {
fn new(
recorder: &'a CommandRecorder<RecordedDraw>,
scene_bbox: RectU16,
strip_storage: &'a StripStorage,
root_render_target: RootTarget,
use_depth_buffer: bool,
paint_resolver: PaintResolver<'a>,
texture_size: SizeU16,
storage: &'p mut ScheduleStorage,
) -> Self {
Self {
recorder,
scene_bbox,
strip_storage,
root_render_target,
use_depth_buffer,
paint_resolver,
cursor: Cursor::new(Atlases::new(texture_size)),
texture_size,
storage,
}
}
fn build(mut self) -> Result<Schedule, RenderError> {
let mut rounds = Rounds::default();
self.schedule_root(&mut rounds)?;
self.storage.buffers.draw_buffers.opaque.reverse();
#[cfg(any(test, debug_assertions))]
rounds.validate(&self.storage.buffers);
let intermediate_textures = IntermediateTextureRequirements {
size: self.texture_size,
allocations: IntermediateTextureAllocations {
layer_pages: rounds.required_layer_pages(),
scratch: self.cursor.scratch_texture(),
},
};
Ok(Schedule {
rounds,
intermediate_textures,
})
}
fn schedule_root(&mut self, rounds: &mut Rounds) -> Result<(), RenderError> {
let target = self.root_render_target;
let mut state = TargetScheduleState::new(
target,
self.cursor.current_round(),
self.scene_bbox,
self.use_depth_buffer,
);
if self.recorder.root_is_blend_target {
let opened_layer = self.open_root_layer();
let layer = self.schedule_layer(opened_layer, rounds)?;
let draw_point = rounds.build_draw(
&mut state,
&mut self.storage.buffers.draw_buffers,
Some(&layer),
|builder| {
builder.push_layer_fill(layer.sample_region, 1.0, None, self.strip_storage);
},
);
self.clear_and_release_allocation(layer.allocation, draw_point.round, rounds);
} else {
for cmd in &self.recorder.nodes {
self.push_draws(&cmd.draws, &mut state, rounds);
let child = self.schedule_child_layer(cmd, state.draw_state.target_bbox, rounds)?;
if let Some(child) = child {
self.compose_simple_layer(child.props, child.layer, &mut state, rounds);
}
}
};
Ok(())
}
fn schedule_layer(
&mut self,
mut layer: OpenLayer<'a>,
rounds: &mut Rounds,
) -> Result<ScheduledLayer, RenderError> {
for cmd in layer.cmds {
let child = self.schedule_child_layer(cmd, layer.sample_placement.dest_bbox, rounds)?;
let target = self.ensure_layer_target(&mut layer)?;
self.push_draws(&cmd.draws, &mut target.schedule_state, rounds);
if let Some(child) = child {
self.compose_layer(child.props, child.layer, &mut target.schedule_state, rounds)?;
}
}
self.ensure_layer_target(&mut layer)?;
let mut target = layer.target.take().unwrap();
let region = target.schedule_state.draw_state.target;
if let Some(filter) = target.filter {
let temporary = self.cursor.allocate_layer(LayerAllocationRequest::new(
region.texture.rect,
layer.kind,
region.texture.target.texture_parity.opposite(),
))?;
let textures = FilterTextureRegions::new(region.texture, temporary.allocation.region);
if filter.data.needs_copy_pass() {
self.cursor.require_scratch_texture();
}
let mut filter_point =
target.schedule_state.ready
.max(SchedulePoint::start(temporary.round_idx))
.after(RoundStage::filter(region.texture.target.texture_parity));
filter_point = rounds.bind_at_or_after(filter_point, textures.round_bindings());
rounds.ensure_exists(filter_point.round);
rounds.round_mut(filter_point.round).push_filter_op(
region.texture.target.texture_parity,
&mut self.storage.buffers,
FilterOp {
textures,
filter_data_offset: filter.data_offset,
gpu_filter: filter.data,
},
);
self.clear_and_release_allocation(temporary.allocation, filter_point.round, rounds);
target.schedule_state.ready = filter_point;
}
let scheduled = ScheduledLayer {
sample_region: layer.sample_placement.resolve_sample_region(region),
allocation: target.allocation,
ready: target.schedule_state.ready,
};
Ok(scheduled)
}
fn schedule_child_layer(
&mut self,
cmd: &Node,
parent_bounds: RectU16,
rounds: &mut Rounds,
) -> Result<Option<PreparedChild<'a>>, RenderError> {
let Some(layer_id) = cmd.layer else {
return Ok(None);
};
let layer = &self.recorder.layers[layer_id as usize];
let mut bbox = if layer.bbox.is_empty() {
if layer.props.blend_mode.is_destructive() {
parent_bounds
} else {
return Ok(None);
}
} else {
layer.bbox
};
if matches!(layer.kind, RecordedLayerKind::Regular) {
bbox = layer
.props
.clip_path
.as_ref()
.map_or(bbox, |clip| bbox.intersect(clip.bbox));
}
if bbox.is_empty() {
return Ok(None);
}
let opened_layer = self.open_layer(layer, bbox);
let scheduled = self.schedule_layer(opened_layer, rounds)?;
Ok(Some(PreparedChild {
props: &layer.props,
layer: scheduled,
}))
}
fn open_layer(&self, layer: &'a RecordedLayer, bbox: RectU16) -> OpenLayer<'a> {
let sample_placement = match &layer.kind {
RecordedLayerKind::Regular => LayerSamplePlacement::regular(bbox),
RecordedLayerKind::Filter { placement, .. } => LayerSamplePlacement::filter(*placement),
};
OpenLayer {
cmds: &layer.nodes,
kind: &layer.kind,
texture_parity: self.layer_texture_parity(layer.depth),
bbox,
sample_placement,
target: None,
}
}
fn open_root_layer(&self) -> OpenLayer<'a> {
OpenLayer {
cmds: &self.recorder.nodes,
kind: ®ULAR_LAYER_KIND,
texture_parity: TextureParity::Odd,
bbox: self.scene_bbox,
sample_placement: LayerSamplePlacement::regular(self.scene_bbox),
target: None,
}
}
fn layer_texture_parity(&self, layer_depth: usize) -> TextureParity {
TextureParity::from_parity(layer_depth + usize::from(self.recorder.root_is_blend_target))
}
fn push_draws<T: ScheduleTarget>(
&mut self,
draws: &core::ops::Range<u32>,
state: &mut TargetScheduleState<T>,
rounds: &mut Rounds,
) {
if draws.is_empty() {
return;
}
rounds.build_draw(
state,
&mut self.storage.buffers.draw_buffers,
None,
|builder| {
for draw in &self.recorder.draws[draws.start as usize..draws.end as usize] {
builder.push_draw(draw, self.strip_storage, self.paint_resolver);
}
},
);
}
fn compose_layer(
&mut self,
child_props: &LayerProps,
child_layer: ScheduledLayer,
parent_state: &mut TargetScheduleState<LayerTextureRegion>,
rounds: &mut Rounds,
) -> Result<(), RenderError> {
let blend_mode = child_props.blend_mode;
let opacity = child_props.opacity;
if blend_mode == BlendMode::default() {
self.compose_simple_layer(child_props, child_layer, parent_state, rounds);
return Ok(());
}
let parent_region = parent_state.draw_state.target;
let child_region = child_layer.sample_region.crop_to(parent_region.layer_bbox);
let mut blend_bbox = if blend_mode.is_destructive() {
parent_region.layer_bbox
} else {
child_region.layer_bbox
};
if let Some(clip_path) = &child_props.clip_path {
blend_bbox = blend_bbox.intersect(clip_path.bbox);
}
if blend_bbox.is_empty() {
let round =
child_layer.ready
.round.max(self.cursor.current_round());
let bindings = RoundBindings::new(child_layer.allocation.region.target);
let point = rounds.bind_at_or_after(SchedulePoint::start(round), bindings);
self.clear_and_release_allocation(child_layer.allocation, point.round, rounds);
return Ok(());
}
let clip_strips = child_props.clip_path.as_ref().map(|clip_path| {
let start = self.storage.buffers.blend_strips.len();
let strips = &self.strip_storage.strips[clip_path.strip_range.clone()];
let geometry_shift = parent_region.geometry_shift();
let tile_bounds = blend_bbox.to_tile_bounds();
visit_strip_fill_segments(
strips,
tile_bounds,
&mut self.storage.buffers.blend_strips,
|blend_strips, segment| {
blend_strips.push(BlendStrip::from_fill_segment(
segment.shift(geometry_shift),
Some(segment.alpha_idx / u32::from(Tile::HEIGHT)),
));
},
|blend_strips, segment| {
blend_strips.push(BlendStrip::from_fill_segment(
segment.shift(geometry_shift),
None,
));
},
);
let end = self.storage.buffers.blend_strips.len();
u32::try_from(start).unwrap()..u32::try_from(end).unwrap()
});
let parent_texture_parity = parent_region.texture.target.texture_parity;
self.cursor.require_scratch_texture();
let blend_stage = RoundStage::blend(parent_texture_parity);
let blend_binding = RoundBindings::new(parent_region.texture.target)
.merge(RoundBindings::new(child_region.texture.target))
.expect("parent and child layers must have compatible texture parities");
let mut blend_point =
parent_state.ready
.max(child_layer.ready)
.after(blend_stage);
blend_point = rounds.bind_at_or_after(blend_point, blend_binding);
rounds.ensure_exists(blend_point.round);
rounds.round_mut(blend_point.round).push_blend_op(
parent_texture_parity,
&mut self.storage.buffers,
BlendOp {
parent_region,
child_region,
blend_bbox,
blend_mode,
opacity,
clip_strips,
},
);
self.clear_and_release_allocation(child_layer.allocation, blend_point.round, rounds);
parent_state.ready = blend_point;
Ok(())
}
fn compose_simple_layer<T: ScheduleTarget>(
&mut self,
props: &LayerProps,
child_layer: ScheduledLayer,
parent_state: &mut TargetScheduleState<T>,
rounds: &mut Rounds,
) {
let draw_point = rounds.build_draw(
parent_state,
&mut self.storage.buffers.draw_buffers,
Some(&child_layer),
|builder| {
builder.push_layer_fill(
child_layer.sample_region,
props.opacity,
props.clip_path.as_ref(),
self.strip_storage,
);
},
);
self.clear_and_release_allocation(child_layer.allocation, draw_point.round, rounds);
}
fn clear_and_release_allocation(
&mut self,
allocation: AllocatedTextureRegion,
round_idx: usize,
rounds: &mut Rounds,
) {
let clear_region = allocation.clear_region();
assert_eq!(
rounds
.round_mut(round_idx)
.texture_binding
.layer_id(clear_region.target.texture_parity),
Some(clear_region.target),
"the clear round must bind the allocation's exact texture page"
);
rounds.push_layer_clear(
round_idx,
clear_region.target.texture_parity,
clear_region.rect,
);
self.cursor.release(allocation, round_idx);
}
fn ensure_layer_target<'b>(
&mut self,
layer: &'b mut OpenLayer<'a>,
) -> Result<&'b mut LayerTarget, RenderError> {
if layer.target.is_none() {
let filter = match layer.kind {
RecordedLayerKind::Filter { filter_data, .. } => {
Some(self.storage.filter_context.push(filter_data))
}
RecordedLayerKind::Regular => None,
};
let request = LayerAllocationRequest::new(layer.bbox, layer.kind, layer.texture_parity);
let allocation = self.cursor.allocate_layer(request)?;
let round = allocation.round_idx;
let allocation = allocation.allocation;
let region = LayerTextureRegion {
texture: allocation.region,
layer_bbox: layer.bbox,
};
let schedule_state = TargetScheduleState::new_layer(region, round);
layer.target = Some(LayerTarget {
allocation,
filter,
schedule_state,
});
}
Ok(layer.target.as_mut().unwrap())
}
}
#[must_use = "scheduled layers must be released"]
#[derive(Debug)]
struct ScheduledLayer {
allocation: AllocatedTextureRegion,
sample_region: LayerTextureRegion,
ready: SchedulePoint,
}
#[derive(Debug)]
struct PreparedChild<'a> {
props: &'a LayerProps,
layer: ScheduledLayer,
}
#[derive(Debug)]
struct OpenLayer<'a> {
cmds: &'a [Node],
kind: &'a RecordedLayerKind,
texture_parity: TextureParity,
bbox: RectU16,
sample_placement: LayerSamplePlacement,
target: Option<LayerTarget>,
}
#[derive(Debug, Clone, Copy)]
struct LayerSamplePlacement {
src_offset: (u16, u16),
dest_bbox: RectU16,
}
impl LayerSamplePlacement {
fn regular(bbox: RectU16) -> Self {
Self {
src_offset: (0, 0),
dest_bbox: bbox,
}
}
fn filter(placement: FilterLayerPlacement) -> Self {
Self {
src_offset: placement.src_origin(),
dest_bbox: placement.dest_bbox(),
}
}
fn resolve_sample_region(self, allocation: LayerTextureRegion) -> LayerTextureRegion {
let x0 = allocation.texture.rect.x0 + self.src_offset.0;
let y0 = allocation.texture.rect.y0 + self.src_offset.1;
LayerTextureRegion {
texture: TextureRegion {
target: allocation.texture.target,
rect: RectU16::new(
x0,
y0,
x0 + self.dest_bbox.width(),
y0 + self.dest_bbox.height(),
),
},
layer_bbox: self.dest_bbox,
}
}
}
#[derive(Debug)]
struct LayerTarget {
allocation: AllocatedTextureRegion,
filter: Option<PreparedGpuFilter>,
schedule_state: TargetScheduleState<LayerTextureRegion>,
}
impl Rounds {
fn build_draw<T: ScheduleTarget>(
&mut self,
state: &mut TargetScheduleState<T>,
draw_buffers: &mut DrawBuffers,
sampled_layer: Option<&ScheduledLayer>,
f: impl FnOnce(&mut DrawBuilder<'_, T>),
) -> SchedulePoint {
let sampled_layer_round_bindings = sampled_layer
.map_or_else(RoundBindings::default, |layer| {
RoundBindings::new(layer.sample_region.texture.target)
});
let round_bindings = state
.draw_state
.target
.round_bindings()
.merge(sampled_layer_round_bindings)
.expect("draw target and sampled layer must have compatible texture parities");
let mut draw_point = sampled_layer.map_or_else(
|| state.next_draw(),
|layer| state.next_draw_after(layer.ready),
);
draw_point = self.bind_at_or_after(draw_point, round_bindings);
state.ready = draw_point;
self.ensure_exists(draw_point.round);
let target_round = self.round_mut(draw_point.round);
let target_draw = state.draw_state.target.draw_mut(target_round);
let mut builder = DrawBuilder::new(target_draw, draw_buffers, &mut state.draw_state);
f(&mut builder);
draw_point
}
}
#[derive(Debug, Default)]
pub(crate) struct ScheduleBuffers {
pub(crate) draw_buffers: DrawBuffers,
pub(crate) filter_ops: Vec<FilterOp>,
pub(crate) blend_ops: Vec<BlendOp>,
pub(crate) blend_strips: Vec<BlendStrip>,
}
impl ScheduleBuffers {
fn clear(&mut self) {
self.draw_buffers.clear();
self.filter_ops.clear();
self.blend_ops.clear();
self.blend_strips.clear();
}
}
#[derive(Debug, Default)]
pub(crate) struct ScheduleStorage {
pub(crate) buffers: ScheduleBuffers,
pub(crate) filter_context: FilterContext,
filter_pass_plan: FilterPassPlan,
}
impl ScheduleStorage {
fn clear(&mut self) {
self.buffers.clear();
self.filter_context.clear();
}
}
#[derive(Debug)]
struct TargetScheduleState<T: ScheduleTarget> {
draw_state: DrawState<T>,
ready: SchedulePoint,
}
impl<T: ScheduleTarget> TargetScheduleState<T> {
fn new(target: T, start_round: usize, target_bbox: RectU16, use_depth_buffer: bool) -> Self {
Self {
draw_state: DrawState::new(target, target_bbox, use_depth_buffer),
ready: SchedulePoint::start(start_round),
}
}
fn next_draw(&self) -> SchedulePoint {
self.ready.after_or_at(self.draw_state.target.draw_stage())
}
fn next_draw_after(&self, dependency: SchedulePoint) -> SchedulePoint {
dependency
.after_or_at(self.draw_state.target.draw_stage())
.max(self.next_draw())
}
}
impl TargetScheduleState<LayerTextureRegion> {
fn new_layer(target: LayerTextureRegion, base_round: usize) -> Self {
Self::new(target, base_round, target.layer_bbox, false)
}
}
trait ScheduleTarget: DrawTarget {
fn draw_mut<'a>(&self, round: &'a mut Round) -> &'a mut Draw;
fn draw_stage(&self) -> RoundStage;
fn round_bindings(&self) -> RoundBindings;
}
impl ScheduleTarget for RootTarget {
fn draw_mut<'a>(&self, round: &'a mut Round) -> &'a mut Draw {
round.root_draw_mut()
}
fn draw_stage(&self) -> RoundStage {
RoundStage::RootDraw
}
fn round_bindings(&self) -> RoundBindings {
RoundBindings::default()
}
}
impl ScheduleTarget for LayerTextureRegion {
fn draw_mut<'a>(&self, round: &'a mut Round) -> &'a mut Draw {
round.layer_draw_mut(self.texture.target.texture_parity)
}
fn draw_stage(&self) -> RoundStage {
RoundStage::draw(self.texture.target.texture_parity)
}
fn round_bindings(&self) -> RoundBindings {
RoundBindings::new(self.texture.target)
}
}