use std::borrow::Cow;
use crate::geometry::{Rect, Transform2D};
use crate::paint::{FillRule, StrokeSpace, StrokeStyle};
#[derive(Debug, Clone, Default)]
pub struct RenderFrame {
pub glyphs: Vec<GlyphQuad>,
pub images: Vec<ImageQuad>,
pub decorations: Vec<DecorationRect>,
pub cosmetic_lines: Vec<CosmeticLine>,
pub shapes: Vec<ShapeQuad>,
pub shadows: Vec<ShadowQuad>,
pub rasterized: Vec<RasterizedQuad>,
pub paths: Vec<PathEntry>,
pub animated_quads: Vec<AnimatedQuadDraw>,
pub anim_params: Vec<AnimParams>,
pub draw_order: Vec<DrawCommand>,
pub pending_images: Vec<PendingImage>,
pub layout_keys: Vec<u64>,
}
impl RenderFrame {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.draw_order.is_empty()
}
pub fn merge(&mut self, other: &RenderFrame) {
let glyph_offset = self.glyphs.len();
let image_offset = self.images.len();
let decoration_offset = self.decorations.len();
let cosmetic_line_offset = self.cosmetic_lines.len();
let shape_offset = self.shapes.len();
let shadow_offset = self.shadows.len();
let rasterized_offset = self.rasterized.len();
let path_offset = self.paths.len();
let animated_offset = self.animated_quads.len();
self.glyphs.extend_from_slice(&other.glyphs);
self.images.extend_from_slice(&other.images);
self.decorations.extend_from_slice(&other.decorations);
self.cosmetic_lines.extend_from_slice(&other.cosmetic_lines);
self.shapes.extend_from_slice(&other.shapes);
self.shadows.extend_from_slice(&other.shadows);
self.rasterized.extend_from_slice(&other.rasterized);
self.paths.extend_from_slice(&other.paths);
self.animated_quads.extend_from_slice(&other.animated_quads);
self.layout_keys.extend_from_slice(&other.layout_keys);
for pending in &other.pending_images {
if !self.pending_images.iter().any(|p| p.name == pending.name) {
self.pending_images.push(pending.clone());
}
}
for cmd in &other.draw_order {
let shifted = match cmd {
DrawCommand::Glyph(i) => DrawCommand::Glyph(i + glyph_offset),
DrawCommand::Image(i) => DrawCommand::Image(i + image_offset),
DrawCommand::Decoration(i) => DrawCommand::Decoration(i + decoration_offset),
DrawCommand::CosmeticLine(i) => DrawCommand::CosmeticLine(i + cosmetic_line_offset),
DrawCommand::Shape(i) => DrawCommand::Shape(i + shape_offset),
DrawCommand::Shadow(i) => DrawCommand::Shadow(i + shadow_offset),
DrawCommand::Rasterized(i) => DrawCommand::Rasterized(i + rasterized_offset),
DrawCommand::Path(i) => DrawCommand::Path(i + path_offset),
DrawCommand::AnimatedQuad(i) => DrawCommand::AnimatedQuad(i + animated_offset),
other => other.clone(),
};
self.draw_order.push(shifted);
}
}
}
impl RenderFrame {
pub fn debug_validate_stacks(&self) {
if !cfg!(debug_assertions) {
return;
}
let mut clip_depth: i32 = 0;
let mut opacity_depth: i32 = 0;
let mut blend_depth: i32 = 0;
let mut transform_depth: i32 = 0;
let mut blur_depth: i32 = 0;
for (i, cmd) in self.draw_order.iter().enumerate() {
match cmd {
DrawCommand::SetClip(_) => clip_depth += 1,
DrawCommand::ClearClip => {
clip_depth -= 1;
debug_assert!(
clip_depth >= 0,
"RenderFrame: ClearClip without matching SetClip at draw_order[{i}]"
);
}
DrawCommand::SetOpacity(_) => opacity_depth += 1,
DrawCommand::RestoreOpacity => {
opacity_depth -= 1;
debug_assert!(
opacity_depth >= 0,
"RenderFrame: RestoreOpacity without matching SetOpacity at draw_order[{i}]"
);
}
DrawCommand::SetBlendMode(_) => blend_depth += 1,
DrawCommand::RestoreBlendMode => {
blend_depth -= 1;
debug_assert!(
blend_depth >= 0,
"RenderFrame: RestoreBlendMode without matching SetBlendMode at draw_order[{i}]"
);
}
DrawCommand::PushTransform(_) => transform_depth += 1,
DrawCommand::PopTransform => {
transform_depth -= 1;
debug_assert!(
transform_depth >= 0,
"RenderFrame: PopTransform without matching PushTransform at draw_order[{i}]"
);
}
DrawCommand::BeginBlurredSubtree { .. } => blur_depth += 1,
DrawCommand::EndBlurredSubtree => {
blur_depth -= 1;
debug_assert!(
blur_depth >= 0,
"RenderFrame: EndBlurredSubtree without matching BeginBlurredSubtree at draw_order[{i}]"
);
}
_ => {}
}
}
debug_assert!(
clip_depth == 0,
"RenderFrame: {clip_depth} unmatched SetClip(s) without ClearClip"
);
debug_assert!(
opacity_depth == 0,
"RenderFrame: {opacity_depth} unmatched SetOpacity(s) without RestoreOpacity"
);
debug_assert!(
blend_depth == 0,
"RenderFrame: {blend_depth} unmatched SetBlendMode(s) without RestoreBlendMode"
);
debug_assert!(
transform_depth == 0,
"RenderFrame: {transform_depth} unmatched PushTransform(s) without PopTransform"
);
debug_assert!(
blur_depth == 0,
"RenderFrame: {blur_depth} unmatched BeginBlurredSubtree(s) without EndBlurredSubtree"
);
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GlyphQuad {
pub screen: [f32; 4],
pub atlas: [f32; 4],
pub color: [f32; 4],
pub is_color: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImageQuad {
pub screen: [f32; 4],
pub name: String,
pub tint: Option<[f32; 4]>,
}
#[derive(Debug, Clone)]
pub struct PendingImage {
pub name: String,
pub width: u32,
pub height: u32,
pub pixels: Cow<'static, [u8]>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DecorationRect {
pub rect: [f32; 4],
pub color: [f32; 4],
pub kind: DecorationKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecorationKind {
WidgetBackground,
Selection,
Cursor,
Underline,
Overline,
Strikeout,
FocusRing,
DropIndicator,
TableBorder,
TableCellBackground,
BlockBackground,
TextBackground,
CellSelection,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CosmeticLine {
pub from: [f32; 2],
pub to: [f32; 2],
pub width: f32,
pub color: [f32; 4],
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShapeQuad {
pub screen: [f32; 4],
pub color: [f32; 4],
pub shape: ShapeKind,
pub stroke_width: f32,
pub stroke_space: StrokeSpace,
pub corner_radii: [f32; 4],
pub paint_data: PaintData,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShapeKind {
RoundedRect,
Circle,
Ellipse,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RasterizedQuad {
pub screen: [f32; 4],
pub atlas: [f32; 4],
pub color: [f32; 4],
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShadowQuad {
pub screen: [f32; 4],
pub color: [f32; 4],
pub corner_radii: [f32; 4],
pub shape_rect: [f32; 4],
pub blur_radius: f32,
pub spread: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PathEntry {
pub path: crate::path::Path,
pub color: [f32; 4],
pub stroke_style: StrokeStyle,
pub fill_rule: FillRule,
pub bounds: [f32; 4],
pub paint_data: PaintData,
}
#[derive(Debug, Default, Clone, PartialEq)]
pub enum PaintData {
#[default]
Solid,
LinearGradient {
start: [f32; 2],
end: [f32; 2],
stops: Vec<crate::paint::GradientStop>,
},
RadialGradient {
center: [f32; 2],
radius: f32,
stops: Vec<crate::paint::GradientStop>,
},
ConicGradient {
center: [f32; 2],
start_angle: f32,
stops: Vec<crate::paint::GradientStop>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BlendMode {
#[default]
Normal,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DrawCommand {
Glyph(usize),
Image(usize),
Decoration(usize),
CosmeticLine(usize),
Shape(usize),
Shadow(usize),
Rasterized(usize),
Path(usize),
AnimatedQuad(usize),
SetClip(Rect),
ClearClip,
SetOpacity(f32),
RestoreOpacity,
SetBlendMode(BlendMode),
RestoreBlendMode,
SetTransform(Transform2D),
PushTransform(Transform2D),
PopTransform,
BeginBlurredSubtree {
bounds: Rect,
radius: f32,
},
EndBlurredSubtree,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AnimatedQuadDraw {
pub screen: [f32; 4],
pub slot: u32,
pub class: AnimatedQuadClass,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AnimatedQuadClass {
Procedural,
Sprite { image_name: String },
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct AnimParams {
pub kind: u32,
pub phase: f32,
pub sweep_ratio: f32,
pub _pad0: f32,
pub color0: [f32; 4],
pub color1: [f32; 4],
pub atlas_cols: f32,
pub atlas_rows: f32,
pub _pad1: [f32; 2],
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn merge_frames() {
let mut a = RenderFrame::new();
a.shapes.push(ShapeQuad {
screen: [0.0, 0.0, 10.0, 10.0],
color: [1.0, 0.0, 0.0, 1.0],
shape: ShapeKind::RoundedRect,
stroke_width: 0.0,
stroke_space: StrokeSpace::Logical,
corner_radii: [0.0; 4],
paint_data: PaintData::Solid,
});
a.draw_order.push(DrawCommand::Shape(0));
let mut b = RenderFrame::new();
b.decorations.push(DecorationRect {
rect: [0.0, 0.0, 5.0, 5.0],
color: [0.0, 0.0, 1.0, 1.0],
kind: DecorationKind::FocusRing,
});
b.draw_order.push(DrawCommand::Decoration(0));
a.merge(&b);
assert_eq!(a.shapes.len(), 1);
assert_eq!(a.decorations.len(), 1);
assert_eq!(a.draw_order.len(), 2);
assert_eq!(a.draw_order[1], DrawCommand::Decoration(0));
}
#[test]
fn merge_preserves_state_commands() {
let mut a = RenderFrame::new();
a.draw_order.push(DrawCommand::SetOpacity(0.5));
let mut b = RenderFrame::new();
b.draw_order.push(DrawCommand::RestoreOpacity);
a.merge(&b);
assert_eq!(a.draw_order.len(), 2);
assert_eq!(a.draw_order[0], DrawCommand::SetOpacity(0.5));
assert_eq!(a.draw_order[1], DrawCommand::RestoreOpacity);
}
}