use std::{
collections::{HashMap, VecDeque},
ops::Range,
};
use indexmap::IndexMap;
use smallvec::{smallvec, SmallVec};
use tracing::{info_span, instrument, trace};
use crate::{
backend::renderer::{element::RenderElementPresentationState, Frame},
output::{Output, OutputModeSource, OutputNoMode},
utils::{Buffer as BufferCoords, Physical, Rectangle, Scale, Size, Transform},
};
use super::{
element::{Element, Id, RenderElement, RenderElementState, RenderElementStates},
sync::SyncPoint,
utils::CommitCounter,
Color32F,
};
use super::{Renderer, Texture};
mod shaper;
use shaper::DamageShaper;
const MAX_AGE: usize = 4;
#[derive(Debug, Clone, Copy)]
struct ElementInstanceState {
last_src: Rectangle<f64, BufferCoords>,
last_geometry: Rectangle<i32, Physical>,
last_transform: Transform,
last_alpha: f32,
last_z_index: usize,
}
impl ElementInstanceState {
#[inline]
fn matches(
&self,
src: Rectangle<f64, BufferCoords>,
geometry: Rectangle<i32, Physical>,
transform: Transform,
alpha: f32,
z_index: usize,
) -> bool {
self.last_src == src
&& self.last_geometry == geometry
&& self.last_transform == transform
&& self.last_alpha == alpha
&& self.last_z_index == z_index
}
}
#[derive(Debug, Clone)]
struct ElementState {
last_commit: CommitCounter,
last_instances: SmallVec<[ElementInstanceState; 1]>,
}
impl ElementState {
#[inline]
fn instance_matches(
&self,
src: Rectangle<f64, BufferCoords>,
geometry: Rectangle<i32, Physical>,
transform: Transform,
alpha: f32,
z_index: usize,
) -> bool {
self.last_instances
.iter()
.any(|instance| instance.matches(src, geometry, transform, alpha, z_index))
}
}
#[derive(Debug, Default)]
struct RendererState {
transform: Option<Transform>,
size: Option<Size<i32, Physical>>,
elements: IndexMap<Id, ElementState>,
old_damage: VecDeque<Vec<Rectangle<i32, Physical>>>,
opaque_regions: Vec<Rectangle<i32, Physical>>,
clear_color: Option<Color32F>,
}
#[derive(Debug)]
pub struct OutputDamageTracker {
mode: OutputModeSource,
last_state: RendererState,
damage_shaper: DamageShaper,
damage: Vec<Rectangle<i32, Physical>>,
element_damage: Vec<Rectangle<i32, Physical>>,
opaque_regions: Vec<Rectangle<i32, Physical>>,
opaque_regions_index: Vec<Range<usize>>,
element_opaque_regions: Vec<Rectangle<i32, Physical>>,
element_visible_area_workhouse: Vec<Rectangle<i32, Physical>>,
span: tracing::Span,
}
#[derive(thiserror::Error)]
pub enum Error<E: std::error::Error> {
#[error(transparent)]
Rendering(E),
#[error(transparent)]
OutputNoMode(#[from] OutputNoMode),
}
#[derive(Debug)]
pub struct RenderOutputResult<'a> {
pub sync: SyncPoint,
pub damage: Option<&'a Vec<Rectangle<i32, Physical>>>,
pub states: RenderElementStates,
}
impl RenderOutputResult<'_> {
fn skipped(states: RenderElementStates) -> Self {
Self {
sync: SyncPoint::signaled(),
damage: None,
states,
}
}
}
impl<E: std::error::Error> std::fmt::Debug for Error<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Rendering(err) => std::fmt::Debug::fmt(err, f),
Error::OutputNoMode(err) => std::fmt::Debug::fmt(err, f),
}
}
}
impl OutputDamageTracker {
pub fn new(
size: impl Into<Size<i32, Physical>>,
scale: impl Into<Scale<f64>>,
transform: Transform,
) -> Self {
Self {
mode: OutputModeSource::Static {
size: size.into(),
scale: scale.into(),
transform,
},
last_state: Default::default(),
damage_shaper: Default::default(),
damage: Default::default(),
element_damage: Default::default(),
opaque_regions: Default::default(),
opaque_regions_index: Default::default(),
element_opaque_regions: Default::default(),
element_visible_area_workhouse: Default::default(),
span: info_span!("renderer_damage"),
}
}
pub fn from_output(output: &Output) -> Self {
Self {
mode: OutputModeSource::Auto(output.clone()),
damage_shaper: Default::default(),
damage: Default::default(),
element_damage: Default::default(),
opaque_regions: Default::default(),
opaque_regions_index: Default::default(),
element_opaque_regions: Default::default(),
element_visible_area_workhouse: Default::default(),
last_state: Default::default(),
span: info_span!("renderer_damage", output = output.name()),
}
}
pub fn from_mode_source(output_mode_source: impl Into<OutputModeSource>) -> Self {
Self {
mode: output_mode_source.into(),
span: info_span!("render_damage"),
damage_shaper: Default::default(),
damage: Default::default(),
element_damage: Default::default(),
element_opaque_regions: Default::default(),
opaque_regions: Default::default(),
opaque_regions_index: Default::default(),
element_visible_area_workhouse: Default::default(),
last_state: Default::default(),
}
}
pub fn mode(&self) -> &OutputModeSource {
&self.mode
}
#[instrument(level = "trace", parent = &self.span, skip(renderer, framebuffer, elements, clear_color))]
#[profiling::function]
pub fn render_output<E, R>(
&mut self,
renderer: &mut R,
framebuffer: &mut R::Framebuffer<'_>,
age: usize,
elements: &[E],
clear_color: impl Into<Color32F>,
) -> Result<RenderOutputResult<'_>, Error<R::Error>>
where
E: RenderElement<R>,
R: Renderer,
R::TextureId: Texture,
{
let clear_color = clear_color.into();
let (output_size, output_scale, output_transform) =
std::convert::TryInto::<(Size<i32, Physical>, Scale<f64>, Transform)>::try_into(&self.mode)?;
let output_transform = output_transform.invert();
let output_geo = Rectangle::from_size(output_transform.transform_size(output_size));
let mut render_elements: Vec<&E> = Vec::with_capacity(elements.len());
let states = self.damage_output_internal(
age,
elements,
output_scale,
output_transform,
output_geo,
Some(clear_color),
&mut render_elements,
);
if self.damage.is_empty() {
trace!("no damage, skipping rendering");
return Ok(RenderOutputResult::skipped(states));
}
trace!(
"rendering with damage {:?} and opaque regions {:?}",
self.damage,
self.opaque_regions
);
let render_res = (|| {
let mut element_damage = std::mem::take(&mut self.element_damage);
let mut element_opaque_regions = std::mem::take(&mut self.element_opaque_regions);
let mut frame = renderer.render(framebuffer, output_size, output_transform)?;
element_damage.clear();
element_damage.extend_from_slice(&self.damage);
element_damage =
Rectangle::subtract_rects_many_in_place(element_damage, self.opaque_regions.iter().copied());
trace!("clearing damage {:?}", element_damage);
frame.clear(clear_color, &element_damage)?;
for (z_index, element) in render_elements.iter().rev().enumerate() {
let element_id = element.id();
let element_geometry = element.geometry(output_scale);
element_damage.clear();
element_damage.extend(
self.damage
.iter()
.filter_map(|d| d.intersection(element_geometry)),
);
let element_opaque_regions_range =
self.opaque_regions_index.iter().rev().nth(z_index).unwrap();
element_damage = Rectangle::subtract_rects_many_in_place(
element_damage,
self.opaque_regions[..element_opaque_regions_range.start]
.iter()
.copied(),
);
element_damage.iter_mut().for_each(|d| {
d.loc -= element_geometry.loc;
});
if element_damage.is_empty() {
trace!(
"skipping rendering element {:?} with geometry {:?}, no damage",
element_id,
element_geometry
);
continue;
}
element_opaque_regions.clear();
element_opaque_regions.extend(
self.opaque_regions[element_opaque_regions_range.start..element_opaque_regions_range.end]
.iter()
.copied()
.map(|mut rect| {
rect.loc -= element_geometry.loc;
rect
}),
);
trace!(
"rendering element {:?} with geometry {:?} and damage {:?}",
element_id,
element_geometry,
element_damage,
);
element.draw(
&mut frame,
element.src(),
element_geometry,
&element_damage,
&element_opaque_regions,
)?;
}
std::mem::swap(&mut self.element_damage, &mut element_damage);
std::mem::swap(&mut self.element_opaque_regions, &mut element_opaque_regions);
frame.finish()
})();
match render_res {
Ok(sync) => Ok(RenderOutputResult {
sync,
damage: Some(&self.damage),
states,
}),
Err(err) => {
self.last_state = Default::default();
Err(Error::Rendering(err))
}
}
}
#[instrument(level = "trace", parent = &self.span, skip(elements))]
#[profiling::function]
pub fn damage_output<'a, 'e, E>(
&'a mut self,
age: usize,
elements: &'e [E],
) -> Result<(Option<&'a Vec<Rectangle<i32, Physical>>>, RenderElementStates), OutputNoMode>
where
E: Element,
{
let (output_size, output_scale, output_transform) = self.mode.clone().try_into()?;
let output_transform = output_transform.invert();
let output_geo = Rectangle::from_size(output_transform.transform_size(output_size));
let mut render_elements: Vec<&E> = Vec::with_capacity(elements.len());
let states = self.damage_output_internal(
age,
elements,
output_scale,
output_transform,
output_geo,
self.last_state.clear_color,
&mut render_elements,
);
if self.damage.is_empty() {
Ok((None, states))
} else {
Ok((Some(&self.damage), states))
}
}
#[allow(clippy::too_many_arguments)]
#[profiling::function]
fn damage_output_internal<'a, E>(
&mut self,
age: usize,
elements: &'a [E],
output_scale: Scale<f64>,
output_transform: Transform,
output_geo: Rectangle<i32, Physical>,
clear_color: Option<Color32F>,
render_elements: &mut Vec<&'a E>,
) -> RenderElementStates
where
E: Element,
{
self.damage.clear();
self.opaque_regions.clear();
self.opaque_regions_index.clear();
let mut element_render_states = RenderElementStates {
states: HashMap::with_capacity(elements.len()),
};
let mut element_damage = std::mem::take(&mut self.element_damage);
let mut element_visible_area_workhouse = std::mem::take(&mut self.element_visible_area_workhouse);
for element in elements.iter() {
let element_id = element.id();
let element_loc = element.geometry(output_scale).loc;
let element_output_geometry = match element.geometry(output_scale).intersection(output_geo) {
Some(geo) => geo,
None => continue,
};
element_visible_area_workhouse.clear();
element_visible_area_workhouse.push(element_output_geometry);
element_visible_area_workhouse = Rectangle::subtract_rects_many_in_place(
element_visible_area_workhouse,
self.opaque_regions.iter().copied(),
);
let element_visible_area = element_visible_area_workhouse
.iter()
.fold(0usize, |acc, item| acc + (item.size.w * item.size.h) as usize);
if element_visible_area == 0 {
if !element_render_states.states.contains_key(element_id) {
element_render_states
.states
.insert(element_id.clone(), RenderElementState::skipped());
}
continue;
}
let element_output_damage = element
.damage_since(
output_scale,
self.last_state.elements.get(element_id).map(|s| s.last_commit),
)
.into_iter()
.map(|mut d| {
d.loc += element_loc;
d
})
.filter_map(|geo| geo.intersection(output_geo));
self.damage.extend(element_output_damage);
let element_opaque_regions_start_index = self.opaque_regions.len();
let element_opaque_regions = element
.opaque_regions(output_scale)
.into_iter()
.map(|mut region| {
region.loc += element_loc;
region
})
.filter_map(|geo| geo.intersection(output_geo));
self.opaque_regions.extend(element_opaque_regions);
let element_opaque_regions_end_index = self.opaque_regions.len();
self.opaque_regions_index
.push(element_opaque_regions_start_index..element_opaque_regions_end_index);
render_elements.push(element);
if let Some(state) = element_render_states.states.get_mut(element_id) {
if matches!(state.presentation_state, RenderElementPresentationState::Skipped) {
*state = RenderElementState::rendered(element_visible_area);
} else {
state.visible_area += element_visible_area;
}
} else {
element_render_states.states.insert(
element_id.clone(),
RenderElementState::rendered(element_visible_area),
);
}
}
std::mem::swap(
&mut self.element_visible_area_workhouse,
&mut element_visible_area_workhouse,
);
let elements_gone = self.last_state.elements.iter().filter(|(id, _)| {
element_render_states
.states
.get(id)
.map(|state| state.presentation_state == RenderElementPresentationState::Skipped)
.unwrap_or(true)
});
for (_, state) in elements_gone {
self.damage.extend(
state
.last_instances
.iter()
.filter_map(|i| i.last_geometry.intersection(output_geo)),
);
}
for (z_index, element) in render_elements.iter().enumerate() {
let element_src = element.src();
let element_geometry = element.geometry(output_scale);
let element_transform = element.transform();
let element_alpha = element.alpha();
let element_last_state = self.last_state.elements.get(element.id());
if element_last_state
.map(|s| {
!s.instance_matches(
element_src,
element_geometry,
element_transform,
element_alpha,
z_index,
)
})
.unwrap_or(true)
{
if let Some(intersection) = element_geometry.intersection(output_geo) {
self.damage.push(intersection);
}
if let Some(state) = element_last_state {
self.damage.extend(
state
.last_instances
.iter()
.filter_map(|i| i.last_geometry.intersection(output_geo)),
);
}
}
}
element_damage.clear();
element_damage.extend_from_slice(&self.last_state.opaque_regions);
element_damage =
Rectangle::subtract_rects_many_in_place(element_damage, self.opaque_regions.iter().copied());
self.damage.extend_from_slice(&element_damage);
std::mem::swap(&mut self.element_damage, &mut element_damage);
if self.last_state.size != Some(output_geo.size)
|| self.last_state.transform != Some(output_transform)
|| self.last_state.clear_color != clear_color
{
trace!(
previous_geometry = ?self.last_state.size,
current_geometry = ?output_geo.size,
previous_transform = ?self.last_state.transform,
current_transform = ?output_transform,
previous_clear_color = ?self.last_state.clear_color,
current_clear_color = ?clear_color,
"Output geometry, transform or clear color changed, damaging whole output geometry");
self.damage.clear();
self.damage.push(output_geo);
}
let mut new_damage = self.damage.clone();
new_damage.shrink_to_fit();
if age > 0 && self.last_state.old_damage.len() >= age {
trace!("age of {} recent enough, using old damage", age);
self.last_state.old_damage.truncate(age);
self.damage
.extend(self.last_state.old_damage.iter().take(age - 1).flatten().copied());
} else {
trace!(
"no old damage available, re-render everything. age: {} old_damage len: {}",
age,
self.last_state.old_damage.len(),
);
self.last_state.old_damage.truncate(MAX_AGE);
self.damage.clear();
self.damage.push(output_geo);
};
self.damage.retain_mut(|rect| {
if let Some(intersected) = rect.intersection(output_geo) {
*rect = intersected;
true
} else {
false
}
});
self.damage_shaper.shape_damage(&mut self.damage);
if self.damage.is_empty() {
trace!("nothing damaged, exiting early");
return element_render_states;
}
let mut new_elements_state = std::mem::take(&mut self.last_state.elements);
new_elements_state.clear();
new_elements_state.reserve(render_elements.len());
let new_elements_state =
render_elements
.iter()
.enumerate()
.fold(new_elements_state, |mut map, (z_index, elem)| {
let id = elem.id();
let elem_src = elem.src();
let elem_alpha = elem.alpha();
let elem_geometry = elem.geometry(output_scale);
let elem_transform = elem.transform();
if let Some(state) = map.get_mut(id) {
state.last_instances.push(ElementInstanceState {
last_src: elem_src,
last_geometry: elem_geometry,
last_transform: elem_transform,
last_alpha: elem_alpha,
last_z_index: z_index,
});
} else {
let current_commit = elem.current_commit();
map.insert(
id.clone(),
ElementState {
last_commit: current_commit,
last_instances: smallvec![ElementInstanceState {
last_src: elem_src,
last_geometry: elem_geometry,
last_transform: elem_transform,
last_alpha: elem_alpha,
last_z_index: z_index,
}],
},
);
}
map
});
self.last_state.size = Some(output_geo.size);
self.last_state.transform = Some(output_transform);
self.last_state.elements = new_elements_state;
self.last_state.old_damage.push_front(new_damage);
self.last_state.opaque_regions.clear();
self.last_state
.opaque_regions
.extend(self.opaque_regions.iter().copied());
self.last_state.opaque_regions.shrink_to_fit();
self.last_state.clear_color = clear_color;
element_render_states
}
}