use valo_dl::DisplayList;
use valo_geometry::{Color, Matrix, Point, Rect};
use crate::frame::{ContextAttachments, Draw};
use crate::pool::{Attachments, LayerTarget};
use crate::raster::FillTarget;
use crate::renderer::RenderTarget;
use super::snapshot::{Placement, Snapshot};
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct PixelArea {
rect: Rect,
size: [u32; 2],
}
impl PixelArea {
pub fn over(rect: Rect) -> Self {
let size = [
rect.width.ceil().max(1.0) as u32,
rect.height.ceil().max(1.0) as u32,
];
Self { rect, size }
}
pub fn of_size(size: [u32; 2]) -> Self {
Self {
rect: Rect::new(0.0, 0.0, size[0] as f32, size[1] as f32),
size,
}
}
pub fn rect(&self) -> Rect {
self.rect
}
pub fn origin(&self) -> Point {
Point::new(self.rect.x, self.rect.y)
}
pub fn size(&self) -> [u32; 2] {
self.size
}
pub fn placement(&self) -> Placement {
Placement {
size: self.size,
transform: Matrix::translation(self.rect.x, self.rect.y),
placed: false,
}
}
pub fn culls(&self, bounds: &Rect) -> bool {
!bounds.intersects(&self.rect)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(super) struct DepthRange {
first_slot: u32,
slots: u32,
}
impl DepthRange {
pub const fn new(first_slot: u32, slots: u32) -> Self {
Self { first_slot, slots }
}
pub fn of_list(list: &DisplayList) -> Self {
Self::new(0, list.depth_slots() + 1)
}
pub fn z(&self, slot: u32) -> f32 {
(slot as f32 - self.first_slot as f32) / self.slots as f32
}
pub fn floor(&self) -> f32 {
0.0
}
pub fn half_slot_above(&self, z: f32) -> f32 {
z + 0.5 / self.slots as f32
}
}
pub(super) struct DrawContext {
pub attachments: ContextAttachments,
pub src_texture: wgpu::Texture,
pub area: PixelArea,
pub depth: DepthRange,
pub clear: Color,
pub first_pass_emitted: bool,
pub draws: Vec<Draw>,
pub clips: Vec<Draw>,
}
impl DrawContext {
pub fn main(attachments: Attachments, target: &RenderTarget, list: &DisplayList) -> Self {
let mut context = Self::new(
attachments.resolving_into(target.view.clone()),
target.texture.clone(),
PixelArea::of_size(target.size),
DepthRange::of_list(list),
);
context.clear = target.clear.unwrap_or(Color::TRANSPARENT);
context.draws.reserve(list.draw_count() as usize);
context
}
pub fn offscreen(layer: LayerTarget, area: PixelArea, depth: DepthRange) -> Self {
Self::new(
ContextAttachments {
msaa: layer.msaa,
depth: layer.depth,
resolve: layer.resolve,
},
layer.resolve_texture,
area,
depth,
)
}
pub fn raster(attachments: Attachments, fill: &FillTarget, depth: DepthRange) -> Self {
Self::new(
attachments.resolving_into(fill.view.clone()),
fill.texture.clone(),
PixelArea::of_size(fill.size),
depth,
)
}
fn new(
attachments: ContextAttachments,
src_texture: wgpu::Texture,
area: PixelArea,
depth: DepthRange,
) -> Self {
Self {
attachments,
src_texture,
area,
depth,
clear: Color::TRANSPARENT,
first_pass_emitted: false,
draws: Vec::new(),
clips: Vec::new(),
}
}
pub fn picture(&self) -> Snapshot {
Snapshot {
view: self.attachments.resolve.clone(),
placement: self.area.placement(),
}
}
}
pub(super) struct DrawContexts {
contexts: Vec<DrawContext>,
}
impl DrawContexts {
pub fn new(main: DrawContext) -> Self {
Self {
contexts: vec![main],
}
}
pub fn top(&self) -> &DrawContext {
self.contexts
.last()
.expect("the main target is never popped")
}
pub fn top_mut(&mut self) -> &mut DrawContext {
self.contexts
.last_mut()
.expect("the main target is never popped")
}
pub fn push(&mut self, context: DrawContext) {
self.contexts.push(context);
}
pub fn pop(&mut self) -> DrawContext {
debug_assert!(self.contexts.len() > 1, "the main target is never popped");
self.contexts.pop().expect("a context above the main one")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_area_rounds_its_texels_up_and_keeps_its_origin() {
let area = PixelArea::over(Rect::new(10.0, -4.0, 20.5, 0.25));
assert_eq!(area.size(), [21, 1]);
assert_eq!(area.origin(), Point::new(10.0, -4.0));
assert!(area.culls(&Rect::new(40.0, 0.0, 5.0, 5.0)));
assert!(!area.culls(&Rect::new(25.0, -10.0, 5.0, 7.0)));
}
#[test]
fn a_layers_depth_range_rebases_its_slots() {
let layer = DepthRange::new(7, 4);
assert_eq!(layer.z(8), 0.25);
assert_eq!(layer.z(10), 0.75);
assert_eq!(layer.floor(), 0.0);
assert_eq!(layer.half_slot_above(layer.z(8)), 0.375);
}
}