use vello_common::geometry::RectU16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RootTarget {
UserSurface,
AtlasLayer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DrawPassTarget {
Root(RootTarget),
Layer(LayerTextureId),
}
impl DrawPassTarget {
pub(crate) fn enable_opaque(&self) -> bool {
matches!(self, Self::Root(RootTarget::UserSurface))
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum TextureParity {
Even = 0,
Odd = 1,
}
impl TextureParity {
pub(crate) const fn from_parity(parity: usize) -> Self {
if parity & 1 == 0 {
Self::Even
} else {
Self::Odd
}
}
pub(crate) const fn get_parity(self) -> usize {
match self {
Self::Even => 0,
Self::Odd => 1,
}
}
pub(crate) const fn opposite(self) -> Self {
match self {
Self::Even => Self::Odd,
Self::Odd => Self::Even,
}
}
}
impl From<TextureParity> for u8 {
fn from(parity: TextureParity) -> Self {
parity as Self
}
}
impl From<TextureParity> for u32 {
fn from(parity: TextureParity) -> Self {
u8::from(parity).into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct LayerTextureId {
pub(crate) texture_parity: TextureParity,
pub(crate) page_index: u16,
}
impl LayerTextureId {
pub(crate) const fn new(texture_parity: TextureParity, page_index: u16) -> Self {
Self {
texture_parity,
page_index,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct DrawPassBindings {
pub(crate) target: DrawPassTarget,
pub(crate) child: Option<LayerTextureId>,
}
impl DrawPassBindings {
pub(crate) const fn new(target: DrawPassTarget, child: Option<LayerTextureId>) -> Self {
Self { target, child }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct FilterPassBindings {
target: LayerTextureId,
temporary: LayerTextureId,
}
impl FilterPassBindings {
pub(crate) const fn new(target: LayerTextureId, temporary: LayerTextureId) -> Self {
debug_assert!(
target.texture_parity as u8 != temporary.texture_parity as u8,
"filter target and temporary textures must have opposite parity"
);
Self { target, temporary }
}
pub(crate) const fn target(self) -> LayerTextureId {
self.target
}
pub(crate) const fn temporary(self) -> LayerTextureId {
self.temporary
}
pub(crate) const fn input(self, step: usize) -> LayerTextureId {
if step & 1 == 0 {
self.target()
} else {
self.temporary()
}
}
pub(crate) const fn output(self, step: usize) -> LayerTextureId {
if step & 1 == 0 {
self.temporary()
} else {
self.target()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct BlendPassBindings {
target: LayerTextureId,
child: LayerTextureId,
}
impl BlendPassBindings {
pub(crate) const fn new(target: LayerTextureId, child: LayerTextureId) -> Self {
debug_assert!(
target.texture_parity as u8 != child.texture_parity as u8,
"blend target and child textures must have opposite parity"
);
Self { target, child }
}
pub(crate) const fn target(self) -> LayerTextureId {
self.target
}
pub(crate) const fn layer_id(self, parity: TextureParity) -> LayerTextureId {
if self.target.texture_parity as u8 == parity as u8 {
self.target
} else {
self.child
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct RoundBindings {
pages: [Option<u16>; 2],
}
impl RoundBindings {
pub(crate) const fn new(id: LayerTextureId) -> Self {
let mut pages = [None; 2];
pages[id.texture_parity.get_parity()] = Some(id.page_index);
Self { pages }
}
pub(crate) fn merge(mut self, other: Self) -> Option<Self> {
for (current, required) in self.pages.iter_mut().zip(other.pages) {
match (*current, required) {
(Some(current), Some(required)) if current != required => return None,
(None, Some(required)) => *current = Some(required),
_ => {}
}
}
Some(self)
}
pub(crate) fn layer_id(self, parity: TextureParity) -> Option<LayerTextureId> {
self.pages[parity.get_parity()].map(|page_index| LayerTextureId::new(parity, page_index))
}
pub(crate) fn required_textures(self) -> [Option<LayerTextureId>; 2] {
core::array::from_fn(|index| {
self.pages[index].map(|page_index| {
LayerTextureId::new(TextureParity::from_parity(index), page_index)
})
})
}
#[cfg(test)]
pub(crate) const fn page_indices(self) -> [Option<u16>; 2] {
self.pages
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct TextureRegion {
pub(crate) target: LayerTextureId,
pub(crate) rect: RectU16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct LayerTextureRegion {
pub(crate) texture: TextureRegion,
pub(crate) layer_bbox: RectU16,
}
impl LayerTextureRegion {
pub(crate) fn crop_to(self, bounds: RectU16) -> Self {
let layer_bbox = self.layer_bbox.intersect(bounds);
Self {
texture: TextureRegion {
target: self.texture.target,
rect: self.texture_rect(layer_bbox),
},
layer_bbox,
}
}
pub(crate) fn texture_rect(self, bbox: RectU16) -> RectU16 {
let x0 = self.texture.rect.x0 + (bbox.x0.checked_sub(self.layer_bbox.x0).unwrap());
let y0 = self.texture.rect.y0 + (bbox.y0.checked_sub(self.layer_bbox.y0).unwrap());
RectU16::new(x0, y0, x0 + bbox.width(), y0 + bbox.height())
}
}
pub(crate) trait DrawTarget {
fn enable_depth(&self) -> bool;
fn geometry_shift(&self) -> (i32, i32);
}
impl DrawTarget for RootTarget {
fn enable_depth(&self) -> bool {
matches!(self, Self::UserSurface)
}
fn geometry_shift(&self) -> (i32, i32) {
(0, 0)
}
}
impl DrawTarget for LayerTextureRegion {
fn enable_depth(&self) -> bool {
false
}
fn geometry_shift(&self) -> (i32, i32) {
(
self.texture.rect.x0 as i32 - i32::from(self.layer_bbox.x0),
self.texture.rect.y0 as i32 - i32::from(self.layer_bbox.y0),
)
}
}
#[cfg(test)]
mod tests {
use super::{LayerTextureId, LayerTextureRegion, RoundBindings, TextureParity, TextureRegion};
use vello_common::geometry::RectU16;
#[test]
fn texture_pair_constraints() {
let constraint = |pages| RoundBindings { pages };
let empty = constraint([None, None]);
let even = constraint([Some(2), None]);
let odd = constraint([None, Some(5)]);
assert_eq!(even.merge(empty).unwrap().pages, [Some(2), None]);
assert_eq!(empty.merge(even).unwrap().pages, [Some(2), None]);
assert_eq!(even.merge(odd).unwrap().pages, [Some(2), Some(5)]);
assert_eq!(even.merge(even).unwrap().pages, [Some(2), None]);
assert!(even.merge(constraint([Some(3), None])).is_none());
assert!(odd.merge(constraint([None, Some(6)])).is_none());
}
#[test]
fn texture_rect_translation() {
let layer = LayerTextureRegion {
texture: TextureRegion {
target: LayerTextureId::new(TextureParity::Even, 0),
rect: RectU16::new(100, 200, 150, 250),
},
layer_bbox: RectU16::new(10, 20, 60, 70),
};
assert_eq!(
layer.texture_rect(RectU16::new(15, 30, 25, 42)),
RectU16::new(105, 210, 115, 222)
);
}
#[test]
fn crop_preserves_texture_mapping() {
let layer = LayerTextureRegion {
texture: TextureRegion {
target: LayerTextureId::new(TextureParity::Even, 0),
rect: RectU16::new(100, 200, 150, 250),
},
layer_bbox: RectU16::new(10, 20, 60, 70),
};
assert_eq!(
layer.crop_to(RectU16::new(15, 30, 25, 42)),
LayerTextureRegion {
texture: TextureRegion {
target: layer.texture.target,
rect: RectU16::new(105, 210, 115, 222),
},
layer_bbox: RectU16::new(15, 30, 25, 42),
}
);
}
}