use blinc_core::{
Affine2D, BillboardFacing, BlendMode, Brush, Camera, ClipShape, Color, CornerRadius,
DrawCommand, DrawContext, Environment, ImageId, ImageOptions, LayerConfig, LayerId, Light,
Mat4, MaterialId, MeshId, MeshInstance, ParticleBlendMode, ParticleEmitterShape, ParticleForce,
ParticleSystemData, Path, Point, Rect, Sdf3DViewport, SdfBuilder, Shadow, ShapeId, Size,
Stroke, TextStyle, Transform,
};
use crate::path::{extract_brush_info, tessellate_fill, tessellate_stroke};
use crate::primitives::{
ClipType, FillType, GlassType, GpuGlassPrimitive, GpuPrimitive, PrimitiveBatch, PrimitiveType,
Sdf3DUniform, Viewport3D,
};
use crate::text::TextRenderingContext;
#[derive(Clone, Debug)]
#[allow(dead_code)]
struct TransformState {
affine: Affine2D,
opacity: f32,
blend_mode: BlendMode,
}
impl Default for TransformState {
fn default() -> Self {
Self {
affine: Affine2D::IDENTITY,
opacity: 1.0,
blend_mode: BlendMode::Normal,
}
}
}
#[derive(Clone, Debug)]
struct LayerState {
config: LayerConfig,
primitive_start: usize,
foreground_primitive_start: usize,
path_start: usize,
foreground_path_start: usize,
parent_state_indices: (usize, usize, usize, usize),
}
type ClipStackEntry = (ClipShape, Option<(u32, u32)>, [f32; 4]);
pub struct GpuPaintContext<'a> {
batch: PrimitiveBatch,
transform_stack: Vec<Affine2D>,
opacity_stack: Vec<f32>,
blend_mode_stack: Vec<BlendMode>,
clip_stack: Vec<ClipStackEntry>,
viewport: Size,
is_3d: bool,
camera: Option<Camera>,
lights: Vec<Light>,
text_ctx: Option<&'a mut TextRenderingContext>,
is_foreground: bool,
z_layer: u32,
layer_stack: Vec<LayerState>,
current_3d_sin_ry: f32,
current_3d_cos_ry: f32,
current_3d_sin_rx: f32,
current_3d_cos_rx: f32,
current_3d_perspective_d: f32,
current_3d_shape_type: f32,
current_3d_depth: f32,
current_3d_ambient: f32,
current_3d_specular: f32,
current_3d_translate_z: f32,
current_3d_light: [f32; 4],
current_3d_group_shapes: Vec<crate::primitives::ShapeDesc>,
current_filter_a: [f32; 4], current_filter_b: [f32; 4], current_mask_params: [f32; 4], current_mask_info: [f32; 4], current_corner_shape: [f32; 4], pending_overflow_fade: [f32; 4], }
impl<'a> GpuPaintContext<'a> {
pub fn new(width: f32, height: f32) -> Self {
Self {
batch: PrimitiveBatch::new(),
transform_stack: vec![Affine2D::IDENTITY],
opacity_stack: vec![1.0],
blend_mode_stack: vec![BlendMode::Normal],
clip_stack: Vec::new(),
viewport: Size::new(width, height),
is_3d: false,
camera: None,
lights: Vec::new(),
text_ctx: None,
is_foreground: false,
z_layer: 0,
layer_stack: Vec::new(),
current_3d_sin_ry: 0.0,
current_3d_cos_ry: 1.0,
current_3d_sin_rx: 0.0,
current_3d_cos_rx: 1.0,
current_3d_perspective_d: 0.0,
current_3d_shape_type: 0.0,
current_3d_depth: 0.0,
current_3d_ambient: 0.3,
current_3d_specular: 32.0,
current_3d_translate_z: 0.0,
current_3d_light: [0.0, -1.0, 0.5, 0.8],
current_3d_group_shapes: Vec::new(),
current_filter_a: [0.0, 0.0, 0.0, 0.0],
current_filter_b: [1.0, 1.0, 1.0, 0.0],
current_mask_params: [0.0; 4],
current_mask_info: [0.0; 4],
current_corner_shape: [1.0; 4],
pending_overflow_fade: [0.0; 4],
}
}
pub fn set_foreground(&mut self, is_foreground: bool) {
self.is_foreground = is_foreground;
}
pub fn with_text_context(
width: f32,
height: f32,
text_ctx: &'a mut TextRenderingContext,
) -> Self {
Self {
batch: PrimitiveBatch::new(),
transform_stack: vec![Affine2D::IDENTITY],
opacity_stack: vec![1.0],
blend_mode_stack: vec![BlendMode::Normal],
clip_stack: Vec::new(),
viewport: Size::new(width, height),
is_3d: false,
camera: None,
lights: Vec::new(),
text_ctx: Some(text_ctx),
is_foreground: false,
z_layer: 0,
layer_stack: Vec::new(),
current_3d_sin_ry: 0.0,
current_3d_cos_ry: 1.0,
current_3d_sin_rx: 0.0,
current_3d_cos_rx: 1.0,
current_3d_perspective_d: 0.0,
current_3d_shape_type: 0.0,
current_3d_depth: 0.0,
current_3d_ambient: 0.3,
current_3d_specular: 32.0,
current_3d_translate_z: 0.0,
current_3d_light: [0.0, -1.0, 0.5, 0.8],
current_3d_group_shapes: Vec::new(),
current_filter_a: [0.0, 0.0, 0.0, 0.0],
current_filter_b: [1.0, 1.0, 1.0, 0.0],
current_mask_params: [0.0; 4],
current_mask_info: [0.0; 4],
current_corner_shape: [1.0; 4],
pending_overflow_fade: [0.0; 4],
}
}
pub fn set_text_context(&mut self, text_ctx: &'a mut TextRenderingContext) {
self.text_ctx = Some(text_ctx);
}
fn current_affine(&self) -> Affine2D {
self.transform_stack
.last()
.copied()
.unwrap_or(Affine2D::IDENTITY)
}
fn combined_opacity(&self) -> f32 {
self.opacity_stack.iter().product()
}
fn transform_point(&self, p: Point) -> Point {
let affine = self.current_affine();
Point::new(
affine.elements[0] * p.x + affine.elements[2] * p.y + affine.elements[4],
affine.elements[1] * p.x + affine.elements[3] * p.y + affine.elements[5],
)
}
fn current_uniform_scale(&self) -> f32 {
let affine = self.current_affine();
let [a, b, c, d, ..] = affine.elements;
let det = a * d - b * c;
det.abs().sqrt().max(1e-6)
}
fn transform_rect(&self, rect: Rect) -> Rect {
let affine = self.current_affine();
let [a, b, c, d, ..] = affine.elements;
let det = a * d - b * c;
let uniform_scale = det.abs().sqrt().max(1e-6);
let center = Point::new(
rect.origin.x + rect.size.width * 0.5,
rect.origin.y + rect.size.height * 0.5,
);
let tc = self.transform_point(center);
let sw = rect.size.width * uniform_scale;
let sh = rect.size.height * uniform_scale;
Rect::new(tc.x - sw * 0.5, tc.y - sh * 0.5, sw, sh)
}
fn current_rotation_sincos(&self) -> [f32; 4] {
let affine = self.current_affine();
let a = affine.elements[0];
let b = affine.elements[1];
let scale = (a * a + b * b).sqrt();
if scale < 1e-6 {
return [0.0, 1.0, self.current_3d_sin_ry, self.current_3d_cos_ry];
}
[
b / scale,
a / scale,
self.current_3d_sin_ry,
self.current_3d_cos_ry,
]
}
fn current_dpi_scale(&self) -> f32 {
let affine = self.current_affine();
let a = affine.elements[0];
let b = affine.elements[1];
let c = affine.elements[2];
let d = affine.elements[3];
let scale_x = (a * a + b * b).sqrt();
let scale_y = (c * c + d * d).sqrt();
(scale_x + scale_y) * 0.5
}
fn current_local_affine(&self) -> [f32; 4] {
let affine = self.current_affine();
let [a, b, c, d, ..] = affine.elements;
let det = a * d - b * c;
let uniform_scale = det.abs().sqrt().max(1e-6);
[
a / uniform_scale,
b / uniform_scale,
c / uniform_scale,
d / uniform_scale,
]
}
fn current_perspective_params(&self) -> [f32; 4] {
let scale = self.current_dpi_scale();
[
self.current_3d_sin_rx,
self.current_3d_cos_rx,
self.current_3d_perspective_d * scale,
self.current_3d_shape_type,
]
}
fn current_sdf_3d_params(&self) -> [f32; 4] {
let scale = self.current_dpi_scale();
[
self.current_3d_depth * scale,
self.current_3d_ambient,
self.current_3d_specular,
self.current_3d_translate_z * scale,
]
}
fn current_light_params(&self) -> [f32; 4] {
self.current_3d_light
}
pub fn set_3d_transform(&mut self, rx_rad: f32, ry_rad: f32, perspective_d: f32) {
self.current_3d_sin_rx = rx_rad.sin();
self.current_3d_cos_rx = rx_rad.cos();
self.current_3d_sin_ry = ry_rad.sin();
self.current_3d_cos_ry = ry_rad.cos();
self.current_3d_perspective_d = perspective_d;
}
pub fn set_3d_shape(&mut self, shape_type: f32, depth: f32, ambient: f32, specular: f32) {
self.current_3d_shape_type = shape_type;
self.current_3d_depth = depth;
self.current_3d_ambient = ambient;
self.current_3d_specular = specular;
}
pub fn set_3d_light(&mut self, direction: [f32; 3], intensity: f32) {
self.current_3d_light = [direction[0], direction[1], direction[2], intensity];
}
pub fn set_3d_translate_z(&mut self, z: f32) {
self.current_3d_translate_z = z;
}
pub fn set_3d_group(&mut self, shapes: &[crate::primitives::ShapeDesc]) {
self.current_3d_group_shapes = shapes.to_vec();
}
pub fn clear_3d(&mut self) {
self.current_3d_sin_ry = 0.0;
self.current_3d_cos_ry = 1.0;
self.current_3d_sin_rx = 0.0;
self.current_3d_cos_rx = 1.0;
self.current_3d_perspective_d = 0.0;
self.current_3d_shape_type = 0.0;
self.current_3d_depth = 0.0;
self.current_3d_ambient = 0.3;
self.current_3d_specular = 32.0;
self.current_3d_translate_z = 0.0;
self.current_3d_light = [0.0, -1.0, 0.5, 0.8];
self.current_3d_group_shapes.clear();
}
#[allow(clippy::too_many_arguments)]
pub fn set_css_filter(
&mut self,
grayscale: f32,
invert: f32,
sepia: f32,
hue_rotate_deg: f32,
brightness: f32,
contrast: f32,
saturate: f32,
) {
self.current_filter_a = [grayscale, invert, sepia, hue_rotate_deg.to_radians()];
self.current_filter_b = [brightness, contrast, saturate, 0.0];
}
pub fn clear_css_filter(&mut self) {
self.current_filter_a = [0.0, 0.0, 0.0, 0.0];
self.current_filter_b = [1.0, 1.0, 1.0, 0.0];
}
pub fn set_mask_gradient(&mut self, params: [f32; 4], info: [f32; 4]) {
self.current_mask_params = params;
self.current_mask_info = info;
}
pub fn clear_mask_gradient(&mut self) {
self.current_mask_params = [0.0; 4];
self.current_mask_info = [0.0; 4];
}
pub fn set_corner_shape_values(&mut self, shape: [f32; 4]) {
self.current_corner_shape = shape;
}
pub fn clear_corner_shape_values(&mut self) {
self.current_corner_shape = [1.0; 4];
}
pub fn set_overflow_fade_values(&mut self, fade: [f32; 4]) {
self.pending_overflow_fade = fade;
}
fn get_clip_fade(&self) -> [f32; 4] {
for (_clip, _poly_meta, fade) in self.clip_stack.iter().rev() {
if fade[0] > 0.0 || fade[1] > 0.0 || fade[2] > 0.0 || fade[3] > 0.0 {
let affine = self.current_affine();
let sx = (affine.elements[0] * affine.elements[0]
+ affine.elements[1] * affine.elements[1])
.sqrt();
let sy = (affine.elements[2] * affine.elements[2]
+ affine.elements[3] * affine.elements[3])
.sqrt();
return [
fade[0] * sy, fade[1] * sx, fade[2] * sy, fade[3] * sx, ];
}
}
[0.0; 4]
}
fn scale_corner_radius(&self, corner_radius: CornerRadius) -> CornerRadius {
let affine = self.current_affine();
let a = affine.elements[0];
let b = affine.elements[1];
let c = affine.elements[2];
let d = affine.elements[3];
let scale_x = (a * a + b * b).sqrt();
let scale_y = (c * c + d * d).sqrt();
let avg_scale = (scale_x + scale_y) / 2.0;
CornerRadius::new(
corner_radius.top_left * avg_scale,
corner_radius.top_right * avg_scale,
corner_radius.bottom_right * avg_scale,
corner_radius.bottom_left * avg_scale,
)
}
fn obb_to_rect_coords(
brush: &Brush,
params: [f32; 4],
rect: Rect,
fill_type: FillType,
) -> [f32; 4] {
let is_obb = matches!(
brush,
Brush::Gradient(blinc_core::Gradient::Linear {
space: blinc_core::GradientSpace::ObjectBoundingBox,
..
}) | Brush::Gradient(blinc_core::Gradient::Radial {
space: blinc_core::GradientSpace::ObjectBoundingBox,
..
}) | Brush::Gradient(blinc_core::Gradient::Conic {
space: blinc_core::GradientSpace::ObjectBoundingBox,
..
})
);
if !is_obb || fill_type == FillType::Solid {
return params;
}
let is_radial = fill_type == FillType::RadialGradient;
if is_radial {
[
rect.x() + params[0] * rect.width(),
rect.y() + params[1] * rect.height(),
params[2] * rect.width().max(rect.height()),
params[3],
]
} else {
[
rect.x() + params[0] * rect.width(),
rect.y() + params[1] * rect.height(),
rect.x() + params[2] * rect.width(),
rect.y() + params[3] * rect.height(),
]
}
}
fn transform_gradient_params(&self, params: [f32; 4], is_radial: bool) -> [f32; 4] {
if is_radial {
let center = self.transform_point(Point::new(params[0], params[1]));
let affine = self.current_affine();
let a = affine.elements[0];
let b = affine.elements[1];
let c = affine.elements[2];
let d = affine.elements[3];
let scale_x = (a * a + b * b).sqrt();
let scale_y = (c * c + d * d).sqrt();
let avg_scale = (scale_x + scale_y) / 2.0;
[center.x, center.y, params[2] * avg_scale, params[3]]
} else {
let start = self.transform_point(Point::new(params[0], params[1]));
let end = self.transform_point(Point::new(params[2], params[3]));
[start.x, start.y, end.x, end.y]
}
}
fn transform_clip_shape(&self, shape: ClipShape) -> ClipShape {
let affine = self.current_affine();
if affine == Affine2D::IDENTITY {
return shape;
}
match shape {
ClipShape::Rect(rect) => {
let corners = [
Point::new(rect.x(), rect.y()),
Point::new(rect.x() + rect.width(), rect.y()),
Point::new(rect.x() + rect.width(), rect.y() + rect.height()),
Point::new(rect.x(), rect.y() + rect.height()),
];
let transformed: Vec<Point> =
corners.iter().map(|p| self.transform_point(*p)).collect();
let min_x = transformed
.iter()
.map(|p| p.x)
.fold(f32::INFINITY, f32::min);
let max_x = transformed
.iter()
.map(|p| p.x)
.fold(f32::NEG_INFINITY, f32::max);
let min_y = transformed
.iter()
.map(|p| p.y)
.fold(f32::INFINITY, f32::min);
let max_y = transformed
.iter()
.map(|p| p.y)
.fold(f32::NEG_INFINITY, f32::max);
ClipShape::Rect(Rect::new(min_x, min_y, max_x - min_x, max_y - min_y))
}
ClipShape::RoundedRect {
rect,
corner_radius,
} => {
let corners = [
Point::new(rect.x(), rect.y()),
Point::new(rect.x() + rect.width(), rect.y()),
Point::new(rect.x() + rect.width(), rect.y() + rect.height()),
Point::new(rect.x(), rect.y() + rect.height()),
];
let transformed: Vec<Point> =
corners.iter().map(|p| self.transform_point(*p)).collect();
let min_x = transformed
.iter()
.map(|p| p.x)
.fold(f32::INFINITY, f32::min);
let max_x = transformed
.iter()
.map(|p| p.x)
.fold(f32::NEG_INFINITY, f32::max);
let min_y = transformed
.iter()
.map(|p| p.y)
.fold(f32::INFINITY, f32::min);
let max_y = transformed
.iter()
.map(|p| p.y)
.fold(f32::NEG_INFINITY, f32::max);
let a = affine.elements[0];
let b = affine.elements[1];
let c = affine.elements[2];
let d = affine.elements[3];
let scale_x = (a * a + b * b).sqrt();
let scale_y = (c * c + d * d).sqrt();
let avg_scale = (scale_x + scale_y) * 0.5;
let scaled_radius = CornerRadius::new(
corner_radius.top_left * avg_scale,
corner_radius.top_right * avg_scale,
corner_radius.bottom_right * avg_scale,
corner_radius.bottom_left * avg_scale,
);
ClipShape::RoundedRect {
rect: Rect::new(min_x, min_y, max_x - min_x, max_y - min_y),
corner_radius: scaled_radius,
}
}
ClipShape::Circle { center, radius } => {
let transformed_center = self.transform_point(center);
let a = affine.elements[0];
let b = affine.elements[1];
let c = affine.elements[2];
let d = affine.elements[3];
let scale_x = (a * a + b * b).sqrt();
let scale_y = (c * c + d * d).sqrt();
if (scale_x - scale_y).abs() < 0.001 {
ClipShape::Circle {
center: transformed_center,
radius: radius * scale_x,
}
} else {
ClipShape::Ellipse {
center: transformed_center,
radii: blinc_core::Vec2::new(radius * scale_x, radius * scale_y),
}
}
}
ClipShape::Ellipse { center, radii } => {
let transformed_center = self.transform_point(center);
let a = affine.elements[0];
let b = affine.elements[1];
let c = affine.elements[2];
let d = affine.elements[3];
let scale_x = (a * a + b * b).sqrt();
let scale_y = (c * c + d * d).sqrt();
ClipShape::Ellipse {
center: transformed_center,
radii: blinc_core::Vec2::new(radii.x * scale_x, radii.y * scale_y),
}
}
ClipShape::Path(path) => {
ClipShape::Path(path)
}
ClipShape::Polygon(pts) => {
let transformed: Vec<Point> =
pts.iter().map(|p| self.transform_point(*p)).collect();
ClipShape::Polygon(transformed)
}
}
}
fn brush_to_colors(&self, brush: &Brush) -> ([f32; 4], [f32; 4], [f32; 4], FillType) {
let opacity = self.combined_opacity();
match brush {
Brush::Solid(color) => {
let c = [color.r, color.g, color.b, color.a * opacity];
(c, c, [0.0, 0.0, 1.0, 0.0], FillType::Solid)
}
Brush::Glass(_) => {
([0.0; 4], [0.0; 4], [0.0, 0.0, 1.0, 0.0], FillType::Solid)
}
Brush::Image(_) => {
([0.0; 4], [0.0; 4], [0.0, 0.0, 1.0, 0.0], FillType::Solid)
}
Brush::Blur(_) => {
([0.0; 4], [0.0; 4], [0.0, 0.0, 1.0, 0.0], FillType::Solid)
}
Brush::Gradient(gradient) => {
let (stops, fill_type, gradient_params) = match gradient {
blinc_core::Gradient::Linear {
start, end, stops, ..
} => {
(
stops,
FillType::LinearGradient,
[start.x, start.y, end.x, end.y],
)
}
blinc_core::Gradient::Radial {
center,
radius,
stops,
..
} => {
(
stops,
FillType::RadialGradient,
[center.x, center.y, *radius, 0.0],
)
}
blinc_core::Gradient::Conic { center, stops, .. } => (
stops,
FillType::RadialGradient,
[center.x, center.y, 100.0, 0.0],
),
};
let (c1, c2) = if stops.len() >= 2 {
let s1 = &stops[0];
let s2 = &stops[stops.len() - 1];
(
[s1.color.r, s1.color.g, s1.color.b, s1.color.a * opacity],
[s2.color.r, s2.color.g, s2.color.b, s2.color.a * opacity],
)
} else if !stops.is_empty() {
let c = &stops[0].color;
let arr = [c.r, c.g, c.b, c.a * opacity];
(arr, arr)
} else {
([1.0, 1.0, 1.0, opacity], [1.0, 1.0, 1.0, opacity])
};
(c1, c2, gradient_params, fill_type)
}
}
}
fn get_clip_data(&self) -> ([f32; 4], [f32; 4], ClipType) {
if self.clip_stack.is_empty() {
return (
[-10000.0, -10000.0, 100000.0, 100000.0],
[0.0; 4],
ClipType::None,
);
}
let mut intersect_min_x = f32::NEG_INFINITY;
let mut intersect_min_y = f32::NEG_INFINITY;
let mut intersect_max_x = f32::INFINITY;
let mut intersect_max_y = f32::INFINITY;
let mut has_rect_clips = false;
let mut corner_sources: [(f32, f32, f32, f32, f32); 4] = [
(
0.0,
f32::NEG_INFINITY,
f32::NEG_INFINITY,
f32::INFINITY,
f32::INFINITY,
), (
0.0,
f32::NEG_INFINITY,
f32::NEG_INFINITY,
f32::INFINITY,
f32::INFINITY,
), (
0.0,
f32::NEG_INFINITY,
f32::NEG_INFINITY,
f32::INFINITY,
f32::INFINITY,
), (
0.0,
f32::NEG_INFINITY,
f32::NEG_INFINITY,
f32::INFINITY,
f32::INFINITY,
), ];
let mut topmost_is_plain_rect = false;
for (clip, _poly_meta, _fade) in &self.clip_stack {
match clip {
ClipShape::Rect(rect) => {
intersect_min_x = intersect_min_x.max(rect.x());
intersect_min_y = intersect_min_y.max(rect.y());
intersect_max_x = intersect_max_x.min(rect.x() + rect.width());
intersect_max_y = intersect_max_y.min(rect.y() + rect.height());
has_rect_clips = true;
topmost_is_plain_rect = true;
}
ClipShape::RoundedRect {
rect,
corner_radius,
} => {
let rx = rect.x();
let ry = rect.y();
let rmax_x = rect.x() + rect.width();
let rmax_y = rect.y() + rect.height();
intersect_min_x = intersect_min_x.max(rx);
intersect_min_y = intersect_min_y.max(ry);
intersect_max_x = intersect_max_x.min(rmax_x);
intersect_max_y = intersect_max_y.min(rmax_y);
if corner_radius.top_left > corner_sources[0].0 {
corner_sources[0] = (corner_radius.top_left, rx, ry, rmax_x, rmax_y);
}
if corner_radius.top_right > corner_sources[1].0 {
corner_sources[1] = (corner_radius.top_right, rx, ry, rmax_x, rmax_y);
}
if corner_radius.bottom_right > corner_sources[2].0 {
corner_sources[2] = (corner_radius.bottom_right, rx, ry, rmax_x, rmax_y);
}
if corner_radius.bottom_left > corner_sources[3].0 {
corner_sources[3] = (corner_radius.bottom_left, rx, ry, rmax_x, rmax_y);
}
has_rect_clips = true;
topmost_is_plain_rect = false;
}
ClipShape::Circle { .. }
| ClipShape::Ellipse { .. }
| ClipShape::Path(_)
| ClipShape::Polygon(_) => {
topmost_is_plain_rect = false;
}
}
}
let topmost_is_non_rect = matches!(
self.clip_stack.last().map(|(c, _, _)| c),
Some(
ClipShape::Circle { .. }
| ClipShape::Ellipse { .. }
| ClipShape::Polygon(_)
| ClipShape::Path(_)
)
);
if has_rect_clips && !topmost_is_non_rect {
let width = (intersect_max_x - intersect_min_x).max(0.0);
let height = (intersect_max_y - intersect_min_y).max(0.0);
let mut radii = [0.0f32; 4];
let (r, src_min_x, src_min_y, _, _) = corner_sources[0];
if r > 0.0 {
let dist_from_left = intersect_min_x - src_min_x;
let dist_from_top = intersect_min_y - src_min_y;
if dist_from_left < r && dist_from_top < r {
radii[0] = (r - dist_from_left.max(0.0)).max(0.0).min(r);
}
}
let (r, _, src_min_y, src_max_x, _) = corner_sources[1];
if r > 0.0 {
let dist_from_right = src_max_x - intersect_max_x;
let dist_from_top = intersect_min_y - src_min_y;
if dist_from_right < r && dist_from_top < r {
radii[1] = (r - dist_from_right.max(0.0)).max(0.0).min(r);
}
}
let (r, _, _, src_max_x, src_max_y) = corner_sources[2];
if r > 0.0 {
let dist_from_right = src_max_x - intersect_max_x;
let dist_from_bottom = src_max_y - intersect_max_y;
if dist_from_right < r && dist_from_bottom < r {
radii[2] = (r - dist_from_right.max(0.0)).max(0.0).min(r);
}
}
let (r, src_min_x, _, _, src_max_y) = corner_sources[3];
if r > 0.0 {
let dist_from_left = intersect_min_x - src_min_x;
let dist_from_bottom = src_max_y - intersect_max_y;
if dist_from_left < r && dist_from_bottom < r {
radii[3] = (r - dist_from_left.max(0.0)).max(0.0).min(r);
}
}
return (
[intersect_min_x, intersect_min_y, width, height],
radii,
ClipType::Rect,
);
}
let scissor_bounds = if has_rect_clips {
let width = (intersect_max_x - intersect_min_x).max(0.0);
let height = (intersect_max_y - intersect_min_y).max(0.0);
[intersect_min_x, intersect_min_y, width, height]
} else {
[-10000.0, -10000.0, 100000.0, 100000.0]
};
let (clip, poly_meta, _fade) = self.clip_stack.last().unwrap();
match clip {
ClipShape::Rect(rect) => (
[rect.x(), rect.y(), rect.width(), rect.height()],
[0.0; 4],
ClipType::Rect,
),
ClipShape::RoundedRect {
rect,
corner_radius,
} => (
[rect.x(), rect.y(), rect.width(), rect.height()],
[
corner_radius.top_left,
corner_radius.top_right,
corner_radius.bottom_right,
corner_radius.bottom_left,
],
ClipType::Rect,
),
ClipShape::Circle { center, radius } => (
scissor_bounds,
[center.x, center.y, *radius, 0.0],
ClipType::Circle,
),
ClipShape::Ellipse { center, radii } => (
scissor_bounds,
[center.x, center.y, radii.x, radii.y],
ClipType::Ellipse,
),
ClipShape::Polygon(_) => {
let (aux_offset, vertex_count) = poly_meta.unwrap_or((0, 0));
(
scissor_bounds,
[0.0, 0.0, vertex_count as f32, aux_offset as f32],
ClipType::Polygon,
)
}
ClipShape::Path(_) => {
(
[-10000.0, -10000.0, 100000.0, 100000.0],
[0.0; 4],
ClipType::None,
)
}
}
}
pub fn take_batch(&mut self) -> PrimitiveBatch {
std::mem::take(&mut self.batch)
}
pub fn batch(&self) -> &PrimitiveBatch {
&self.batch
}
pub fn batch_mut(&mut self) -> &mut PrimitiveBatch {
&mut self.batch
}
pub fn clear(&mut self) {
self.batch.clear();
self.transform_stack = vec![Affine2D::IDENTITY];
self.opacity_stack = vec![1.0];
self.blend_mode_stack = vec![BlendMode::Normal];
self.clip_stack.clear();
self.layer_stack.clear();
self.is_3d = false;
self.camera = None;
}
fn apply_opacity_to_brush(brush: Brush, opacity: f32) -> Brush {
if opacity >= 1.0 {
return brush;
}
match brush {
Brush::Solid(color) => {
Brush::Solid(Color::rgba(color.r, color.g, color.b, color.a * opacity))
}
other => other,
}
}
pub fn resize(&mut self, width: f32, height: f32) {
self.viewport = Size::new(width, height);
}
pub fn execute_commands(&mut self, commands: &[DrawCommand]) {
for cmd in commands {
self.execute_command(cmd);
}
}
pub fn execute_command(&mut self, cmd: &DrawCommand) {
match cmd {
DrawCommand::PushTransform(t) => self.push_transform(t.clone()),
DrawCommand::PopTransform => self.pop_transform(),
DrawCommand::PushClip(shape) => self.push_clip(shape.clone()),
DrawCommand::PopClip => self.pop_clip(),
DrawCommand::PushOpacity(o) => self.push_opacity(*o),
DrawCommand::PopOpacity => self.pop_opacity(),
DrawCommand::PushBlendMode(m) => self.push_blend_mode(*m),
DrawCommand::PopBlendMode => self.pop_blend_mode(),
DrawCommand::FillPath { path, brush } => self.fill_path(path, brush.clone()),
DrawCommand::StrokePath {
path,
stroke,
brush,
} => self.stroke_path(path, stroke, brush.clone()),
DrawCommand::FillRect {
rect,
corner_radius,
brush,
} => self.fill_rect(*rect, *corner_radius, brush.clone()),
DrawCommand::StrokeRect {
rect,
corner_radius,
stroke,
brush,
} => self.stroke_rect(*rect, *corner_radius, stroke, brush.clone()),
DrawCommand::FillCircle {
center,
radius,
brush,
} => self.fill_circle(*center, *radius, brush.clone()),
DrawCommand::StrokeCircle {
center,
radius,
stroke,
brush,
} => self.stroke_circle(*center, *radius, stroke, brush.clone()),
DrawCommand::DrawText {
text,
origin,
style,
} => self.draw_text(text, *origin, style),
DrawCommand::DrawImage {
image,
rect,
options,
} => self.draw_image(*image, *rect, options),
DrawCommand::DrawShadow {
rect,
corner_radius,
shadow,
} => self.draw_shadow(*rect, *corner_radius, *shadow),
DrawCommand::DrawInnerShadow {
rect,
corner_radius,
shadow,
} => self.draw_inner_shadow(*rect, *corner_radius, *shadow),
DrawCommand::DrawCircleShadow {
center,
radius,
shadow,
} => self.draw_circle_shadow(*center, *radius, *shadow),
DrawCommand::DrawCircleInnerShadow {
center,
radius,
shadow,
} => self.draw_circle_inner_shadow(*center, *radius, *shadow),
DrawCommand::SetCamera(camera) => self.set_camera(camera),
DrawCommand::DrawMesh {
mesh,
material,
transform,
} => self.draw_mesh(*mesh, *material, *transform),
DrawCommand::DrawMeshInstanced { mesh, instances } => {
self.draw_mesh_instanced(*mesh, instances)
}
DrawCommand::AddLight(light) => self.add_light(light.clone()),
DrawCommand::SetEnvironment(env) => self.set_environment(env),
DrawCommand::PushLayer(config) => self.push_layer(config.clone()),
DrawCommand::PopLayer => self.pop_layer(),
DrawCommand::SampleLayer {
id,
source_rect,
dest_rect,
} => self.sample_layer(*id, *source_rect, *dest_rect),
}
}
}
impl<'a> DrawContext for GpuPaintContext<'a> {
fn push_transform(&mut self, transform: Transform) {
let current = self.current_affine();
let new_transform = match transform {
Transform::Affine2D(affine) => current.then(&affine),
Transform::Mat4(_) => {
current
}
};
self.transform_stack.push(new_transform);
}
fn pop_transform(&mut self) {
if self.transform_stack.len() > 1 {
self.transform_stack.pop();
}
}
fn current_transform(&self) -> Transform {
Transform::Affine2D(self.current_affine())
}
fn push_clip(&mut self, shape: ClipShape) {
let transformed_shape = self.transform_clip_shape(shape);
let poly_meta = if let ClipShape::Polygon(ref pts) = transformed_shape {
let aux_offset = self.batch.aux_data.len() as u32;
let vertex_count = pts.len() as u32;
let mut i = 0;
while i < pts.len() {
let x0 = pts[i].x;
let y0 = pts[i].y;
let (x1, y1) = if i + 1 < pts.len() {
(pts[i + 1].x, pts[i + 1].y)
} else {
(0.0, 0.0) };
self.batch.aux_data.push([x0, y0, x1, y1]);
i += 2;
}
Some((aux_offset, vertex_count))
} else {
None
};
let fade = std::mem::replace(&mut self.pending_overflow_fade, [0.0; 4]);
self.clip_stack.push((transformed_shape, poly_meta, fade));
}
fn pop_clip(&mut self) {
self.clip_stack.pop();
}
fn push_opacity(&mut self, opacity: f32) {
self.opacity_stack.push(opacity);
}
fn pop_opacity(&mut self) {
if self.opacity_stack.len() > 1 {
self.opacity_stack.pop();
}
}
fn push_blend_mode(&mut self, mode: BlendMode) {
self.blend_mode_stack.push(mode);
}
fn pop_blend_mode(&mut self) {
if self.blend_mode_stack.len() > 1 {
self.blend_mode_stack.pop();
}
}
fn set_foreground_layer(&mut self, is_foreground: bool) {
self.is_foreground = is_foreground;
}
fn set_z_layer(&mut self, layer: u32) {
self.z_layer = layer;
}
fn z_layer(&self) -> u32 {
self.z_layer
}
fn set_3d_transform(&mut self, rx_rad: f32, ry_rad: f32, perspective_d: f32) {
self.current_3d_sin_rx = rx_rad.sin();
self.current_3d_cos_rx = rx_rad.cos();
self.current_3d_sin_ry = ry_rad.sin();
self.current_3d_cos_ry = ry_rad.cos();
self.current_3d_perspective_d = perspective_d;
}
fn set_3d_shape(&mut self, shape_type: f32, depth: f32, ambient: f32, specular: f32) {
self.current_3d_shape_type = shape_type;
self.current_3d_depth = depth;
self.current_3d_ambient = ambient;
self.current_3d_specular = specular;
}
fn set_3d_light(&mut self, direction: [f32; 3], intensity: f32) {
self.current_3d_light = [direction[0], direction[1], direction[2], intensity];
}
fn set_3d_translate_z(&mut self, z: f32) {
self.current_3d_translate_z = z;
}
fn set_3d_group_raw(&mut self, shapes: &[[f32; 16]]) {
use crate::primitives::ShapeDesc;
self.current_3d_group_shapes = shapes
.iter()
.map(|arr| ShapeDesc {
offset: [arr[0], arr[1], arr[2], arr[3]],
params: [arr[4], arr[5], arr[6], arr[7]],
half_ext: [arr[8], arr[9], arr[10], arr[11]],
color: [arr[12], arr[13], arr[14], arr[15]],
})
.collect();
}
fn clear_3d(&mut self) {
self.current_3d_sin_ry = 0.0;
self.current_3d_cos_ry = 1.0;
self.current_3d_sin_rx = 0.0;
self.current_3d_cos_rx = 1.0;
self.current_3d_perspective_d = 0.0;
self.current_3d_shape_type = 0.0;
self.current_3d_depth = 0.0;
self.current_3d_ambient = 0.3;
self.current_3d_specular = 32.0;
self.current_3d_translate_z = 0.0;
self.current_3d_light = [0.0, -1.0, 0.5, 0.8];
self.current_3d_group_shapes.clear();
}
fn set_css_filter(
&mut self,
grayscale: f32,
invert: f32,
sepia: f32,
hue_rotate_deg: f32,
brightness: f32,
contrast: f32,
saturate: f32,
) {
self.current_filter_a = [grayscale, invert, sepia, hue_rotate_deg.to_radians()];
self.current_filter_b = [brightness, contrast, saturate, 0.0];
}
fn clear_css_filter(&mut self) {
self.current_filter_a = [0.0, 0.0, 0.0, 0.0];
self.current_filter_b = [1.0, 1.0, 1.0, 0.0];
}
fn set_mask_gradient(&mut self, params: [f32; 4], info: [f32; 4]) {
self.current_mask_params = params;
self.current_mask_info = info;
}
fn clear_mask_gradient(&mut self) {
self.current_mask_params = [0.0; 4];
self.current_mask_info = [0.0; 4];
}
fn set_corner_shape(&mut self, shape: [f32; 4]) {
self.current_corner_shape = shape;
}
fn clear_corner_shape(&mut self) {
self.current_corner_shape = [1.0; 4];
}
fn set_overflow_fade(&mut self, fade: [f32; 4]) {
self.pending_overflow_fade = fade;
}
fn clear_overflow_fade(&mut self) {
self.pending_overflow_fade = [0.0; 4];
}
fn fill_path(&mut self, path: &Path, brush: Brush) {
let opacity = self.combined_opacity();
let brush = Self::apply_opacity_to_brush(brush, opacity);
let brush_info = extract_brush_info(&brush);
let mut tessellated = tessellate_fill(path, &brush);
let affine = self.current_affine();
for vertex in &mut tessellated.vertices {
let x = vertex.position[0];
let y = vertex.position[1];
vertex.position[0] =
affine.elements[0] * x + affine.elements[2] * y + affine.elements[4];
vertex.position[1] =
affine.elements[1] * x + affine.elements[3] * y + affine.elements[5];
}
if !tessellated.is_empty() {
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
if self.is_foreground {
self.batch.push_foreground_path_with_brush_info(
tessellated,
clip_bounds,
clip_radius,
clip_type,
&brush_info,
);
} else {
self.batch.push_path_with_brush_info(
tessellated,
clip_bounds,
clip_radius,
clip_type,
&brush_info,
);
}
}
}
fn stroke_path(&mut self, path: &Path, stroke: &Stroke, brush: Brush) {
let opacity = self.combined_opacity();
let brush = Self::apply_opacity_to_brush(brush, opacity);
let brush_info = extract_brush_info(&brush);
let mut tessellated = tessellate_stroke(path, stroke, &brush);
let affine = self.current_affine();
for vertex in &mut tessellated.vertices {
let x = vertex.position[0];
let y = vertex.position[1];
vertex.position[0] =
affine.elements[0] * x + affine.elements[2] * y + affine.elements[4];
vertex.position[1] =
affine.elements[1] * x + affine.elements[3] * y + affine.elements[5];
}
if !tessellated.is_empty() {
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
if self.is_foreground {
self.batch.push_foreground_path_with_brush_info(
tessellated,
clip_bounds,
clip_radius,
clip_type,
&brush_info,
);
} else {
self.batch.push_path_with_brush_info(
tessellated,
clip_bounds,
clip_radius,
clip_type,
&brush_info,
);
}
}
}
fn fill_rect(&mut self, rect: Rect, corner_radius: CornerRadius, brush: Brush) {
let transformed = self.transform_rect(rect);
let scaled_radius = self.scale_corner_radius(corner_radius);
if let Brush::Glass(style) = &brush {
let mut glass = GpuGlassPrimitive::new(
transformed.x(),
transformed.y(),
transformed.width(),
transformed.height(),
)
.with_corner_radius_per_corner(
scaled_radius.top_left,
scaled_radius.top_right,
scaled_radius.bottom_right,
scaled_radius.bottom_left,
)
.with_blur(style.blur)
.with_tint(style.tint.r, style.tint.g, style.tint.b, style.tint.a)
.with_saturation(style.saturation)
.with_brightness(style.brightness)
.with_noise(style.noise)
.with_border_thickness(style.border_thickness);
if let Some(bc) = style.border_color {
glass = glass.with_border_color(bc.r, bc.g, bc.b, bc.a);
}
if let Some(ref shadow) = style.shadow {
let [a, b, c, d, ..] = self.current_affine().elements;
let scale = (a * d - b * c).abs().sqrt().max(1e-6);
glass = glass.with_shadow_offset(
shadow.blur * scale,
shadow.color.a, shadow.offset_x * scale,
shadow.offset_y * scale,
);
}
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
match clip_type {
ClipType::None => {}
ClipType::Rect => {
let has_radius = clip_radius.iter().any(|&r| r > 0.0);
if has_radius {
glass = glass.with_clip_rounded_rect_per_corner(
clip_bounds[0],
clip_bounds[1],
clip_bounds[2],
clip_bounds[3],
clip_radius[0],
clip_radius[1],
clip_radius[2],
clip_radius[3],
);
} else {
glass = glass.with_clip_rect(
clip_bounds[0],
clip_bounds[1],
clip_bounds[2],
clip_bounds[3],
);
}
}
ClipType::Circle | ClipType::Ellipse | ClipType::Polygon => {
glass = glass.with_clip_rect(
clip_bounds[0] - clip_bounds[2],
clip_bounds[1] - clip_bounds[3],
clip_bounds[2] * 2.0,
clip_bounds[3] * 2.0,
);
}
}
if style.simple {
glass = glass.with_glass_type(GlassType::Simple);
}
if style.depth > 0 {
self.batch.push_nested_glass(glass);
} else {
self.batch.push_glass(glass);
}
return;
}
if let Brush::Blur(style) = &brush {
let mut glass = GpuGlassPrimitive::new(
transformed.x(),
transformed.y(),
transformed.width(),
transformed.height(),
)
.with_corner_radius_per_corner(
scaled_radius.top_left,
scaled_radius.top_right,
scaled_radius.bottom_right,
scaled_radius.bottom_left,
)
.with_blur(style.radius)
.with_saturation(1.0) .with_brightness(1.0);
if let Some(ref tint) = style.tint {
glass = glass.with_tint(tint.r, tint.g, tint.b, tint.a * style.opacity);
} else {
glass = glass.with_tint(1.0, 1.0, 1.0, 0.1 * style.opacity);
}
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
match clip_type {
ClipType::None => {}
ClipType::Rect => {
let has_radius = clip_radius.iter().any(|&r| r > 0.0);
if has_radius {
glass = glass.with_clip_rounded_rect_per_corner(
clip_bounds[0],
clip_bounds[1],
clip_bounds[2],
clip_bounds[3],
clip_radius[0],
clip_radius[1],
clip_radius[2],
clip_radius[3],
);
} else {
glass = glass.with_clip_rect(
clip_bounds[0],
clip_bounds[1],
clip_bounds[2],
clip_bounds[3],
);
}
}
ClipType::Circle | ClipType::Ellipse | ClipType::Polygon => {
glass = glass.with_clip_rect(
clip_bounds[0] - clip_bounds[2],
clip_bounds[1] - clip_bounds[3],
clip_bounds[2] * 2.0,
clip_bounds[3] * 2.0,
);
}
}
self.batch.push_glass(glass);
return;
}
let (color, color2, gradient_params, fill_type) = self.brush_to_colors(&brush);
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
let gradient_params = Self::obb_to_rect_coords(&brush, gradient_params, rect, fill_type);
let is_radial = fill_type == FillType::RadialGradient;
let transformed_gradient_params = if fill_type != FillType::Solid {
self.transform_gradient_params(gradient_params, is_radial)
} else {
gradient_params
};
let mut border = [0.0_f32; 4];
if !self.current_3d_group_shapes.is_empty() {
let aux_offset = self.batch.aux_data.len() as f32;
let shape_count = self.current_3d_group_shapes.len() as f32;
let mut max_depth: f32 = 1.0;
for shape in &self.current_3d_group_shapes {
max_depth = max_depth.max(shape.params[1]); }
for shape in &self.current_3d_group_shapes {
self.batch.aux_data.push(shape.offset);
self.batch.aux_data.push(shape.params);
self.batch.aux_data.push(shape.half_ext);
self.batch.aux_data.push(shape.color);
}
border = [0.0, shape_count, aux_offset, max_depth];
}
let primitive = GpuPrimitive {
bounds: [
transformed.x(),
transformed.y(),
transformed.width(),
transformed.height(),
],
corner_radius: [
scaled_radius.top_left,
scaled_radius.top_right,
scaled_radius.bottom_right,
scaled_radius.bottom_left,
],
color,
color2,
border,
border_color: [0.0; 4],
shadow: [0.0; 4],
shadow_color: [0.0; 4],
clip_bounds,
clip_radius,
gradient_params: transformed_gradient_params,
rotation: self.current_rotation_sincos(),
local_affine: self.current_local_affine(),
perspective: self.current_perspective_params(),
sdf_3d: self.current_sdf_3d_params(),
light: self.current_light_params(),
filter_a: self.current_filter_a,
filter_b: self.current_filter_b,
mask_params: self.current_mask_params,
mask_info: self.current_mask_info,
corner_shape: self.current_corner_shape,
clip_fade: self.get_clip_fade(),
type_info: [
PrimitiveType::Rect as u32,
fill_type as u32,
clip_type as u32,
self.z_layer,
],
};
if self.is_foreground {
self.batch.push_foreground(primitive);
} else {
self.batch.push(primitive);
}
}
fn fill_rect_with_per_side_border(
&mut self,
rect: Rect,
corner_radius: CornerRadius,
brush: Brush,
border_widths: [f32; 4],
border_color: Color,
) {
let transformed = self.transform_rect(rect);
let scaled_radius = self.scale_corner_radius(corner_radius);
let (color, color2, gradient_params, fill_type) = self.brush_to_colors(&brush);
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
let affine = self.current_affine();
let a = affine.elements[0];
let b = affine.elements[1];
let c = affine.elements[2];
let d = affine.elements[3];
let scale_x = (a * a + b * b).sqrt();
let scale_y = (c * c + d * d).sqrt();
let scaled_borders = [
border_widths[0] * scale_y, border_widths[1] * scale_x, border_widths[2] * scale_y, border_widths[3] * scale_x, ];
let gradient_params = Self::obb_to_rect_coords(&brush, gradient_params, rect, fill_type);
let is_radial = fill_type == FillType::RadialGradient;
let transformed_gradient_params = if fill_type != FillType::Solid {
self.transform_gradient_params(gradient_params, is_radial)
} else {
gradient_params
};
let opacity = self.combined_opacity();
let primitive = GpuPrimitive {
bounds: [
transformed.x(),
transformed.y(),
transformed.width(),
transformed.height(),
],
corner_radius: [
scaled_radius.top_left,
scaled_radius.top_right,
scaled_radius.bottom_right,
scaled_radius.bottom_left,
],
color,
color2,
border: scaled_borders,
border_color: [
border_color.r,
border_color.g,
border_color.b,
border_color.a * opacity,
],
shadow: [0.0; 4],
shadow_color: [0.0; 4],
clip_bounds,
clip_radius,
gradient_params: transformed_gradient_params,
rotation: self.current_rotation_sincos(),
local_affine: self.current_local_affine(),
perspective: self.current_perspective_params(),
sdf_3d: self.current_sdf_3d_params(),
light: self.current_light_params(),
filter_a: self.current_filter_a,
filter_b: self.current_filter_b,
mask_params: self.current_mask_params,
mask_info: self.current_mask_info,
corner_shape: self.current_corner_shape,
clip_fade: self.get_clip_fade(),
type_info: [
PrimitiveType::Rect as u32,
fill_type as u32,
clip_type as u32,
self.z_layer,
],
};
if self.is_foreground {
self.batch.push_foreground(primitive);
} else {
self.batch.push(primitive);
}
}
fn stroke_rect(
&mut self,
rect: Rect,
corner_radius: CornerRadius,
stroke: &Stroke,
brush: Brush,
) {
let transformed = self.transform_rect(rect);
let scaled_radius = self.scale_corner_radius(corner_radius);
let (color, _color2, gradient_params, fill_type) = self.brush_to_colors(&brush);
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
let scaled_border_width = stroke.width * self.current_uniform_scale();
let primitive = GpuPrimitive {
bounds: [
transformed.x(),
transformed.y(),
transformed.width(),
transformed.height(),
],
corner_radius: [
scaled_radius.top_left,
scaled_radius.top_right,
scaled_radius.bottom_right,
scaled_radius.bottom_left,
],
color: [0.0, 0.0, 0.0, 0.0], color2: [0.0, 0.0, 0.0, 0.0],
border: [scaled_border_width, 0.0, 0.0, 0.0],
border_color: color,
shadow: [0.0; 4],
shadow_color: [0.0; 4],
clip_bounds,
clip_radius,
gradient_params,
rotation: self.current_rotation_sincos(),
local_affine: self.current_local_affine(),
perspective: self.current_perspective_params(),
sdf_3d: self.current_sdf_3d_params(),
light: self.current_light_params(),
filter_a: self.current_filter_a,
filter_b: self.current_filter_b,
mask_params: self.current_mask_params,
mask_info: self.current_mask_info,
corner_shape: self.current_corner_shape,
clip_fade: self.get_clip_fade(),
type_info: [
PrimitiveType::Rect as u32,
fill_type as u32,
clip_type as u32,
self.z_layer,
],
};
if self.is_foreground {
self.batch.push_foreground(primitive);
} else {
self.batch.push(primitive);
}
}
fn fill_circle(&mut self, center: Point, radius: f32, brush: Brush) {
let transformed_center = self.transform_point(center);
let affine = self.current_affine();
let a = affine.elements[0];
let b = affine.elements[1];
let c = affine.elements[2];
let d = affine.elements[3];
let scale = ((a * a + b * b).sqrt() + (c * c + d * d).sqrt()) / 2.0;
let transformed_radius = radius * scale;
if let Brush::Glass(style) = &brush {
let mut glass = GpuGlassPrimitive::circle(
transformed_center.x,
transformed_center.y,
transformed_radius,
)
.with_blur(style.blur)
.with_tint(style.tint.r, style.tint.g, style.tint.b, style.tint.a)
.with_saturation(style.saturation)
.with_brightness(style.brightness)
.with_noise(style.noise)
.with_border_thickness(style.border_thickness);
if let Some(bc) = style.border_color {
glass = glass.with_border_color(bc.r, bc.g, bc.b, bc.a);
}
if style.depth > 0 {
self.batch.push_nested_glass(glass);
} else {
self.batch.push_glass(glass);
}
return;
}
let (color, color2, gradient_params, fill_type) = self.brush_to_colors(&brush);
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
let circle_rect = Rect::new(
center.x - radius,
center.y - radius,
radius * 2.0,
radius * 2.0,
);
let gradient_params =
Self::obb_to_rect_coords(&brush, gradient_params, circle_rect, fill_type);
let is_radial = fill_type == FillType::RadialGradient;
let transformed_gradient_params = if fill_type != FillType::Solid {
self.transform_gradient_params(gradient_params, is_radial)
} else {
gradient_params
};
let primitive = GpuPrimitive {
bounds: [
transformed_center.x - transformed_radius,
transformed_center.y - transformed_radius,
transformed_radius * 2.0,
transformed_radius * 2.0,
],
corner_radius: [0.0; 4], color,
color2,
border: [0.0; 4],
border_color: [0.0; 4],
shadow: [0.0; 4],
shadow_color: [0.0; 4],
clip_bounds,
clip_radius,
gradient_params: transformed_gradient_params,
rotation: [0.0, 1.0, 0.0, 1.0],
local_affine: [1.0, 0.0, 0.0, 1.0],
perspective: self.current_perspective_params(),
sdf_3d: self.current_sdf_3d_params(),
light: self.current_light_params(),
filter_a: self.current_filter_a,
filter_b: self.current_filter_b,
mask_params: self.current_mask_params,
mask_info: self.current_mask_info,
corner_shape: self.current_corner_shape,
clip_fade: self.get_clip_fade(),
type_info: [
PrimitiveType::Circle as u32,
fill_type as u32,
clip_type as u32,
self.z_layer,
],
};
if self.is_foreground {
self.batch.push_foreground(primitive);
} else {
self.batch.push(primitive);
}
}
fn stroke_circle(&mut self, center: Point, radius: f32, stroke: &Stroke, brush: Brush) {
let transformed_center = self.transform_point(center);
let affine = self.current_affine();
let a = affine.elements[0];
let b = affine.elements[1];
let c = affine.elements[2];
let d = affine.elements[3];
let scale = ((a * a + b * b).sqrt() + (c * c + d * d).sqrt()) / 2.0;
let transformed_radius = radius * scale;
let (color, _, gradient_params, fill_type) = self.brush_to_colors(&brush);
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
let circle_rect = Rect::new(
center.x - radius,
center.y - radius,
radius * 2.0,
radius * 2.0,
);
let gradient_params =
Self::obb_to_rect_coords(&brush, gradient_params, circle_rect, fill_type);
let is_radial = fill_type == FillType::RadialGradient;
let transformed_gradient_params = if fill_type != FillType::Solid {
self.transform_gradient_params(gradient_params, is_radial)
} else {
gradient_params
};
let primitive = GpuPrimitive {
bounds: [
transformed_center.x - transformed_radius,
transformed_center.y - transformed_radius,
transformed_radius * 2.0,
transformed_radius * 2.0,
],
corner_radius: [0.0; 4],
color: [0.0, 0.0, 0.0, 0.0], color2: [0.0, 0.0, 0.0, 0.0],
border: [stroke.width * scale, 0.0, 0.0, 0.0],
border_color: color,
shadow: [0.0; 4],
shadow_color: [0.0; 4],
clip_bounds,
clip_radius,
gradient_params: transformed_gradient_params,
rotation: [0.0, 1.0, 0.0, 1.0],
local_affine: [1.0, 0.0, 0.0, 1.0],
perspective: self.current_perspective_params(),
sdf_3d: self.current_sdf_3d_params(),
light: self.current_light_params(),
filter_a: self.current_filter_a,
filter_b: self.current_filter_b,
mask_params: self.current_mask_params,
mask_info: self.current_mask_info,
corner_shape: self.current_corner_shape,
clip_fade: self.get_clip_fade(),
type_info: [
PrimitiveType::Circle as u32,
fill_type as u32,
clip_type as u32,
self.z_layer,
],
};
if self.is_foreground {
self.batch.push_foreground(primitive);
} else {
self.batch.push(primitive);
}
}
fn draw_text(&mut self, text: &str, origin: Point, style: &TextStyle) {
use blinc_core::{TextAlign, TextBaseline};
use blinc_text::{TextAlignment, TextAnchor};
if self.text_ctx.is_none() {
return;
}
let transformed_origin = self.transform_point(origin);
let opacity = self.combined_opacity();
let (clip_bounds, _, _) = self.get_clip_data();
let color = [
style.color.r,
style.color.g,
style.color.b,
style.color.a * opacity,
];
let alignment = match style.align {
TextAlign::Left => TextAlignment::Left,
TextAlign::Center => TextAlignment::Center,
TextAlign::Right => TextAlignment::Right,
};
let anchor = match style.baseline {
TextBaseline::Top => TextAnchor::Top,
TextBaseline::Middle => TextAnchor::Center,
TextBaseline::Alphabetic => TextAnchor::Baseline,
TextBaseline::Bottom => TextAnchor::Baseline, };
let text_ctx = self.text_ctx.as_mut().unwrap();
if let Ok(mut glyphs) = text_ctx.prepare_text_with_options(
text,
transformed_origin.x,
transformed_origin.y,
style.size,
color,
anchor,
alignment,
None, false, ) {
let glyph_clip_fade = self.get_clip_fade();
for glyph in &mut glyphs {
glyph.clip_bounds = clip_bounds;
glyph.clip_fade = glyph_clip_fade;
}
for glyph in glyphs {
self.batch.push_glyph(glyph);
}
}
}
fn draw_image(&mut self, _image: ImageId, _rect: Rect, _options: &ImageOptions) {
}
fn draw_rgba_pixels(&mut self, data: &[u8], width: u32, height: u32, dest: Rect) {
let transformed = self.transform_rect(dest);
let opacity = self.current_opacity();
self.batch
.dynamic_images
.push(crate::primitives::DynamicImage {
data: data.to_vec(),
width,
height,
dest: transformed,
opacity,
corner_radius: 0.0,
});
}
fn draw_shadow(&mut self, rect: Rect, corner_radius: CornerRadius, shadow: Shadow) {
let transformed = self.transform_rect(rect);
let scaled_radius = self.scale_corner_radius(corner_radius);
let opacity = self.combined_opacity();
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
let s = self.current_uniform_scale();
let primitive = GpuPrimitive {
bounds: [
transformed.x(),
transformed.y(),
transformed.width(),
transformed.height(),
],
corner_radius: [
scaled_radius.top_left,
scaled_radius.top_right,
scaled_radius.bottom_right,
scaled_radius.bottom_left,
],
color: [0.0, 0.0, 0.0, 0.0], color2: [0.0, 0.0, 0.0, 0.0],
border: [0.0; 4],
border_color: [0.0; 4],
shadow: [
shadow.offset_x * s,
shadow.offset_y * s,
shadow.blur * s,
shadow.spread * s,
],
shadow_color: [
shadow.color.r,
shadow.color.g,
shadow.color.b,
shadow.color.a * opacity,
],
clip_bounds,
clip_radius,
gradient_params: [0.0, 0.0, 1.0, 0.0],
rotation: self.current_rotation_sincos(),
local_affine: self.current_local_affine(),
perspective: self.current_perspective_params(),
sdf_3d: self.current_sdf_3d_params(),
light: self.current_light_params(),
filter_a: self.current_filter_a,
filter_b: self.current_filter_b,
mask_params: self.current_mask_params,
mask_info: self.current_mask_info,
corner_shape: self.current_corner_shape,
clip_fade: self.get_clip_fade(),
type_info: [
PrimitiveType::Shadow as u32,
FillType::Solid as u32,
clip_type as u32,
self.z_layer,
],
};
if self.is_foreground {
self.batch.push_foreground(primitive);
} else {
self.batch.push(primitive);
}
}
fn draw_inner_shadow(&mut self, rect: Rect, corner_radius: CornerRadius, shadow: Shadow) {
let transformed = self.transform_rect(rect);
let scaled_radius = self.scale_corner_radius(corner_radius);
let opacity = self.combined_opacity();
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
let s = self.current_uniform_scale();
let primitive = GpuPrimitive {
bounds: [
transformed.x(),
transformed.y(),
transformed.width(),
transformed.height(),
],
corner_radius: [
scaled_radius.top_left,
scaled_radius.top_right,
scaled_radius.bottom_right,
scaled_radius.bottom_left,
],
color: [0.0, 0.0, 0.0, 0.0], color2: [0.0, 0.0, 0.0, 0.0],
border: [0.0; 4],
border_color: [0.0; 4],
shadow: [
shadow.offset_x * s,
shadow.offset_y * s,
shadow.blur * s,
shadow.spread * s,
],
shadow_color: [
shadow.color.r,
shadow.color.g,
shadow.color.b,
shadow.color.a * opacity,
],
clip_bounds,
clip_radius,
gradient_params: [0.0, 0.0, 1.0, 0.0],
rotation: self.current_rotation_sincos(),
local_affine: self.current_local_affine(),
perspective: self.current_perspective_params(),
sdf_3d: self.current_sdf_3d_params(),
light: self.current_light_params(),
filter_a: self.current_filter_a,
filter_b: self.current_filter_b,
mask_params: self.current_mask_params,
mask_info: self.current_mask_info,
corner_shape: self.current_corner_shape,
clip_fade: self.get_clip_fade(),
type_info: [
PrimitiveType::InnerShadow as u32,
FillType::Solid as u32,
clip_type as u32,
self.z_layer,
],
};
if self.is_foreground {
self.batch.push_foreground(primitive);
} else {
self.batch.push(primitive);
}
}
fn draw_circle_shadow(&mut self, center: Point, radius: f32, shadow: Shadow) {
let transformed_center = self.transform_point(center);
let opacity = self.combined_opacity();
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
let size = radius * 2.0;
let primitive = GpuPrimitive {
bounds: [
transformed_center.x - radius,
transformed_center.y - radius,
size,
size,
],
corner_radius: [radius, radius, radius, radius], color: [0.0, 0.0, 0.0, 0.0],
color2: [0.0, 0.0, 0.0, 0.0],
border: [0.0; 4],
border_color: [0.0; 4],
shadow: [shadow.offset_x, shadow.offset_y, shadow.blur, shadow.spread],
shadow_color: [
shadow.color.r,
shadow.color.g,
shadow.color.b,
shadow.color.a * opacity,
],
clip_bounds,
clip_radius,
gradient_params: [0.0, 0.0, 1.0, 0.0],
rotation: [0.0, 1.0, 0.0, 1.0],
local_affine: [1.0, 0.0, 0.0, 1.0],
perspective: self.current_perspective_params(),
sdf_3d: self.current_sdf_3d_params(),
light: self.current_light_params(),
filter_a: self.current_filter_a,
filter_b: self.current_filter_b,
mask_params: self.current_mask_params,
mask_info: self.current_mask_info,
corner_shape: self.current_corner_shape,
clip_fade: self.get_clip_fade(),
type_info: [
PrimitiveType::CircleShadow as u32,
FillType::Solid as u32,
clip_type as u32,
self.z_layer,
],
};
if self.is_foreground {
self.batch.push_foreground(primitive);
} else {
self.batch.push(primitive);
}
}
fn draw_circle_inner_shadow(&mut self, center: Point, radius: f32, shadow: Shadow) {
let transformed_center = self.transform_point(center);
let opacity = self.combined_opacity();
let (clip_bounds, clip_radius, clip_type) = self.get_clip_data();
let size = radius * 2.0;
let primitive = GpuPrimitive {
bounds: [
transformed_center.x - radius,
transformed_center.y - radius,
size,
size,
],
corner_radius: [radius, radius, radius, radius],
color: [0.0, 0.0, 0.0, 0.0],
color2: [0.0, 0.0, 0.0, 0.0],
border: [0.0; 4],
border_color: [0.0; 4],
shadow: [shadow.offset_x, shadow.offset_y, shadow.blur, shadow.spread],
shadow_color: [
shadow.color.r,
shadow.color.g,
shadow.color.b,
shadow.color.a * opacity,
],
clip_bounds,
clip_radius,
gradient_params: [0.0, 0.0, 1.0, 0.0],
rotation: [0.0, 1.0, 0.0, 1.0],
local_affine: [1.0, 0.0, 0.0, 1.0],
perspective: self.current_perspective_params(),
sdf_3d: self.current_sdf_3d_params(),
light: self.current_light_params(),
filter_a: self.current_filter_a,
filter_b: self.current_filter_b,
mask_params: self.current_mask_params,
mask_info: self.current_mask_info,
corner_shape: self.current_corner_shape,
clip_fade: self.get_clip_fade(),
type_info: [
PrimitiveType::CircleInnerShadow as u32,
FillType::Solid as u32,
clip_type as u32,
self.z_layer,
],
};
if self.is_foreground {
self.batch.push_foreground(primitive);
} else {
self.batch.push(primitive);
}
}
fn sdf_build(&mut self, f: &mut dyn FnMut(&mut dyn SdfBuilder)) {
let mut builder = GpuSdfBuilder::new(self);
f(&mut builder);
}
fn set_camera(&mut self, camera: &Camera) {
self.camera = Some(camera.clone());
self.is_3d = true;
}
fn draw_mesh(&mut self, _mesh: MeshId, _material: MaterialId, _transform: Mat4) {
}
fn draw_mesh_instanced(&mut self, _mesh: MeshId, _instances: &[MeshInstance]) {
}
fn add_light(&mut self, light: Light) {
self.lights.push(light);
}
fn set_environment(&mut self, _env: &Environment) {
}
fn billboard_draw(
&mut self,
_size: Size,
_transform: Mat4,
_facing: BillboardFacing,
f: &mut dyn FnMut(&mut dyn DrawContext),
) {
f(self);
}
fn viewport_3d_draw(
&mut self,
_rect: Rect,
camera: &Camera,
f: &mut dyn FnMut(&mut dyn DrawContext),
) {
let was_3d = self.is_3d;
let old_camera = self.camera.take();
self.set_camera(camera);
f(self);
self.is_3d = was_3d;
self.camera = old_camera;
}
fn draw_sdf_viewport(&mut self, rect: Rect, viewport: &Sdf3DViewport) {
let transformed = self.transform_rect(rect);
let (clip_bounds, _, _) = self.get_clip_data();
let clip_min_x = clip_bounds[0];
let clip_min_y = clip_bounds[1];
let clip_max_x = clip_bounds[0] + clip_bounds[2];
let clip_max_y = clip_bounds[1] + clip_bounds[3];
let orig_x = transformed.x();
let orig_y = transformed.y();
let orig_w = transformed.width();
let orig_h = transformed.height();
let clipped_x = orig_x.max(clip_min_x);
let clipped_y = orig_y.max(clip_min_y);
let clipped_right = (orig_x + orig_w).min(clip_max_x);
let clipped_bottom = (orig_y + orig_h).min(clip_max_y);
let clipped_w = (clipped_right - clipped_x).max(0.0);
let clipped_h = (clipped_bottom - clipped_y).max(0.0);
if clipped_w <= 0.0 || clipped_h <= 0.0 {
return;
}
let uv_offset_x = if orig_w > 0.0 {
(clipped_x - orig_x) / orig_w
} else {
0.0
};
let uv_offset_y = if orig_h > 0.0 {
(clipped_y - orig_y) / orig_h
} else {
0.0
};
let uv_scale_x = if orig_w > 0.0 {
clipped_w / orig_w
} else {
1.0
};
let uv_scale_y = if orig_h > 0.0 {
clipped_h / orig_h
} else {
1.0
};
let uniforms = Sdf3DUniform {
camera_pos: [
viewport.camera_pos.x,
viewport.camera_pos.y,
viewport.camera_pos.z,
1.0,
],
camera_dir: [
viewport.camera_dir.x,
viewport.camera_dir.y,
viewport.camera_dir.z,
0.0,
],
camera_up: [
viewport.camera_up.x,
viewport.camera_up.y,
viewport.camera_up.z,
0.0,
],
camera_right: [
viewport.camera_right.x,
viewport.camera_right.y,
viewport.camera_right.z,
0.0,
],
resolution: [orig_w, orig_h],
time: viewport.time,
fov: viewport.fov,
max_steps: viewport.max_steps,
max_distance: viewport.max_distance,
epsilon: viewport.epsilon,
_padding: 0.0,
uv_offset: [uv_offset_x, uv_offset_y],
uv_scale: [uv_scale_x, uv_scale_y],
};
let viewport_3d = Viewport3D {
shader_wgsl: viewport.shader_wgsl.clone(),
uniforms,
bounds: [clipped_x, clipped_y, clipped_w, clipped_h],
lights: viewport.lights.clone(),
};
self.batch.push_viewport_3d(viewport_3d);
}
fn draw_particles(&mut self, rect: Rect, particle_data: &ParticleSystemData) {
use crate::particles::{GpuEmitter, GpuForce};
use crate::primitives::ParticleViewport3D;
let transformed = self.transform_rect(rect);
let (clip_bounds, _, _) = self.get_clip_data();
let clip_min_x = clip_bounds[0];
let clip_min_y = clip_bounds[1];
let clip_max_x = clip_bounds[0] + clip_bounds[2];
let clip_max_y = clip_bounds[1] + clip_bounds[3];
let orig_x = transformed.x();
let orig_y = transformed.y();
let orig_w = transformed.width();
let orig_h = transformed.height();
let clipped_x = orig_x.max(clip_min_x);
let clipped_y = orig_y.max(clip_min_y);
let clipped_right = (orig_x + orig_w).min(clip_max_x);
let clipped_bottom = (orig_y + orig_h).min(clip_max_y);
let clipped_w = (clipped_right - clipped_x).max(0.0);
let clipped_h = (clipped_bottom - clipped_y).max(0.0);
if clipped_w <= 0.0 || clipped_h <= 0.0 {
return;
}
if !particle_data.playing {
return;
}
let (shape_type, shape_params) = match &particle_data.emitter {
ParticleEmitterShape::Point => (0u32, [0.0f32; 4]),
ParticleEmitterShape::Sphere { radius } => (1u32, [*radius, 0.0, 0.0, 0.0]),
ParticleEmitterShape::Hemisphere { radius } => (2u32, [*radius, 0.0, 0.0, 0.0]),
ParticleEmitterShape::Cone { angle, radius } => (3u32, [*angle, *radius, 0.0, 0.0]),
ParticleEmitterShape::Box { half_extents } => {
(4u32, [half_extents.x, half_extents.y, half_extents.z, 0.0])
}
ParticleEmitterShape::Circle { radius } => (5u32, [*radius, 0.0, 0.0, 0.0]),
};
let emitter = GpuEmitter {
position_shape: [
particle_data.emitter_position.x,
particle_data.emitter_position.y,
particle_data.emitter_position.z,
shape_type as f32,
],
shape_params,
direction_randomness: [
particle_data.direction.x,
particle_data.direction.y,
particle_data.direction.z,
particle_data.direction_randomness,
],
emission_config: [
particle_data.emission_rate,
particle_data.burst_count, 0.0, particle_data.gravity_scale,
],
lifetime_speed: [
particle_data.lifetime.0,
particle_data.lifetime.1,
particle_data.start_speed.0,
particle_data.start_speed.1,
],
size_config: [
particle_data.start_size.0,
particle_data.start_size.1,
particle_data.end_size.0,
particle_data.end_size.1,
],
start_color: [
particle_data.start_color.r,
particle_data.start_color.g,
particle_data.start_color.b,
particle_data.start_color.a,
],
mid_color: [
particle_data.mid_color.r,
particle_data.mid_color.g,
particle_data.mid_color.b,
particle_data.mid_color.a,
],
end_color: [
particle_data.end_color.r,
particle_data.end_color.g,
particle_data.end_color.b,
particle_data.end_color.a,
],
};
let forces: Vec<GpuForce> = particle_data
.forces
.iter()
.map(|force| match force {
ParticleForce::Gravity(dir) => GpuForce {
type_strength: [0.0, 1.0, 0.0, 0.0],
direction_params: [dir.x, dir.y, dir.z, 0.0],
},
ParticleForce::Wind {
direction,
strength,
turbulence,
} => GpuForce {
type_strength: [1.0, *strength, 0.0, 0.0],
direction_params: [direction.x, direction.y, direction.z, *turbulence],
},
ParticleForce::Vortex {
axis,
center: _,
strength,
} => GpuForce {
type_strength: [2.0, *strength, 0.0, 0.0],
direction_params: [axis.x, axis.y, axis.z, 0.0],
},
ParticleForce::Drag(coefficient) => GpuForce {
type_strength: [3.0, *coefficient, 0.0, 0.0],
direction_params: [0.0, 0.0, 0.0, 0.0],
},
ParticleForce::Turbulence {
strength,
frequency,
} => GpuForce {
type_strength: [4.0, *strength, 0.0, 0.0],
direction_params: [0.0, 0.0, 0.0, *frequency],
},
ParticleForce::Attractor { position, strength } => GpuForce {
type_strength: [5.0, *strength, 0.0, 0.0],
direction_params: [position.x, position.y, position.z, 0.0],
},
})
.collect();
let blend_mode = match particle_data.blend_mode {
ParticleBlendMode::Alpha => 0,
ParticleBlendMode::Additive => 1,
ParticleBlendMode::Multiply => 2,
};
let viewport = ParticleViewport3D {
emitter,
forces,
max_particles: particle_data.max_particles,
bounds: [clipped_x, clipped_y, clipped_w, clipped_h],
camera_pos: [
particle_data.camera_pos.x,
particle_data.camera_pos.y,
particle_data.camera_pos.z,
],
camera_target: [
particle_data.camera_pos.x + particle_data.camera_dir.x,
particle_data.camera_pos.y + particle_data.camera_dir.y,
particle_data.camera_pos.z + particle_data.camera_dir.z,
],
camera_up: [
particle_data.camera_up.x,
particle_data.camera_up.y,
particle_data.camera_up.z,
],
fov: 0.8, time: particle_data.time,
delta_time: particle_data.delta_time,
blend_mode,
playing: particle_data.playing,
};
self.batch.push_particle_viewport(viewport);
}
fn push_layer(&mut self, config: LayerConfig) {
let state = LayerState {
config: config.clone(),
primitive_start: self.batch.primitive_count(),
foreground_primitive_start: self.batch.foreground_primitive_count(),
path_start: self.batch.path_vertex_count(),
foreground_path_start: self.batch.foreground_path_vertex_count(),
parent_state_indices: (
self.transform_stack.len(),
self.opacity_stack.len(),
self.blend_mode_stack.len(),
self.clip_stack.len(),
),
};
self.layer_stack.push(state);
if config.blend_mode != BlendMode::Normal {
self.blend_mode_stack.push(config.blend_mode);
}
if config.opacity < 1.0 {
self.opacity_stack.push(config.opacity);
}
self.batch
.push_layer_command(crate::primitives::LayerCommand::Push {
config: config.clone(),
});
}
fn pop_layer(&mut self) {
if let Some(state) = self.layer_stack.pop() {
let (transform_idx, opacity_idx, blend_idx, clip_idx) = state.parent_state_indices;
if self.transform_stack.len() > transform_idx {
self.transform_stack.truncate(transform_idx.max(1));
}
if self.opacity_stack.len() > opacity_idx {
self.opacity_stack.truncate(opacity_idx.max(1));
}
if self.blend_mode_stack.len() > blend_idx {
self.blend_mode_stack.truncate(blend_idx.max(1));
}
if self.clip_stack.len() > clip_idx {
self.clip_stack.truncate(clip_idx);
}
self.batch
.push_layer_command(crate::primitives::LayerCommand::Pop);
}
}
fn sample_layer(&mut self, id: LayerId, source_rect: Rect, dest_rect: Rect) {
self.batch
.push_layer_command(crate::primitives::LayerCommand::Sample {
id,
source: source_rect,
dest: dest_rect,
});
}
fn viewport_size(&self) -> Size {
self.viewport
}
fn is_3d_context(&self) -> bool {
self.is_3d
}
fn current_opacity(&self) -> f32 {
self.combined_opacity()
}
fn current_blend_mode(&self) -> BlendMode {
self.blend_mode_stack
.last()
.copied()
.unwrap_or(BlendMode::Normal)
}
}
struct GpuSdfBuilder<'a, 'b> {
ctx: &'a mut GpuPaintContext<'b>,
shapes: Vec<SdfShapeData>,
}
#[derive(Clone, Debug)]
enum SdfShapeData {
Rect {
rect: Rect,
corner_radius: CornerRadius,
},
Circle {
center: Point,
radius: f32,
},
Ellipse {
center: Point,
radii: (f32, f32),
},
}
impl<'a, 'b> GpuSdfBuilder<'a, 'b> {
fn new(ctx: &'a mut GpuPaintContext<'b>) -> Self {
Self {
ctx,
shapes: Vec::new(),
}
}
fn add_shape(&mut self, shape: SdfShapeData) -> ShapeId {
let id = ShapeId(self.shapes.len() as u32);
self.shapes.push(shape);
id
}
}
impl<'a, 'b> SdfBuilder for GpuSdfBuilder<'a, 'b> {
fn rect(&mut self, rect: Rect, corner_radius: CornerRadius) -> ShapeId {
self.add_shape(SdfShapeData::Rect {
rect,
corner_radius,
})
}
fn circle(&mut self, center: Point, radius: f32) -> ShapeId {
self.add_shape(SdfShapeData::Circle { center, radius })
}
fn ellipse(&mut self, center: Point, radii: blinc_core::Vec2) -> ShapeId {
self.add_shape(SdfShapeData::Ellipse {
center,
radii: (radii.x, radii.y),
})
}
fn line(&mut self, _from: Point, _to: Point, _width: f32) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn arc(
&mut self,
_center: Point,
_radius: f32,
_start: f32,
_end: f32,
_width: f32,
) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn quad_bezier(&mut self, _p0: Point, _p1: Point, _p2: Point, _width: f32) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn union(&mut self, _a: ShapeId, _b: ShapeId) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn subtract(&mut self, _a: ShapeId, _b: ShapeId) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn intersect(&mut self, _a: ShapeId, _b: ShapeId) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn smooth_union(&mut self, _a: ShapeId, _b: ShapeId, _radius: f32) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn smooth_subtract(&mut self, _a: ShapeId, _b: ShapeId, _radius: f32) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn smooth_intersect(&mut self, _a: ShapeId, _b: ShapeId, _radius: f32) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn round(&mut self, _shape: ShapeId, _radius: f32) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn outline(&mut self, _shape: ShapeId, _width: f32) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn offset(&mut self, _shape: ShapeId, _distance: f32) -> ShapeId {
ShapeId(self.shapes.len() as u32)
}
fn fill(&mut self, shape: ShapeId, brush: Brush) {
if let Some(shape_data) = self.shapes.get(shape.0 as usize) {
match shape_data.clone() {
SdfShapeData::Rect {
rect,
corner_radius,
} => {
self.ctx.fill_rect(rect, corner_radius, brush);
}
SdfShapeData::Circle { center, radius } => {
self.ctx.fill_circle(center, radius, brush);
}
SdfShapeData::Ellipse { center, radii } => {
let radius = radii.0.max(radii.1);
self.ctx.fill_circle(center, radius, brush);
}
}
}
}
fn stroke(&mut self, shape: ShapeId, stroke: &Stroke, brush: Brush) {
if let Some(shape_data) = self.shapes.get(shape.0 as usize) {
match shape_data.clone() {
SdfShapeData::Rect {
rect,
corner_radius,
} => {
self.ctx.stroke_rect(rect, corner_radius, stroke, brush);
}
SdfShapeData::Circle { center, radius } => {
self.ctx.stroke_circle(center, radius, stroke, brush);
}
SdfShapeData::Ellipse { center, radii } => {
let radius = radii.0.max(radii.1);
self.ctx.stroke_circle(center, radius, stroke, brush);
}
}
}
}
fn shadow(&mut self, shape: ShapeId, shadow: Shadow) {
if let Some(shape_data) = self.shapes.get(shape.0 as usize) {
match shape_data.clone() {
SdfShapeData::Rect {
rect,
corner_radius,
} => {
self.ctx.draw_shadow(rect, corner_radius, shadow);
}
SdfShapeData::Circle { center, radius } => {
let rect = Rect::new(
center.x - radius,
center.y - radius,
radius * 2.0,
radius * 2.0,
);
self.ctx.draw_shadow(rect, radius.into(), shadow);
}
SdfShapeData::Ellipse { center, radii } => {
let rect = Rect::new(
center.x - radii.0,
center.y - radii.1,
radii.0 * 2.0,
radii.1 * 2.0,
);
self.ctx.draw_shadow(rect, CornerRadius::default(), shadow);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use blinc_core::Color;
#[test]
fn test_gpu_paint_context_creation() {
let ctx = GpuPaintContext::new(800.0, 600.0);
assert_eq!(ctx.viewport_size(), Size::new(800.0, 600.0));
assert!(!ctx.is_3d_context());
assert_eq!(ctx.current_opacity(), 1.0);
}
#[test]
fn test_fill_rect() {
let mut ctx = GpuPaintContext::new(800.0, 600.0);
ctx.fill_rect(
Rect::new(10.0, 20.0, 100.0, 50.0),
8.0.into(),
Color::BLUE.into(),
);
assert_eq!(ctx.batch().primitive_count(), 1);
}
#[test]
fn test_transform_stack() {
let mut ctx = GpuPaintContext::new(800.0, 600.0);
ctx.push_transform(Transform::translate(10.0, 20.0));
ctx.fill_rect(
Rect::new(0.0, 0.0, 100.0, 50.0),
0.0.into(),
Color::RED.into(),
);
let batch = ctx.batch();
let prim = &batch.primitives[0];
assert_eq!(prim.bounds[0], 10.0);
assert_eq!(prim.bounds[1], 20.0);
}
#[test]
fn test_opacity_stack() {
let mut ctx = GpuPaintContext::new(800.0, 600.0);
ctx.push_opacity(0.5);
ctx.push_opacity(0.5);
assert_eq!(ctx.current_opacity(), 0.25);
ctx.pop_opacity();
assert_eq!(ctx.current_opacity(), 0.5);
}
#[test]
fn test_execute_commands() {
use blinc_core::RecordingContext;
let mut recording = RecordingContext::new(Size::new(800.0, 600.0));
recording.fill_rect(
Rect::new(10.0, 20.0, 100.0, 50.0),
4.0.into(),
Color::GREEN.into(),
);
let commands = recording.take_commands();
let mut ctx = GpuPaintContext::new(800.0, 600.0);
ctx.execute_commands(&commands);
assert_eq!(ctx.batch().primitive_count(), 1);
}
#[test]
fn test_layer_stack_tracking() {
let mut ctx = GpuPaintContext::new(800.0, 600.0);
assert_eq!(ctx.layer_stack.len(), 0);
assert_eq!(ctx.current_opacity(), 1.0);
assert_eq!(ctx.current_blend_mode(), BlendMode::Normal);
let config = LayerConfig {
id: None,
position: None,
size: None,
blend_mode: BlendMode::Multiply,
opacity: 0.5,
depth: false,
effects: Vec::new(),
transform_3d: None,
};
ctx.push_layer(config);
assert_eq!(ctx.layer_stack.len(), 1);
assert_eq!(ctx.current_opacity(), 0.5);
assert_eq!(ctx.current_blend_mode(), BlendMode::Multiply);
ctx.fill_rect(
Rect::new(10.0, 10.0, 100.0, 100.0),
0.0.into(),
Color::RED.into(),
);
ctx.pop_layer();
assert_eq!(ctx.layer_stack.len(), 0);
assert_eq!(ctx.current_opacity(), 1.0);
assert_eq!(ctx.current_blend_mode(), BlendMode::Normal);
}
#[test]
fn test_nested_layers() {
let mut ctx = GpuPaintContext::new(800.0, 600.0);
let config1 = LayerConfig {
id: None,
position: None,
size: None,
blend_mode: BlendMode::Normal,
opacity: 0.8,
depth: false,
effects: Vec::new(),
transform_3d: None,
};
ctx.push_layer(config1);
assert_eq!(ctx.layer_stack.len(), 1);
assert_eq!(ctx.current_opacity(), 0.8);
let config2 = LayerConfig {
id: None,
position: None,
size: None,
blend_mode: BlendMode::Screen,
opacity: 0.5,
depth: false,
effects: Vec::new(),
transform_3d: None,
};
ctx.push_layer(config2);
assert_eq!(ctx.layer_stack.len(), 2);
assert!((ctx.current_opacity() - 0.4).abs() < 0.001);
assert_eq!(ctx.current_blend_mode(), BlendMode::Screen);
ctx.pop_layer();
assert_eq!(ctx.layer_stack.len(), 1);
assert_eq!(ctx.current_opacity(), 0.8);
ctx.pop_layer();
assert_eq!(ctx.layer_stack.len(), 0);
assert_eq!(ctx.current_opacity(), 1.0);
}
}