#![cfg_attr(windows, allow(dead_code))]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point,
};
use std::{
fmt::Debug,
iter::Peekable,
ops::{Add, Range, Sub},
slice,
};
#[allow(non_camel_case_types, unused)]
#[expect(missing_docs)]
pub type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
#[expect(missing_docs)]
pub type DrawOrder = u32;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[repr(transparent)]
pub struct PaddedBool32(u32);
impl From<bool> for PaddedBool32 {
fn from(value: bool) -> Self {
PaddedBool32(value as u32)
}
}
#[derive(Default)]
#[expect(missing_docs)]
pub struct Scene {
pub(crate) paint_operations: Vec<PaintOperation>,
primitive_bounds: BoundsTree<ScaledPixels>,
layer_stack: Vec<DrawOrder>,
pub shadows: Vec<Shadow>,
pub quads: Vec<Quad>,
pub paths: Vec<Path<ScaledPixels>>,
pub underlines: Vec<Underline>,
pub monochrome_sprites: Vec<MonochromeSprite>,
pub subpixel_sprites: Vec<SubpixelSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
pub surfaces: Vec<PaintSurface>,
pub custom_draws: Vec<CustomDraw>,
pub shader_draws: Vec<ShaderDraw>,
pub backdrops: Vec<crate::BackdropDraw>,
pub layer_boundaries: Vec<crate::LayerBoundary>,
}
#[expect(missing_docs)]
impl Scene {
pub fn clear(&mut self) {
self.paint_operations.clear();
self.primitive_bounds.clear();
self.layer_stack.clear();
self.paths.clear();
self.shadows.clear();
self.quads.clear();
self.underlines.clear();
self.monochrome_sprites.clear();
self.subpixel_sprites.clear();
self.polychrome_sprites.clear();
self.surfaces.clear();
self.custom_draws.clear();
self.shader_draws.clear();
self.backdrops.clear();
self.layer_boundaries.clear();
}
pub fn len(&self) -> usize {
self.paint_operations.len()
}
pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
let order = self.primitive_bounds.insert(bounds);
self.layer_stack.push(order);
self.paint_operations
.push(PaintOperation::StartLayer(bounds));
}
pub fn pop_layer(&mut self) {
self.layer_stack.pop();
self.paint_operations.push(PaintOperation::EndLayer);
}
pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
static NEXT_COMMAND: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
self.insert_retained_primitive(
primitive.into(),
NEXT_COMMAND.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
);
}
fn insert_retained_primitive(&mut self, mut primitive: Primitive, identity: u64) {
let clipped_bounds = primitive
.bounds()
.intersect(&primitive.content_mask().bounds);
if clipped_bounds.is_empty() {
return;
}
let order = if matches!(
primitive,
Primitive::Backdrop(_) | Primitive::LayerBoundary(_)
) {
let fence = primitive.content_mask().bounds;
let order = self.primitive_bounds.insert(fence);
let after = self.primitive_bounds.insert(fence);
self.layer_stack.fill(after);
order
} else {
self.layer_stack
.last()
.copied()
.unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds))
};
match &mut primitive {
Primitive::LayerBoundary(draw) => {
draw.order = order;
self.layer_boundaries.push(draw.clone());
}
Primitive::Backdrop(draw) => {
draw.order = order;
self.backdrops.push(draw.clone());
}
Primitive::Shadow(shadow) => {
shadow.order = order;
self.shadows.push(*shadow);
}
Primitive::Quad(quad) => {
quad.order = order;
self.quads.push(*quad);
}
Primitive::Path(path) => {
path.order = order;
path.id = PathId(self.paths.len());
self.paths.push(path.clone());
}
Primitive::Underline(underline) => {
underline.order = order;
self.underlines.push(*underline);
}
Primitive::MonochromeSprite(sprite) => {
sprite.order = order;
self.monochrome_sprites.push(*sprite);
}
Primitive::SubpixelSprite(sprite) => {
sprite.order = order;
self.subpixel_sprites.push(*sprite);
}
Primitive::PolychromeSprite(sprite) => {
sprite.order = order;
self.polychrome_sprites.push(*sprite);
}
Primitive::Shader(draw) => {
draw.order = order;
self.shader_draws.push(draw.clone());
}
Primitive::Custom(draw) => {
draw.order = order;
self.custom_draws.push(draw.clone());
}
Primitive::Surface(surface) => {
surface.order = order;
self.surfaces.push(surface.clone());
}
}
self.paint_operations
.push(PaintOperation::Primitive(primitive, identity));
}
pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
for operation in &prev_scene.paint_operations[range] {
match operation {
PaintOperation::Primitive(primitive, identity) => {
self.insert_retained_primitive(primitive.clone(), *identity)
}
PaintOperation::StartLayer(bounds) => self.push_layer(*bounds),
PaintOperation::EndLayer => self.pop_layer(),
}
}
}
pub fn begin_layer_capture(
&mut self,
capture: std::sync::Arc<crate::LayerCapture>,
viewport: ContentMask<ScaledPixels>,
) {
self.insert_primitive(Primitive::LayerBoundary(crate::LayerBoundary {
order: 0,
bounds: viewport.bounds,
content_mask: viewport,
capture,
begin: true,
}));
}
pub fn end_layer_capture(
&mut self,
capture: std::sync::Arc<crate::LayerCapture>,
viewport: ContentMask<ScaledPixels>,
) {
self.insert_primitive(Primitive::LayerBoundary(crate::LayerBoundary {
order: 0,
bounds: viewport.bounds,
content_mask: viewport,
capture,
begin: false,
}));
}
pub fn layer_plans(
&self,
viewport: Size<crate::DevicePixels>,
) -> anyhow::Result<Vec<crate::LayerPlan>> {
use anyhow::ensure;
use std::sync::Arc;
if self.layer_boundaries.is_empty() {
return Ok(Vec::new());
}
ensure!(
self.layer_boundaries.len() <= crate::MAX_LAYER_GROUPS * 2,
"at most four captured groups per frame"
);
let mut plans: Vec<crate::LayerPlan> = Vec::new();
let mut revisions: Vec<Vec<u64>> = Vec::new();
let mut stack: Vec<(Arc<crate::LayerCapture>, usize)> = Vec::new();
for operation in &self.paint_operations {
let PaintOperation::Primitive(primitive, identity) = operation else {
continue;
};
let closing = matches!(primitive,Primitive::LayerBoundary(boundary) if !boundary.begin);
for (depth, (_, slot)) in stack.iter().enumerate() {
if closing && depth + 1 == stack.len() {
continue;
}
revisions[*slot].push(*identity);
ensure!(
revisions[*slot].len() <= crate::MAX_LAYER_COMMANDS,
"captured group exceeds 65536 commands"
);
}
if let Primitive::LayerBoundary(boundary) = primitive {
let index = self
.layer_boundaries
.iter()
.position(|b| {
b.order == boundary.order
&& b.begin == boundary.begin
&& Arc::ptr_eq(&b.capture, &boundary.capture)
})
.ok_or_else(|| anyhow::anyhow!("missing layer boundary"))?;
if boundary.begin {
ensure!(
plans.len() < crate::MAX_LAYER_GROUPS,
"at most four captured groups per frame"
);
let plan = crate::LayerPlan::new(&boundary.capture, viewport, index)?;
if let Some((_, parent)) = stack.last() {
let p = &plans[*parent];
ensure!(
plan.origin[0] >= p.origin[0]
&& plan.origin[1] >= p.origin[1]
&& plan.origin[0] + plan.size[0] <= p.origin[0] + p.size[0]
&& plan.origin[1] + plan.size[1] <= p.origin[1] + p.size[1],
"nested layer escapes its parent capture"
);
}
let slot = plans.len();
plans.push(plan);
revisions.push(Vec::new());
stack.push((boundary.capture.clone(), slot));
} else {
let (capture, slot) = stack
.pop()
.ok_or_else(|| anyhow::anyhow!("unmatched layer end"))?;
ensure!(
Arc::ptr_eq(&capture, &boundary.capture),
"crossed layer boundaries"
);
plans[slot].end_index = index;
plans[slot].revision = std::mem::take(&mut revisions[slot]).into();
}
continue;
}
if !stack.is_empty() {
ensure!(
!matches!(primitive, Primitive::Backdrop(_)),
"backdrop sampling inside captured groups is not supported"
);
ensure!(
!matches!(primitive, Primitive::SubpixelSprite(_)),
"captured text requires grayscale glyph coverage"
);
}
for (_, slot) in &stack {
let plan = &mut plans[*slot];
if matches!(primitive, Primitive::Custom(_) | Primitive::Surface(_)) {
plan.reusable = false;
}
let clipped = primitive
.bounds()
.intersect(&primitive.content_mask().bounds);
ensure!(
clipped.is_empty()
|| (clipped.left().0 >= plan.origin[0] as f32
&& clipped.top().0 >= plan.origin[1] as f32
&& clipped.right().0 <= (plan.origin[0] + plan.size[0]) as f32
&& clipped.bottom().0 <= (plan.origin[1] + plan.size[1]) as f32),
"paint escapes its layer capture; apply the capture content mask"
);
}
}
ensure!(stack.is_empty(), "unclosed layer capture");
Ok(plans)
}
pub fn backdrop_cache_keys(
&self,
viewport: Size<crate::DevicePixels>,
) -> anyhow::Result<Vec<crate::BackdropCacheKey>> {
if self.backdrops.is_empty() {
return Ok(Vec::new());
}
anyhow::ensure!(
self.backdrops.len() <= crate::MAX_BACKDROPS,
"at most four backdrop boundaries per frame"
);
let mut keys = Vec::new();
let mut revision = Vec::new();
let mut reusable = true;
for operation in &self.paint_operations {
let PaintOperation::Primitive(primitive, identity) = operation else {
continue;
};
if let Primitive::Backdrop(draw) = primitive {
let plan = draw.plan(viewport)?;
keys.push((
draw.order,
crate::BackdropCacheKey {
viewport,
origin: plan.origin,
size: plan.size,
filter: [plan.parameters[0], plan.parameters[1]],
revision: revision.clone().into(),
reusable,
},
));
}
if matches!(primitive, Primitive::Custom(_) | Primitive::Surface(_)) {
reusable = false;
}
if revision.len() < crate::MAX_LAYER_COMMANDS {
let order = match primitive {
Primitive::Shadow(p) => p.order,
Primitive::Quad(p) => p.order,
Primitive::Path(p) => p.order,
Primitive::Underline(p) => p.order,
Primitive::MonochromeSprite(p) => p.order,
Primitive::SubpixelSprite(p) => p.order,
Primitive::PolychromeSprite(p) => p.order,
Primitive::Surface(p) => p.order,
Primitive::Custom(p) => p.order,
Primitive::Shader(p) => p.order,
Primitive::Backdrop(p) => p.order,
Primitive::LayerBoundary(p) => p.order,
};
revision.push((*identity, order));
} else {
reusable = false;
}
}
anyhow::ensure!(
keys.len() == self.backdrops.len(),
"backdrops require scene insertion/replay"
);
keys.sort_by_key(|(order, _)| *order);
Ok(keys.into_iter().map(|(_, key)| key).collect())
}
pub fn finish(&mut self) {
self.shadows.sort_by_key(|shadow| shadow.order);
self.quads.sort_by_key(|quad| quad.order);
self.paths.sort_by_key(|path| path.order);
self.underlines.sort_by_key(|underline| underline.order);
self.monochrome_sprites
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
self.subpixel_sprites
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
self.polychrome_sprites
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
self.surfaces.sort_by_key(|surface| surface.order);
self.custom_draws.sort_by_key(|draw| draw.order);
self.shader_draws.sort_by_key(|draw| draw.order);
self.backdrops.sort_by_key(|draw| draw.order);
self.layer_boundaries.sort_by_key(|draw| draw.order);
}
#[cfg_attr(
all(
any(target_os = "linux", target_os = "freebsd"),
not(any(feature = "x11", feature = "wayland"))
),
allow(dead_code)
)]
pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
BatchIterator {
shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(),
quads_start: 0,
quads_iter: self.quads.iter().peekable(),
paths_start: 0,
paths_iter: self.paths.iter().peekable(),
underlines_start: 0,
underlines_iter: self.underlines.iter().peekable(),
monochrome_sprites_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
subpixel_sprites_start: 0,
subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(),
polychrome_sprites_start: 0,
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
layer_boundary_start: 0,
layer_boundary_iter: self.layer_boundaries.iter().peekable(),
backdrop_start: 0,
backdrop_iter: self.backdrops.iter().peekable(),
shader_start: 0,
shader_iter: self.shader_draws.iter().peekable(),
custom_start: 0,
custom_iter: self.custom_draws.iter().peekable(),
surfaces_start: 0,
surfaces_iter: self.surfaces.iter().peekable(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
#[cfg_attr(
all(
any(target_os = "linux", target_os = "freebsd"),
not(any(feature = "x11", feature = "wayland"))
),
allow(dead_code)
)]
pub(crate) enum PrimitiveKind {
Shadow,
#[default]
Quad,
Path,
Underline,
MonochromeSprite,
SubpixelSprite,
PolychromeSprite,
Surface,
Custom,
Shader,
Backdrop,
LayerBoundary,
}
pub(crate) enum PaintOperation {
Primitive(Primitive, u64),
StartLayer(Bounds<ScaledPixels>),
EndLayer,
}
#[derive(Clone)]
#[expect(missing_docs)]
pub enum Primitive {
Shadow(Shadow),
Quad(Quad),
Path(Path<ScaledPixels>),
Underline(Underline),
MonochromeSprite(MonochromeSprite),
SubpixelSprite(SubpixelSprite),
PolychromeSprite(PolychromeSprite),
Surface(PaintSurface),
Custom(CustomDraw),
Shader(ShaderDraw),
Backdrop(crate::BackdropDraw),
LayerBoundary(crate::LayerBoundary),
}
#[expect(missing_docs)]
impl Primitive {
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.bounds,
Primitive::Quad(quad) => &quad.bounds,
Primitive::Path(path) => &path.bounds,
Primitive::Underline(underline) => &underline.bounds,
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
Primitive::SubpixelSprite(sprite) => &sprite.bounds,
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
Primitive::Surface(surface) => &surface.bounds,
Primitive::Custom(draw) => &draw.bounds,
Primitive::Shader(draw) => &draw.bounds,
Primitive::Backdrop(draw) => &draw.bounds,
Primitive::LayerBoundary(draw) => &draw.bounds,
}
}
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.content_mask,
Primitive::Quad(quad) => &quad.content_mask,
Primitive::Path(path) => &path.content_mask,
Primitive::Underline(underline) => &underline.content_mask,
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
Primitive::SubpixelSprite(sprite) => &sprite.content_mask,
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
Primitive::Surface(surface) => &surface.content_mask,
Primitive::Custom(draw) => &draw.content_mask,
Primitive::Shader(draw) => &draw.content_mask,
Primitive::Backdrop(draw) => &draw.content_mask,
Primitive::LayerBoundary(draw) => &draw.content_mask,
}
}
}
#[cfg_attr(
all(
any(target_os = "linux", target_os = "freebsd"),
not(any(feature = "x11", feature = "wayland"))
),
allow(dead_code)
)]
struct BatchIterator<'a> {
layer_boundary_start: usize,
layer_boundary_iter: Peekable<slice::Iter<'a, crate::LayerBoundary>>,
backdrop_start: usize,
backdrop_iter: Peekable<slice::Iter<'a, crate::BackdropDraw>>,
shader_start: usize,
shader_iter: Peekable<slice::Iter<'a, ShaderDraw>>,
custom_start: usize,
custom_iter: Peekable<slice::Iter<'a, CustomDraw>>,
shadows_start: usize,
shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
quads_start: usize,
quads_iter: Peekable<slice::Iter<'a, Quad>>,
paths_start: usize,
paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
underlines_start: usize,
underlines_iter: Peekable<slice::Iter<'a, Underline>>,
monochrome_sprites_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
subpixel_sprites_start: usize,
subpixel_sprites_iter: Peekable<slice::Iter<'a, SubpixelSprite>>,
polychrome_sprites_start: usize,
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
surfaces_start: usize,
surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
}
impl<'a> Iterator for BatchIterator<'a> {
type Item = PrimitiveBatch;
fn next(&mut self) -> Option<Self::Item> {
let mut orders_and_kinds = [
(
self.layer_boundary_iter.peek().map(|d| d.order),
PrimitiveKind::LayerBoundary,
),
(
self.backdrop_iter.peek().map(|d| d.order),
PrimitiveKind::Backdrop,
),
(
self.shader_iter.peek().map(|d| d.order),
PrimitiveKind::Shader,
),
(
self.custom_iter.peek().map(|d| d.order),
PrimitiveKind::Custom,
),
(
self.shadows_iter.peek().map(|s| s.order),
PrimitiveKind::Shadow,
),
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
(self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
(
self.underlines_iter.peek().map(|u| u.order),
PrimitiveKind::Underline,
),
(
self.monochrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::MonochromeSprite,
),
(
self.subpixel_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::SubpixelSprite,
),
(
self.polychrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::PolychromeSprite,
),
(
self.surfaces_iter.peek().map(|s| s.order),
PrimitiveKind::Surface,
),
];
orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
let first = orders_and_kinds[0];
let second = orders_and_kinds[1];
let (batch_kind, max_order_and_kind) = if first.0.is_some() {
(first.1, (second.0.unwrap_or(u32::MAX), second.1))
} else {
return None;
};
match batch_kind {
PrimitiveKind::LayerBoundary => {
let index = self.layer_boundary_start;
self.layer_boundary_iter.next();
self.layer_boundary_start += 1;
Some(PrimitiveBatch::LayerBoundary(index))
}
PrimitiveKind::Backdrop => {
let index = self.backdrop_start;
self.backdrop_iter.next();
self.backdrop_start += 1;
Some(PrimitiveBatch::Backdrop(index))
}
PrimitiveKind::Shader => {
let start = self.shader_start;
let first = self.shader_iter.next().unwrap();
let program_id = first.program.id();
let texture_id = first.glyph_mask.map(|tile| tile.texture_id);
self.shader_start += 1;
let mut coverage_bounds = Vec::new();
if first.path_mask.is_some() {
coverage_bounds.push(first.bounds.dilate(ScaledPixels(1.)));
}
while self
.shader_iter
.next_if(|draw| {
let compatible = draw.program.id() == program_id
&& draw.glyph_mask.map(|tile| tile.texture_id) == texture_id
&& (draw.order, batch_kind) < max_order_and_kind;
if !compatible || first.path_mask.is_some() != draw.path_mask.is_some() {
return false;
}
if first.path_mask.is_some() {
if coverage_bounds.len() >= 64 {
return false;
}
let bounds = draw.bounds.dilate(ScaledPixels(1.));
if coverage_bounds
.iter()
.any(|previous| previous.intersects(&bounds))
{
return false;
}
coverage_bounds.push(bounds);
}
true
})
.is_some()
{
self.shader_start += 1;
}
Some(PrimitiveBatch::Shaders {
program_id,
range: start..self.shader_start,
})
}
PrimitiveKind::Custom => {
let start = self.custom_start;
self.custom_iter.next();
self.custom_start += 1;
while self
.custom_iter
.next_if(|d| (d.order, batch_kind) < max_order_and_kind)
.is_some()
{
self.custom_start += 1;
}
Some(PrimitiveBatch::Custom(start..self.custom_start))
}
PrimitiveKind::Shadow => {
let shadows_start = self.shadows_start;
let mut shadows_end = shadows_start + 1;
self.shadows_iter.next();
while self
.shadows_iter
.next_if(|shadow| (shadow.order, batch_kind) < max_order_and_kind)
.is_some()
{
shadows_end += 1;
}
self.shadows_start = shadows_end;
Some(PrimitiveBatch::Shadows(shadows_start..shadows_end))
}
PrimitiveKind::Quad => {
let quads_start = self.quads_start;
let mut quads_end = quads_start + 1;
self.quads_iter.next();
while self
.quads_iter
.next_if(|quad| (quad.order, batch_kind) < max_order_and_kind)
.is_some()
{
quads_end += 1;
}
self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(quads_start..quads_end))
}
PrimitiveKind::Path => {
let paths_start = self.paths_start;
let mut paths_end = paths_start + 1;
self.paths_iter.next();
while self
.paths_iter
.next_if(|path| (path.order, batch_kind) < max_order_and_kind)
.is_some()
{
paths_end += 1;
}
self.paths_start = paths_end;
Some(PrimitiveBatch::Paths(paths_start..paths_end))
}
PrimitiveKind::Underline => {
let underlines_start = self.underlines_start;
let mut underlines_end = underlines_start + 1;
self.underlines_iter.next();
while self
.underlines_iter
.next_if(|underline| (underline.order, batch_kind) < max_order_and_kind)
.is_some()
{
underlines_end += 1;
}
self.underlines_start = underlines_end;
Some(PrimitiveBatch::Underlines(underlines_start..underlines_end))
}
PrimitiveKind::MonochromeSprite => {
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.monochrome_sprites_start;
let mut sprites_end = sprites_start + 1;
self.monochrome_sprites_iter.next();
while self
.monochrome_sprites_iter
.next_if(|sprite| {
(sprite.order, batch_kind) < max_order_and_kind
&& sprite.tile.texture_id == texture_id
})
.is_some()
{
sprites_end += 1;
}
self.monochrome_sprites_start = sprites_end;
Some(PrimitiveBatch::MonochromeSprites {
texture_id,
range: sprites_start..sprites_end,
})
}
PrimitiveKind::SubpixelSprite => {
let texture_id = self.subpixel_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.subpixel_sprites_start;
let mut sprites_end = sprites_start + 1;
self.subpixel_sprites_iter.next();
while self
.subpixel_sprites_iter
.next_if(|sprite| {
(sprite.order, batch_kind) < max_order_and_kind
&& sprite.tile.texture_id == texture_id
})
.is_some()
{
sprites_end += 1;
}
self.subpixel_sprites_start = sprites_end;
Some(PrimitiveBatch::SubpixelSprites {
texture_id,
range: sprites_start..sprites_end,
})
}
PrimitiveKind::PolychromeSprite => {
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.polychrome_sprites_start;
let mut sprites_end = sprites_start + 1;
self.polychrome_sprites_iter.next();
while self
.polychrome_sprites_iter
.next_if(|sprite| {
(sprite.order, batch_kind) < max_order_and_kind
&& sprite.tile.texture_id == texture_id
})
.is_some()
{
sprites_end += 1;
}
self.polychrome_sprites_start = sprites_end;
Some(PrimitiveBatch::PolychromeSprites {
texture_id,
range: sprites_start..sprites_end,
})
}
PrimitiveKind::Surface => {
let surfaces_start = self.surfaces_start;
let mut surfaces_end = surfaces_start + 1;
self.surfaces_iter.next();
while self
.surfaces_iter
.next_if(|surface| (surface.order, batch_kind) < max_order_and_kind)
.is_some()
{
surfaces_end += 1;
}
self.surfaces_start = surfaces_end;
Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end))
}
}
}
}
#[derive(Debug)]
#[cfg_attr(
all(
any(target_os = "linux", target_os = "freebsd"),
not(any(feature = "x11", feature = "wayland"))
),
allow(dead_code)
)]
#[allow(missing_docs)]
pub enum PrimitiveBatch {
Backdrop(usize),
LayerBoundary(usize),
Shadows(Range<usize>),
Quads(Range<usize>),
Paths(Range<usize>),
Underlines(Range<usize>),
MonochromeSprites {
texture_id: AtlasTextureId,
range: Range<usize>,
},
#[cfg_attr(target_os = "macos", allow(dead_code))]
SubpixelSprites {
texture_id: AtlasTextureId,
range: Range<usize>,
},
PolychromeSprites {
texture_id: AtlasTextureId,
range: Range<usize>,
},
Surfaces(Range<usize>),
Custom(Range<usize>),
Shaders {
program_id: u64,
range: Range<usize>,
},
}
impl PrimitiveBatch {
#[expect(missing_docs)]
pub fn label(&self) -> String {
match self {
Self::LayerBoundary(index) => format!("layer boundary ({index})"),
Self::Backdrop(index) => format!("backdrop ({index})"),
Self::Shaders { range, .. } => format!("shaders ({})", range.len()),
Self::Shadows(range) => format!("shadows ({})", range.len()),
Self::Quads(range) => format!("quads ({})", range.len()),
Self::Paths(range) => format!("paths ({})", range.len()),
Self::Underlines(range) => format!("underlines ({})", range.len()),
Self::MonochromeSprites { texture_id, range } => {
format!(
"monochrome sprites ({}) on atlas {}",
range.len(),
texture_id.index
)
}
Self::SubpixelSprites { texture_id, range } => {
format!(
"subpixel sprites ({}) on atlas {}",
range.len(),
texture_id.index
)
}
Self::PolychromeSprites { texture_id, range } => {
format!(
"polychrome sprites ({}) on atlas {}",
range.len(),
texture_id.index
)
}
Self::Custom(range) => format!("custom ({})", range.len()),
Self::Surfaces(range) => format!("surfaces ({})", range.len()),
}
}
}
#[derive(Default, Debug, Copy, Clone)]
#[repr(C)]
#[expect(missing_docs)]
pub struct Quad {
pub order: DrawOrder,
pub border_style: BorderStyle,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub background: Background,
pub border_color: Hsla,
pub corner_radii: Corners<ScaledPixels>,
pub border_widths: Edges<ScaledPixels>,
}
impl From<Quad> for Primitive {
fn from(quad: Quad) -> Self {
Primitive::Quad(quad)
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
#[expect(missing_docs)]
pub struct Underline {
pub order: DrawOrder,
pub pad: u32, pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub thickness: ScaledPixels,
pub wavy: PaddedBool32,
}
impl From<Underline> for Primitive {
fn from(underline: Underline) -> Self {
Primitive::Underline(underline)
}
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
#[expect(missing_docs)]
pub struct Shadow {
pub order: DrawOrder,
pub blur_radius: ScaledPixels,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub element_bounds: Bounds<ScaledPixels>,
pub element_corner_radii: Corners<ScaledPixels>,
pub inset: u32,
pub pad: u32, }
impl From<Shadow> for Primitive {
fn from(shadow: Shadow) -> Self {
Primitive::Shadow(shadow)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub enum BorderStyle {
#[default]
Solid = 0,
Dashed = 1,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct TransformationMatrix {
pub rotation_scale: [[f32; 2]; 2],
pub translation: [f32; 2],
}
impl Eq for TransformationMatrix {}
impl TransformationMatrix {
pub fn unit() -> Self {
Self {
rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
translation: [0.0, 0.0],
}
}
pub fn translate(mut self, point: Point<ScaledPixels>) -> Self {
self.compose(Self {
rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
translation: [point.x.0, point.y.0],
})
}
pub fn rotate(self, angle: Radians) -> Self {
self.compose(Self {
rotation_scale: [
[angle.0.cos(), -angle.0.sin()],
[angle.0.sin(), angle.0.cos()],
],
translation: [0.0, 0.0],
})
}
pub fn scale(self, size: Size<f32>) -> Self {
self.compose(Self {
rotation_scale: [[size.width, 0.0], [0.0, size.height]],
translation: [0.0, 0.0],
})
}
#[inline]
pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
if other == Self::unit() {
return self;
}
TransformationMatrix {
rotation_scale: [
[
self.rotation_scale[0][0] * other.rotation_scale[0][0]
+ self.rotation_scale[0][1] * other.rotation_scale[1][0],
self.rotation_scale[0][0] * other.rotation_scale[0][1]
+ self.rotation_scale[0][1] * other.rotation_scale[1][1],
],
[
self.rotation_scale[1][0] * other.rotation_scale[0][0]
+ self.rotation_scale[1][1] * other.rotation_scale[1][0],
self.rotation_scale[1][0] * other.rotation_scale[0][1]
+ self.rotation_scale[1][1] * other.rotation_scale[1][1],
],
],
translation: [
self.translation[0]
+ self.rotation_scale[0][0] * other.translation[0]
+ self.rotation_scale[0][1] * other.translation[1],
self.translation[1]
+ self.rotation_scale[1][0] * other.translation[0]
+ self.rotation_scale[1][1] * other.translation[1],
],
}
}
pub fn apply(&self, point: Point<Pixels>) -> Point<Pixels> {
let input = [point.x.0, point.y.0];
let mut output = self.translation;
for (i, output_cell) in output.iter_mut().enumerate() {
for (k, input_cell) in input.iter().enumerate() {
*output_cell += self.rotation_scale[i][k] * *input_cell;
}
}
Point::new(output[0].into(), output[1].into())
}
}
impl Default for TransformationMatrix {
fn default() -> Self {
Self::unit()
}
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
#[expect(missing_docs)]
pub struct MonochromeSprite {
pub order: DrawOrder,
pub pad: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub tile: AtlasTile,
pub transformation: TransformationMatrix,
}
impl From<MonochromeSprite> for Primitive {
fn from(sprite: MonochromeSprite) -> Self {
Primitive::MonochromeSprite(sprite)
}
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
#[expect(missing_docs)]
pub struct SubpixelSprite {
pub order: DrawOrder,
pub pad: u32, pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub tile: AtlasTile,
pub transformation: TransformationMatrix,
}
impl From<SubpixelSprite> for Primitive {
fn from(sprite: SubpixelSprite) -> Self {
Primitive::SubpixelSprite(sprite)
}
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
#[expect(missing_docs)]
pub struct PolychromeSprite {
pub order: DrawOrder,
pub pad: u32,
pub grayscale: PaddedBool32,
pub opacity: f32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub tile: AtlasTile,
}
impl From<PolychromeSprite> for Primitive {
fn from(sprite: PolychromeSprite) -> Self {
Primitive::PolychromeSprite(sprite)
}
}
#[derive(Clone, Debug)]
#[allow(missing_docs)]
pub struct PaintSurface {
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub image_buffer: objc2_core_foundation::CFRetained<objc2_core_video::CVPixelBuffer>,
}
impl From<PaintSurface> for Primitive {
fn from(surface: PaintSurface) -> Self {
Primitive::Surface(surface)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[expect(missing_docs)]
pub struct PathId(pub usize);
#[derive(Clone, Debug)]
#[expect(missing_docs)]
pub struct Path<P: Clone + Debug + Default + PartialEq> {
pub id: PathId,
pub order: DrawOrder,
pub bounds: Bounds<P>,
pub content_mask: ContentMask<P>,
pub vertices: Vec<PathVertex<P>>,
pub color: Background,
start: Point<P>,
current: Point<P>,
contour_count: usize,
}
impl Path<Pixels> {
pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
order: DrawOrder::default(),
vertices: Vec::new(),
start,
current: start,
bounds: Bounds {
origin: start,
size: Default::default(),
},
content_mask: Default::default(),
color: Default::default(),
contour_count: 0,
}
}
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
Path {
id: self.id,
order: self.order,
bounds: self.bounds.scale(factor),
content_mask: self.content_mask.scale(factor),
vertices: self
.vertices
.iter()
.map(|vertex| vertex.scale(factor))
.collect(),
start: self.start.map(|start| start.scale(factor)),
current: self.current.scale(factor),
contour_count: self.contour_count,
color: self.color,
}
}
pub fn move_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
self.start = to;
self.current = to;
}
pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.current = to;
}
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.push_triangle(
(self.current, ctrl, to),
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
);
self.current = to;
}
pub fn push_triangle(
&mut self,
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>),
) {
self.bounds = self
.bounds
.union(&Bounds {
origin: xy.0,
size: Default::default(),
})
.union(&Bounds {
origin: xy.1,
size: Default::default(),
})
.union(&Bounds {
origin: xy.2,
size: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.0,
st_position: st.0,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.1,
st_position: st.1,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.2,
st_position: st.2,
content_mask: Default::default(),
});
}
}
impl<T> Path<T>
where
T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
{
#[allow(unused)]
#[expect(missing_docs)]
pub fn clipped_bounds(&self) -> Bounds<T> {
self.bounds.intersect(&self.content_mask.bounds)
}
}
impl From<Path<ScaledPixels>> for Primitive {
fn from(path: Path<ScaledPixels>) -> Self {
Primitive::Path(path)
}
}
#[derive(Clone, Debug)]
#[repr(C)]
#[expect(missing_docs)]
pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
pub xy_position: Point<P>,
pub st_position: Point<f32>,
pub content_mask: ContentMask<P>,
}
#[expect(missing_docs)]
impl PathVertex<Pixels> {
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
PathVertex {
xy_position: self.xy_position.scale(factor),
st_position: self.st_position,
content_mask: self.content_mask.scale(factor),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PreparationRequest {
logical_size: Size<Pixels>,
raster_size: Size<crate::DevicePixels>,
scale_factor: f32,
}
impl PreparationRequest {
pub fn new(
logical_size: Size<Pixels>,
raster_size: Size<crate::DevicePixels>,
scale_factor: f32,
) -> anyhow::Result<Self> {
anyhow::ensure!(
logical_size.width.0.is_finite()
&& logical_size.width.0 > 0.
&& logical_size.height.0.is_finite()
&& logical_size.height.0 > 0.
&& raster_size.width.0 > 0
&& raster_size.height.0 > 0
&& scale_factor.is_finite()
&& scale_factor > 0.,
"invalid custom preparation geometry"
);
Ok(Self {
logical_size,
raster_size,
scale_factor,
})
}
pub fn logical_size(&self) -> Size<Pixels> {
self.logical_size
}
pub fn raster_size(&self) -> Size<crate::DevicePixels> {
self.raster_size
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor
}
}
pub trait CustomContent: std::fmt::Debug + Send + Sync + 'static {
fn image_color_transform(&self) -> [[f32; 4]; 3] {
[[1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.]]
}
fn image_source_size(&self) -> Option<Size<crate::DevicePixels>> {
None
}
fn image_sample_opacity(&self) -> f32 {
1.
}
fn image_color_mode(&self) -> crate::ImageColorMode {
crate::ImageColorMode::EncodedSrgb
}
fn image_linear_adjustments(&self) -> Option<crate::LinearImagePaint> {
None
}
fn prepare(
&self,
backend: &dyn std::any::Any,
size: Size<crate::DevicePixels>,
) -> anyhow::Result<Box<dyn std::any::Any>>;
fn prepare_in_frame(
&self,
backend: &dyn std::any::Any,
request: &PreparationRequest,
_frame: &crate::PreparationFrame,
) -> anyhow::Result<Box<dyn std::any::Any>> {
self.prepare_with_request(backend, request)
}
fn prepare_with_request(
&self,
backend: &dyn std::any::Any,
request: &PreparationRequest,
) -> anyhow::Result<Box<dyn std::any::Any>> {
self.prepare(backend, request.raster_size())
}
}
#[derive(Debug)]
struct ContentWithPreparationGeometry {
content: std::sync::Arc<dyn CustomContent>,
logical_size: Size<Pixels>,
scale_factor: f32,
}
impl CustomContent for ContentWithPreparationGeometry {
fn image_source_size(&self) -> Option<Size<crate::DevicePixels>> {
self.content.image_source_size()
}
fn image_sample_opacity(&self) -> f32 {
self.content.image_sample_opacity()
}
fn image_color_mode(&self) -> crate::ImageColorMode {
self.content.image_color_mode()
}
fn image_linear_adjustments(&self) -> Option<crate::LinearImagePaint> {
self.content.image_linear_adjustments()
}
fn image_color_transform(&self) -> [[f32; 4]; 3] {
self.content.image_color_transform()
}
fn prepare(
&self,
backend: &dyn std::any::Any,
size: Size<crate::DevicePixels>,
) -> anyhow::Result<Box<dyn std::any::Any>> {
let request = PreparationRequest::new(self.logical_size, size, self.scale_factor)?;
self.content.prepare_with_request(backend, &request)
}
fn prepare_in_frame(
&self,
backend: &dyn std::any::Any,
request: &PreparationRequest,
frame: &crate::PreparationFrame,
) -> anyhow::Result<Box<dyn std::any::Any>> {
let request =
PreparationRequest::new(self.logical_size, request.raster_size(), self.scale_factor)?;
self.content.prepare_in_frame(backend, &request, frame)
}
fn prepare_with_request(
&self,
backend: &dyn std::any::Any,
request: &PreparationRequest,
) -> anyhow::Result<Box<dyn std::any::Any>> {
self.prepare(backend, request.raster_size())
}
}
pub(crate) fn content_with_preparation_geometry(
content: std::sync::Arc<dyn CustomContent>,
logical_size: Size<Pixels>,
scale_factor: f32,
) -> std::sync::Arc<dyn CustomContent> {
std::sync::Arc::new(ContentWithPreparationGeometry {
content,
logical_size,
scale_factor,
})
}
#[cfg(test)]
mod preparation_tests;
#[derive(Clone)]
pub enum CustomGeometry {
RoundedRect {
bounds: Bounds<Pixels>,
corner_radii: Corners<Pixels>,
},
Border(crate::PaintQuad),
Path(Path<Pixels>),
}
#[derive(Clone, Debug)]
pub enum CustomCoverage {
Border {
widths: Edges<ScaledPixels>,
style: BorderStyle,
},
Path(std::sync::Arc<Path<ScaledPixels>>),
}
#[derive(Clone, Debug)]
pub struct CustomDraw {
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub opacity: f32,
pub content: std::sync::Arc<dyn CustomContent>,
pub coverage: Option<CustomCoverage>,
}
impl From<CustomDraw> for Primitive {
fn from(draw: CustomDraw) -> Self {
Self::Custom(draw)
}
}
pub const SHADER_DATA_SLOTS: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ShaderOutputEncoding {
EncodedSrgb,
LinearDisplayP3,
LinearRec2020,
}
#[derive(Debug)]
pub struct SceneShaderProgram {
id: u64,
output_encoding: ShaderOutputEncoding,
glyph_mask: bool,
path_mask: bool,
wgsl: String,
metal: (String, String, String),
hlsl: (String, String, String),
}
impl SceneShaderProgram {
pub fn new(
wgsl: String,
metal: (String, String, String),
hlsl: (String, String, String),
) -> Self {
Self::new_with_output_encoding(wgsl, metal, hlsl, ShaderOutputEncoding::EncodedSrgb)
}
pub fn new_with_output_encoding(
wgsl: String,
metal: (String, String, String),
hlsl: (String, String, String),
output_encoding: ShaderOutputEncoding,
) -> Self {
static NEXT_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
Self {
id: NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
output_encoding,
glyph_mask: false,
path_mask: false,
wgsl,
metal,
hlsl,
}
}
pub fn with_glyph_mask(mut self) -> Self {
self.glyph_mask = true;
self.path_mask = false;
self
}
pub fn with_path_mask(mut self) -> Self {
self.path_mask = true;
self.glyph_mask = false;
self
}
pub fn uses_path_mask(&self) -> bool {
self.path_mask
}
pub fn uses_glyph_mask(&self) -> bool {
self.glyph_mask
}
pub fn output_encoding(&self) -> ShaderOutputEncoding {
self.output_encoding
}
pub fn wgsl(&self) -> &str {
&self.wgsl
}
pub fn metal(&self) -> &(String, String, String) {
&self.metal
}
pub fn hlsl(&self) -> &(String, String, String) {
&self.hlsl
}
pub fn id(&self) -> u64 {
self.id
}
}
#[derive(Clone, Debug)]
pub struct ShaderDraw {
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub opacity: f32,
pub scale_factor: f32,
pub program: std::sync::Arc<SceneShaderProgram>,
pub glyph_mask: Option<AtlasTile>,
pub path_mask: Option<std::sync::Arc<Path<ScaledPixels>>>,
pub data: [[f32; 4]; SHADER_DATA_SLOTS],
}
impl ShaderDraw {
pub fn validate_masks(&self) -> anyhow::Result<()> {
anyhow::ensure!(
self.program.uses_glyph_mask() == self.glyph_mask.is_some(),
"shader glyph mask/program mismatch"
);
anyhow::ensure!(
self.program.uses_path_mask() == self.path_mask.is_some(),
"shader path mask/program mismatch"
);
anyhow::ensure!(
self.glyph_mask.is_none() || self.path_mask.is_none(),
"shader cannot combine glyph and path masks"
);
if let Some(path) = &self.path_mask {
anyhow::ensure!(
!path.vertices.is_empty()
&& path.vertices.len() <= 65_536
&& path.vertices.len() % 3 == 0,
"path brush requires 1..65536 triangle vertices"
);
anyhow::ensure!(
path.color.as_solid().is_some_and(|c| c.a == 1.),
"path brush coverage must be opaque"
);
}
Ok(())
}
pub fn instance_data(&self) -> [[f32; 4]; 3 + SHADER_DATA_SLOTS] {
let mut result = [[0.; 4]; 3 + SHADER_DATA_SLOTS];
result[0] = [
self.bounds.origin.x.0,
self.bounds.origin.y.0,
self.bounds.size.width.0,
self.bounds.size.height.0,
];
let clip = self.content_mask.bounds;
result[1] = [
clip.origin.x.0,
clip.origin.y.0,
clip.origin.x.0 + clip.size.width.0,
clip.origin.y.0 + clip.size.height.0,
];
result[2] = [self.opacity, self.scale_factor, 0., 0.];
result[3..].copy_from_slice(&self.data);
result
}
}
pub trait GlyphBrush: std::fmt::Debug + Send + Sync + 'static {
fn cache_key(&self) -> std::sync::Arc<()>;
fn paint_glyph(
&self,
window: &mut crate::Window,
glyph: MonochromeSprite,
brush_bounds: Bounds<Pixels>,
opacity: f32,
) -> anyhow::Result<()>;
}