use std::sync::Arc;
use valo_dl::{BlendMode, GlyphPos, Image, Paint, PaintStyle, Sampling, Shader};
use valo_geometry::{Color, FillRule, Matrix, Path, Rect};
use valo_text::Font;
use crate::pipelines::{Frag, PipelineKind};
use super::Planner;
pub(super) enum DrawSource<'a> {
Rect(&'a Rect),
Path {
path: &'a Arc<Path>,
rule: FillRule,
},
Image {
image: &'a Image,
src: &'a Rect,
dst: &'a Rect,
sampling: Sampling,
},
RRectBlur {
rect: &'a Rect,
radii: [f32; 4],
},
Glyphs(GlyphRun<'a>),
}
#[derive(Clone, Copy)]
pub(super) struct GlyphRun<'a> {
pub font: &'a Arc<Font>,
pub size: f32,
pub glyphs: &'a Arc<Vec<GlyphPos>>,
pub device_bounds: Rect,
}
impl Planner<'_> {
pub(super) fn plan_routed(
&mut self,
source: DrawSource<'_>,
paint: &Paint,
current: &Matrix,
device_bounds: Rect,
z: f32,
) {
self.stats.draws += 1;
if let DrawSource::RRectBlur { rect, radii } = source {
return self.plan_rrect_blur(rect, radii, paint, current, z);
}
let folded = match source {
DrawSource::Image { .. } | DrawSource::Glyphs(_) => None,
_ => self.prepare_paint(paint),
};
let paint = folded.as_ref().unwrap_or(paint);
if needs_effect_layer(&source, paint) {
return self.plan_effect_layer(source, paint, current, device_bounds, z);
}
if let Some(mode) = advanced_mode(paint) {
return self.plan_advanced_blend(source, paint, current, z, mode);
}
if let Some(run) = masked_glyph_run(&source, paint) {
return self.plan_masked_glyphs(&run, paint, current, z);
}
self.emit_direct(source, paint, current, z);
}
fn plan_masked_glyphs(&mut self, run: &GlyphRun<'_>, paint: &Paint, current: &Matrix, z: f32) {
let local_quad = current.invert().map_or(run.device_bounds, |inverse| {
inverse.map_rect(&run.device_bounds)
});
let fill = Paint {
shader: paint.shader.clone(),
color: paint.color,
blend_mode: BlendMode::SrcIn,
..Default::default()
};
let (font, glyphs, current2) = (run.font.clone(), run.glyphs.clone(), *current);
let (size, style) = (run.size, paint.style.clone());
self.plan_via_implicit_layer(run.device_bounds, z, paint.blend_mode, move |p| {
let mask = Paint {
style: style.clone(),
..Paint::from_color(Color::WHITE)
};
p.plan_glyph_tiers(&font, size, &mask, &glyphs, ¤t2, 0.5);
p.emit_rect_quad(&local_quad, &fill, ¤t2, 0.5);
});
}
fn plan_effect_layer(
&mut self,
source: DrawSource<'_>,
paint: &Paint,
current: &Matrix,
device_bounds: Rect,
z: f32,
) {
match source {
DrawSource::Rect(rect) => {
let (rect, inner_paint, current2) = (*rect, plain(paint), *current);
self.plan_via_effect_layer_at(device_bounds, paint, current, z, move |p| {
p.emit_rect_quad(&rect, &inner_paint, ¤t2, 0.5);
});
}
DrawSource::Path { path, rule } => {
let (path, inner_paint, current2) = (path.clone(), plain(paint), *current);
self.plan_via_effect_layer_at(device_bounds, paint, current, z, move |p| {
p.emit_path(&path, rule, &inner_paint, ¤t2, 0.5);
});
}
DrawSource::Image {
image,
src,
dst,
sampling,
} => {
let (image, src, dst, inner_paint, current2) =
(image.clone(), *src, *dst, plain(paint), *current);
self.plan_via_effect_layer_at(device_bounds, paint, current, z, move |p| {
p.emit_image(&image, &src, &dst, sampling, &inner_paint, ¤t2, 0.5);
});
}
DrawSource::Glyphs(run) => {
let (font, glyphs, current2) = (run.font.clone(), run.glyphs.clone(), *current);
let (size, inner_paint) = (run.size, plain(paint));
self.plan_via_effect_layer_at(device_bounds, paint, current, z, move |p| {
p.plan_glyph_tiers(&font, size, &inner_paint, &glyphs, ¤t2, 0.5);
});
}
DrawSource::RRectBlur { .. } => unreachable!("handled before routing"),
}
}
fn prepare_paint(&mut self, paint: &Paint) -> Option<Paint> {
if let Some(folded) = folded_paint(paint) {
return Some(folded);
}
if paint.mask_blur.is_some() {
return None;
}
let filter = paint.color_filter?;
let Some(Shader::Image { image, .. }) = paint.shader.as_ref() else {
return None;
};
let filtered_image = self.filtered_image(image, filter);
let mut prepared = paint.clone();
let Some(Shader::Image { image, .. }) = prepared.shader.as_mut() else {
unreachable!("source kind changed while cloning paint");
};
*image = filtered_image;
prepared.color_filter = None;
Some(prepared)
}
fn emit_direct(&mut self, source: DrawSource<'_>, paint: &Paint, current: &Matrix, z: f32) {
match source {
DrawSource::Rect(rect) => self.emit_rect_quad(rect, paint, current, z),
DrawSource::Path { path, rule } => self.emit_path(path, rule, paint, current, z),
DrawSource::Image {
image,
src,
dst,
sampling,
} => self.emit_image(image, src, dst, sampling, paint, current, z),
DrawSource::Glyphs(run) => {
self.plan_glyph_tiers(run.font, run.size, paint, run.glyphs, current, z)
}
DrawSource::RRectBlur { .. } => unreachable!("handled before routing"),
}
}
fn plan_rrect_blur(
&mut self,
rect: &Rect,
radii: [f32; 4],
paint: &Paint,
current: &Matrix,
z: f32,
) {
if let Some(mode) = advanced_mode(paint) {
let device_bounds = current.map_rect(&rect.expand(paint.mask_padding()));
let (rect, paint, current) = (*rect, paint.clone(), *current);
self.plan_via_implicit_layer(device_bounds, z, mode, move |p| {
p.emit_rrect_blur(&rect, radii, &paint, ¤t, 0.5);
});
return;
}
self.emit_rrect_blur(rect, radii, paint, current, z);
}
fn plan_advanced_blend(
&mut self,
source: DrawSource<'_>,
paint: &Paint,
current: &Matrix,
z: f32,
mode: BlendMode,
) {
match source {
DrawSource::Rect(rect) if paint.shader.is_none() => {
let snapshot = self.break_pass(¤t.map_rect(rect));
let group_alpha = self.elision_alpha();
let frame = self.frames.last_mut().expect("frame stack never empty");
self.emit.blend_solid_quad(
frame,
group_alpha,
PipelineKind::Draw(Frag::BlendSolid),
rect,
paint,
current,
z,
mode,
&snapshot,
);
}
DrawSource::Path { path, rule }
if paint.shader.is_none() && matches!(paint.style, PaintStyle::Fill) =>
{
let bounds = path.bounds();
let Some(mesh) = self.stencil_fan_mesh(path, current) else {
return;
};
let frame = self.frames.last_mut().expect("frame stack never empty");
self.emit.push_fan(frame, rule, current, mesh, z);
let snapshot = self.break_pass(¤t.map_rect(&bounds));
let group_alpha = self.elision_alpha();
let frame = self.frames.last_mut().expect("frame stack never empty");
self.emit.blend_solid_quad(
frame,
group_alpha,
PipelineKind::Cover(Frag::BlendSolid),
&bounds,
paint,
current,
z,
mode,
&snapshot,
);
}
DrawSource::Rect(rect) => {
let device_bounds = current.map_rect(rect);
let (rect, paint, current) = (*rect, paint.clone(), *current);
self.plan_via_implicit_layer(device_bounds, z, mode, move |p| {
p.emit_rect_quad(&rect, &paint, ¤t, 0.5);
});
}
DrawSource::Path { path, rule } => {
let padded = path
.bounds()
.expand(paint.stroke_padding_at_scale(current.max_scale()));
let device_bounds = current.map_rect(&padded);
let (path, paint, current) = (path.clone(), plain(paint), *current);
self.plan_via_implicit_layer(device_bounds, z, mode, move |p| {
p.emit_path(&path, rule, &paint, ¤t, 0.5);
});
}
DrawSource::Image {
image,
src,
dst,
sampling,
} => {
let device_bounds = current.map_rect(dst);
let (image, src, dst, paint, current) =
(image.clone(), *src, *dst, paint.clone(), *current);
self.plan_via_implicit_layer(device_bounds, z, mode, move |p| {
p.emit_image(&image, &src, &dst, sampling, &paint, ¤t, 0.5);
});
}
DrawSource::Glyphs(run) => {
let (font, glyphs, current2) = (run.font.clone(), run.glyphs.clone(), *current);
let (size, inner_paint) = (run.size, plain(paint));
self.plan_via_implicit_layer(run.device_bounds, z, mode, move |p| {
p.plan_glyph_tiers(&font, size, &inner_paint, &glyphs, ¤t2, 0.5);
});
}
DrawSource::RRectBlur { .. } => unreachable!("handled before routing"),
}
}
}
fn masked_glyph_run<'a>(source: &DrawSource<'a>, paint: &Paint) -> Option<GlyphRun<'a>> {
match source {
DrawSource::Glyphs(run) if paint.shader.is_some() => Some(*run),
_ => None,
}
}
fn needs_effect_layer(source: &DrawSource<'_>, paint: &Paint) -> bool {
let blur_family = paint.mask_blur.is_some() || paint.effective_image_filter().is_some();
match source {
DrawSource::Image { .. } => blur_family,
_ => blur_family || paint.color_filter.is_some(),
}
}
fn advanced_mode(paint: &Paint) -> Option<BlendMode> {
(!paint.blend_mode.is_pipeline_blendable()).then_some(paint.blend_mode)
}
fn folded_paint(paint: &Paint) -> Option<Paint> {
let filter = paint.color_filter?;
let mut folded = paint.clone();
match &mut folded.shader {
Some(shader) => {
if !shader.fold_color_filter(&filter) {
return None;
}
}
None => folded.color = filter.folded_into(paint.color)?,
}
folded.color_filter = None;
Some(folded)
}
fn plain(paint: &Paint) -> Paint {
Paint {
blend_mode: BlendMode::SrcOver,
mask_blur: None,
color_filter: None,
image_filter: None,
..paint.clone()
}
}