use crate::cache::CachedTessellation;
use crate::gradient::types::Fill;
use crate::util::{GradientCache, PoolManager};
use crate::vertex::{CustomVertex, InstanceTransform};
use crate::{Color, Stroke};
use ahash::AHashMap;
use lyon::lyon_tessellation::{
BuffersBuilder, FillOptions, FillTessellator, FillVertex, VertexBuffers,
};
use lyon::path::Winding;
use lyon::tessellation::FillVertexConstructor;
use smallvec::SmallVec;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct CachedShapeHandle {
pub(crate) tessellation: Arc<CachedTessellation>,
pub(crate) is_rect: bool,
pub(crate) rect_bounds: Option<[(f32, f32); 2]>,
pub(crate) geometry_id: Option<u64>,
}
impl CachedShapeHandle {
pub(crate) fn new(
shape: &Shape,
tessellator: &mut FillTessellator,
pool: &mut PoolManager,
geometry_id: Option<u64>,
) -> Self {
let (is_rect, rect_bounds) = match shape {
Shape::Rect(r) => (true, Some(r.rect)),
_ => (false, None),
};
let tessellation = shape.tessellate(tessellator, pool, geometry_id);
Self {
tessellation,
is_rect,
rect_bounds,
geometry_id,
}
}
#[inline]
pub(crate) fn vertex_buffers(&self) -> &Arc<VertexBuffers<CustomVertex, u16>> {
&self.tessellation.vertex_buffers
}
#[inline]
pub(crate) fn local_bounds(&self) -> [(f32, f32); 2] {
self.tessellation.local_bounds
}
#[inline]
pub(crate) fn texture_mapping_size(&self) -> [f32; 2] {
self.tessellation.texture_mapping_size
}
}
fn rect_size(rect_bounds: [(f32, f32); 2]) -> [f32; 2] {
[
(rect_bounds[1].0 - rect_bounds[0].0).abs().max(1e-6),
(rect_bounds[1].1 - rect_bounds[0].1).abs().max(1e-6),
]
}
fn compute_vertex_bounds(vertices: &[CustomVertex]) -> [(f32, f32); 2] {
if vertices.is_empty() {
return [(0.0, 0.0), (1.0, 1.0)];
}
let mut min_x = f32::INFINITY;
let mut min_y = f32::INFINITY;
let mut max_x = f32::NEG_INFINITY;
let mut max_y = f32::NEG_INFINITY;
for vertex in vertices {
let x = vertex.position[0];
let y = vertex.position[1];
if x < min_x {
min_x = x;
}
if y < min_y {
min_y = y;
}
if x > max_x {
max_x = x;
}
if y > max_y {
max_y = y;
}
}
[(min_x, min_y), (max_x, max_y)]
}
#[derive(Debug, Clone)]
pub enum Shape {
Path(PathShape),
Rect(RectShape),
}
impl Shape {
pub fn builder() -> ShapeBuilder {
ShapeBuilder::new()
}
pub fn rect(rect: [(f32, f32); 2], stroke: Stroke) -> Shape {
let rect_shape = RectShape::new(rect, stroke);
Shape::Rect(rect_shape)
}
pub fn rounded_rect(rect: [(f32, f32); 2], border_radii: BorderRadii, stroke: Stroke) -> Shape {
let mut path_builder = lyon::path::Path::builder();
let box2d = lyon::math::Box2D::new(rect[0].into(), rect[1].into());
path_builder.add_rounded_rectangle(&box2d, &border_radii.into(), Winding::Positive);
let path = path_builder.build();
let path_shape = PathShape { path, stroke };
Shape::Path(path_shape)
}
pub(crate) fn tessellate(
&self,
tessellator: &mut FillTessellator,
buffers_pool: &mut PoolManager,
tesselation_cache_key: Option<u64>,
) -> Arc<CachedTessellation> {
match &self {
Shape::Path(path_shape) => {
path_shape.tessellate(tessellator, buffers_pool, tesselation_cache_key)
}
Shape::Rect(rect_shape) => {
if let Some(cache_key) = tesselation_cache_key {
if let Some(cached_tessellation) = buffers_pool
.tessellation_cache
.get_vertex_buffers(&cache_key)
{
return cached_tessellation;
}
}
let min_width = rect_shape.rect[0].0;
let min_height = rect_shape.rect[0].1;
let max_width = rect_shape.rect[1].0;
let max_height = rect_shape.rect[1].1;
let w = (max_width - min_width).max(1e-6);
let h = (max_height - min_height).max(1e-6);
let uv =
|x: f32, y: f32| -> [f32; 2] { [(x - min_width) / w, (y - min_height) / h] };
let quad = [
CustomVertex {
position: [min_width, min_height],
tex_coords: uv(min_width, min_height),
normal: [0.0, 0.0],
coverage: 1.0,
},
CustomVertex {
position: [max_width, min_height],
tex_coords: uv(max_width, min_height),
normal: [0.0, 0.0],
coverage: 1.0,
},
CustomVertex {
position: [max_width, max_height],
tex_coords: uv(max_width, max_height),
normal: [0.0, 0.0],
coverage: 1.0,
},
CustomVertex {
position: [min_width, max_height],
tex_coords: uv(min_width, max_height),
normal: [0.0, 0.0],
coverage: 1.0,
},
];
let indices = [0u16, 1, 2, 0, 2, 3];
let local_bounds = rect_shape.rect;
let mut vertex_buffers = buffers_pool.lyon_vertex_buffers_pool.get_vertex_buffers();
vertex_buffers.vertices.extend(quad);
vertex_buffers.indices.extend(indices);
generate_aa_fringe(
&mut vertex_buffers.vertices,
&mut vertex_buffers.indices,
&mut buffers_pool.aa_fringe_scratch,
);
let tessellation = Arc::new(CachedTessellation {
vertex_buffers: Arc::new(vertex_buffers),
local_bounds,
texture_mapping_size: rect_size(local_bounds),
});
if let Some(tesselation_cache_key) = tesselation_cache_key {
buffers_pool
.tessellation_cache
.insert_vertex_buffers(tesselation_cache_key, Arc::clone(&tessellation));
}
tessellation
}
}
}
}
impl From<PathShape> for Shape {
fn from(value: PathShape) -> Self {
Shape::Path(value)
}
}
impl From<RectShape> for Shape {
fn from(value: RectShape) -> Self {
Shape::Rect(value)
}
}
impl AsRef<Shape> for Shape {
fn as_ref(&self) -> &Shape {
self
}
}
#[derive(Debug, Clone)]
pub struct RectShape {
pub(crate) rect: [(f32, f32); 2],
#[allow(unused)]
pub(crate) stroke: Stroke,
}
impl RectShape {
pub fn new(rect: [(f32, f32); 2], stroke: Stroke) -> Self {
Self { rect, stroke }
}
}
#[derive(Clone, Debug)]
pub struct PathShape {
pub(crate) path: lyon::path::Path,
#[allow(unused)]
pub(crate) stroke: Stroke,
}
struct VertexConverter {}
impl VertexConverter {
fn new() -> Self {
Self {}
}
}
impl FillVertexConstructor<CustomVertex> for VertexConverter {
fn new_vertex(&mut self, vertex: FillVertex) -> CustomVertex {
CustomVertex {
position: vertex.position().to_array(),
tex_coords: [0.0, 0.0],
normal: [0.0, 0.0],
coverage: 1.0,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
struct BoundaryVertexKey {
x_bits: u32,
y_bits: u32,
}
impl BoundaryVertexKey {
fn from_position(position: [f32; 2]) -> Self {
Self {
x_bits: normalized_float_bits(position[0]),
y_bits: normalized_float_bits(position[1]),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
struct BoundaryEdgeKey {
start: BoundaryVertexKey,
end: BoundaryVertexKey,
}
impl BoundaryEdgeKey {
fn new(start_position: [f32; 2], end_position: [f32; 2]) -> Self {
let start = BoundaryVertexKey::from_position(start_position);
let end = BoundaryVertexKey::from_position(end_position);
if start <= end {
Self { start, end }
} else {
Self {
start: end,
end: start,
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct BoundaryEdge {
start_vertex_index: u16,
end_vertex_index: u16,
opposite_vertex_index: u16,
triangle_index: usize,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
struct BoundaryCornerKey {
vertex_key: BoundaryVertexKey,
component_index: usize,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct BoundaryCornerNormalData {
accumulated_normal: [f32; 2],
source_vertex_index: u16,
}
pub(crate) struct AaFringeScratch {
edge_use_counts: AHashMap<BoundaryEdgeKey, (usize, BoundaryEdge)>,
edge_owners: AHashMap<BoundaryEdgeKey, SmallVec<[usize; 2]>>,
incident_triangles_by_vertex: AHashMap<BoundaryVertexKey, SmallVec<[usize; 4]>>,
triangle_adjacency: AHashMap<(BoundaryVertexKey, usize), SmallVec<[usize; 4]>>,
visited_triangles: AHashMap<usize, usize>,
triangle_component_map: AHashMap<(usize, BoundaryVertexKey), usize>,
boundary_corner_normals: AHashMap<BoundaryCornerKey, BoundaryCornerNormalData>,
outer_vertex_indices: AHashMap<BoundaryCornerKey, u16>,
boundary_edges: Vec<BoundaryEdge>,
triangle_stack: Vec<usize>,
}
impl AaFringeScratch {
pub(crate) fn new() -> Self {
Self {
edge_use_counts: AHashMap::new(),
edge_owners: AHashMap::new(),
incident_triangles_by_vertex: AHashMap::new(),
triangle_adjacency: AHashMap::new(),
visited_triangles: AHashMap::new(),
triangle_component_map: AHashMap::new(),
boundary_corner_normals: AHashMap::new(),
outer_vertex_indices: AHashMap::new(),
boundary_edges: Vec::new(),
triangle_stack: Vec::new(),
}
}
fn clear(&mut self) {
self.edge_use_counts.clear();
self.edge_owners.clear();
self.incident_triangles_by_vertex.clear();
self.triangle_adjacency.clear();
self.visited_triangles.clear();
self.triangle_component_map.clear();
self.boundary_corner_normals.clear();
self.outer_vertex_indices.clear();
self.boundary_edges.clear();
self.triangle_stack.clear();
}
pub(crate) fn trim(&mut self) {
self.edge_use_counts.shrink_to_fit();
self.edge_owners.shrink_to_fit();
self.incident_triangles_by_vertex.shrink_to_fit();
self.triangle_adjacency.shrink_to_fit();
self.visited_triangles.shrink_to_fit();
self.triangle_component_map.shrink_to_fit();
self.boundary_corner_normals.shrink_to_fit();
self.outer_vertex_indices.shrink_to_fit();
self.boundary_edges.shrink_to_fit();
self.triangle_stack.shrink_to_fit();
}
}
fn normalized_float_bits(value: f32) -> u32 {
if value == 0.0 {
0.0f32.to_bits()
} else {
value.to_bits()
}
}
fn build_boundary_data(vertices: &[CustomVertex], indices: &[u16], scratch: &mut AaFringeScratch) {
scratch.clear();
for (triangle_index, tri) in indices.chunks_exact(3).enumerate() {
let a = tri[0];
let b = tri[1];
let c = tri[2];
let vertex_keys = [a, b, c].map(|vertex_index| {
BoundaryVertexKey::from_position(vertices[vertex_index as usize].position)
});
for &vertex_key in &vertex_keys {
scratch
.incident_triangles_by_vertex
.entry(vertex_key)
.or_default()
.push(triangle_index);
}
for &(i, j, opp) in &[(a, b, c), (b, c, a), (c, a, b)] {
let key =
BoundaryEdgeKey::new(vertices[i as usize].position, vertices[j as usize].position);
scratch
.edge_use_counts
.entry(key)
.and_modify(|(count, _)| *count += 1)
.or_insert((
1,
BoundaryEdge {
start_vertex_index: i,
end_vertex_index: j,
opposite_vertex_index: opp,
triangle_index,
},
));
scratch
.edge_owners
.entry(key)
.or_default()
.push(triangle_index);
}
}
scratch.boundary_edges.extend(
scratch
.edge_use_counts
.values()
.filter_map(|(count, boundary_edge)| (*count == 1).then_some(*boundary_edge)),
);
}
fn build_triangle_component_map(scratch: &mut AaFringeScratch) {
for (edge_key, owners) in &scratch.edge_owners {
if owners.len() < 2 {
continue;
}
for &vertex_key in &[edge_key.start, edge_key.end] {
for (owner_index, &owner_triangle_index) in owners.iter().enumerate() {
for &other_triangle_index in &owners[owner_index + 1..] {
scratch
.triangle_adjacency
.entry((vertex_key, owner_triangle_index))
.or_default()
.push(other_triangle_index);
scratch
.triangle_adjacency
.entry((vertex_key, other_triangle_index))
.or_default()
.push(owner_triangle_index);
}
}
}
}
for (&vertex_key, incident_triangles) in &scratch.incident_triangles_by_vertex {
scratch.visited_triangles.clear();
let mut component_index = 0usize;
for &triangle_index in incident_triangles {
if scratch.visited_triangles.contains_key(&triangle_index) {
continue;
}
scratch.triangle_stack.push(triangle_index);
while let Some(current_triangle_index) = scratch.triangle_stack.pop() {
if scratch
.visited_triangles
.insert(current_triangle_index, component_index)
.is_some()
{
continue;
}
if let Some(neighbors) = scratch
.triangle_adjacency
.get(&(vertex_key, current_triangle_index))
{
for &neighbor_triangle_index in neighbors {
if !scratch
.visited_triangles
.contains_key(&neighbor_triangle_index)
{
scratch.triangle_stack.push(neighbor_triangle_index);
}
}
}
}
component_index += 1;
}
for (&triangle_index, &component_index) in &scratch.visited_triangles {
scratch
.triangle_component_map
.insert((triangle_index, vertex_key), component_index);
}
}
}
#[cfg(test)]
fn find_boundary_edges<'a>(
vertices: &[CustomVertex],
indices: &[u16],
scratch: &'a mut AaFringeScratch,
) -> &'a [BoundaryEdge] {
build_boundary_data(vertices, indices, scratch);
&scratch.boundary_edges
}
fn generate_aa_fringe(
vertices: &mut Vec<CustomVertex>,
indices: &mut Vec<u16>,
scratch: &mut AaFringeScratch,
) {
build_boundary_data(vertices, indices, scratch);
if scratch.boundary_edges.is_empty() {
return;
}
build_triangle_component_map(scratch);
for boundary_edge in &scratch.boundary_edges {
let pa = vertices[boundary_edge.start_vertex_index as usize].position;
let pb = vertices[boundary_edge.end_vertex_index as usize].position;
let po = vertices[boundary_edge.opposite_vertex_index as usize].position;
let dx = pb[0] - pa[0];
let dy = pb[1] - pa[1];
let edge_len = (dx * dx + dy * dy).sqrt();
if edge_len < 1e-10 {
continue;
}
let n1 = [-dy / edge_len, dx / edge_len];
let n2 = [dy / edge_len, -dx / edge_len];
let to_opp = [po[0] - pa[0], po[1] - pa[1]];
let dot1 = n1[0] * to_opp[0] + n1[1] * to_opp[1];
let outward = if dot1 < 0.0 { n1 } else { n2 };
for &vertex_index in &[
boundary_edge.start_vertex_index,
boundary_edge.end_vertex_index,
] {
let vertex_key =
BoundaryVertexKey::from_position(vertices[vertex_index as usize].position);
let component_index = *scratch
.triangle_component_map
.get(&(boundary_edge.triangle_index, vertex_key))
.unwrap_or_else(|| {
panic!(
"missing triangle component mapping for triangle {} and boundary vertex {:?}",
boundary_edge.triangle_index,
vertex_key
)
});
let entry = scratch
.boundary_corner_normals
.entry(BoundaryCornerKey {
vertex_key,
component_index,
})
.or_insert(BoundaryCornerNormalData {
accumulated_normal: [0.0, 0.0],
source_vertex_index: vertex_index,
});
entry.accumulated_normal[0] += outward[0];
entry.accumulated_normal[1] += outward[1];
}
}
for boundary_corner_normal in scratch.boundary_corner_normals.values_mut() {
let len = (boundary_corner_normal.accumulated_normal[0]
* boundary_corner_normal.accumulated_normal[0]
+ boundary_corner_normal.accumulated_normal[1]
* boundary_corner_normal.accumulated_normal[1])
.sqrt();
if len > 1e-10 {
boundary_corner_normal.accumulated_normal[0] /= len;
boundary_corner_normal.accumulated_normal[1] /= len;
}
}
vertices.reserve(scratch.boundary_corner_normals.len());
indices.reserve(scratch.boundary_edges.len() * 6);
for (&boundary_corner_key, boundary_corner_normal) in &scratch.boundary_corner_normals {
let source_vertex = &vertices[boundary_corner_normal.source_vertex_index as usize];
let outer_vertex = CustomVertex {
position: source_vertex.position,
tex_coords: source_vertex.tex_coords,
normal: boundary_corner_normal.accumulated_normal,
coverage: 0.0,
};
let new_idx = vertices.len() as u16;
vertices.push(outer_vertex);
scratch
.outer_vertex_indices
.insert(boundary_corner_key, new_idx);
}
for boundary_edge in &scratch.boundary_edges {
let start_vertex_key = BoundaryVertexKey::from_position(
vertices[boundary_edge.start_vertex_index as usize].position,
);
let end_vertex_key = BoundaryVertexKey::from_position(
vertices[boundary_edge.end_vertex_index as usize].position,
);
let pa = vertices[boundary_edge.start_vertex_index as usize].position;
let pb = vertices[boundary_edge.end_vertex_index as usize].position;
let po = vertices[boundary_edge.opposite_vertex_index as usize].position;
let dx = pb[0] - pa[0];
let dy = pb[1] - pa[1];
let edge_len = (dx * dx + dy * dy).sqrt();
if edge_len < 1e-10 {
continue;
}
let start_component_index = *scratch
.triangle_component_map
.get(&(boundary_edge.triangle_index, start_vertex_key))
.unwrap_or_else(|| {
panic!(
"missing triangle component mapping for triangle {} and boundary vertex {:?}",
boundary_edge.triangle_index, start_vertex_key
)
});
let end_component_index = *scratch
.triangle_component_map
.get(&(boundary_edge.triangle_index, end_vertex_key))
.unwrap_or_else(|| {
panic!(
"missing triangle component mapping for triangle {} and boundary vertex {:?}",
boundary_edge.triangle_index, end_vertex_key
)
});
let start_boundary_corner_key = BoundaryCornerKey {
vertex_key: start_vertex_key,
component_index: start_component_index,
};
let start_outer_vertex_index = match scratch
.outer_vertex_indices
.get(&start_boundary_corner_key)
{
Some(&idx) => idx,
None => {
debug_assert!(
false,
"missing outer vertex index for {:?} (start_vertex_key: {:?}, start_component_index: {})",
start_boundary_corner_key,
start_vertex_key,
start_component_index
);
continue;
}
};
let end_boundary_corner_key = BoundaryCornerKey {
vertex_key: end_vertex_key,
component_index: end_component_index,
};
let end_outer_vertex_index = match scratch
.outer_vertex_indices
.get(&end_boundary_corner_key)
{
Some(&idx) => idx,
None => {
debug_assert!(
false,
"missing outer vertex index for {:?} (end_vertex_key: {:?}, end_component_index: {})",
end_boundary_corner_key,
end_vertex_key,
end_component_index
);
continue;
}
};
let cross = (pb[0] - pa[0]) * (po[1] - pa[1]) - (pb[1] - pa[1]) * (po[0] - pa[0]);
if cross >= 0.0 {
indices.push(boundary_edge.start_vertex_index);
indices.push(boundary_edge.end_vertex_index);
indices.push(start_outer_vertex_index);
indices.push(boundary_edge.end_vertex_index);
indices.push(end_outer_vertex_index);
indices.push(start_outer_vertex_index);
} else {
indices.push(boundary_edge.start_vertex_index);
indices.push(start_outer_vertex_index);
indices.push(boundary_edge.end_vertex_index);
indices.push(boundary_edge.end_vertex_index);
indices.push(start_outer_vertex_index);
indices.push(end_outer_vertex_index);
}
}
}
impl PathShape {
pub fn new(path: lyon::path::Path, stroke: Stroke) -> Self {
Self { path, stroke }
}
pub(crate) fn tessellate(
&self,
tessellator: &mut FillTessellator,
buffers_pool: &mut PoolManager,
tesselation_cache_key: Option<u64>,
) -> Arc<CachedTessellation> {
if let Some(cache_key) = tesselation_cache_key {
if let Some(cached_tessellation) = buffers_pool
.tessellation_cache
.get_vertex_buffers(&cache_key)
{
return cached_tessellation;
}
}
let mut buffers: VertexBuffers<CustomVertex, u16> =
buffers_pool.lyon_vertex_buffers_pool.get_vertex_buffers();
let local_bounds = self.tessellate_into_buffers(
&mut buffers,
tessellator,
&mut buffers_pool.aa_fringe_scratch,
);
#[allow(clippy::manual_is_multiple_of)]
let needs_index_padding = buffers.indices.len() % 2 != 0;
if needs_index_padding {
buffers.indices.push(0);
}
let tessellation = Arc::new(CachedTessellation {
vertex_buffers: Arc::new(buffers),
local_bounds,
texture_mapping_size: rect_size(local_bounds),
});
if let Some(cache_key) = tesselation_cache_key {
buffers_pool
.tessellation_cache
.insert_vertex_buffers(cache_key, Arc::clone(&tessellation));
}
tessellation
}
fn tessellate_into_buffers(
&self,
buffers: &mut VertexBuffers<CustomVertex, u16>,
tessellator: &mut FillTessellator,
aa_fringe_scratch: &mut AaFringeScratch,
) -> [(f32, f32); 2] {
let options = FillOptions::default();
let vertex_converter = VertexConverter::new();
tessellator
.tessellate_path(
&self.path,
&options,
&mut BuffersBuilder::new(buffers, vertex_converter),
)
.unwrap();
let local_bounds = compute_vertex_bounds(&buffers.vertices);
if !buffers.vertices.is_empty() {
let w = (local_bounds[1].0 - local_bounds[0].0).max(1e-6);
let h = (local_bounds[1].1 - local_bounds[0].1).max(1e-6);
for v in buffers.vertices.iter_mut() {
let u = (v.position[0] - local_bounds[0].0) / w;
let vcoord = (v.position[1] - local_bounds[0].1) / h;
v.tex_coords = [u, vcoord];
}
}
generate_aa_fringe(
&mut buffers.vertices,
&mut buffers.indices,
aa_fringe_scratch,
);
local_bounds
}
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub enum ShapeTextureFitMode {
#[default]
Stretch,
OriginalSize,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct ShapeTextureOptions {
pub texture_id: Option<u64>,
pub fit_mode: ShapeTextureFitMode,
}
impl ShapeTextureOptions {
pub fn new(texture_id: u64) -> Self {
Self {
texture_id: Some(texture_id),
fit_mode: ShapeTextureFitMode::Stretch,
}
}
pub fn fit_mode(mut self, fit_mode: ShapeTextureFitMode) -> Self {
self.fit_mode = fit_mode;
self
}
}
#[derive(Clone, Debug)]
pub struct ShapeDrawCommandOptions {
pub transform: Option<InstanceTransform>,
pub clips_children: bool,
pub background_texture: ShapeTextureOptions,
pub foreground_texture: ShapeTextureOptions,
pub fill: Option<Fill>,
}
impl Default for ShapeDrawCommandOptions {
fn default() -> Self {
Self {
transform: None,
clips_children: true,
background_texture: ShapeTextureOptions::default(),
foreground_texture: ShapeTextureOptions::default(),
fill: None,
}
}
}
impl ShapeDrawCommandOptions {
pub fn new() -> Self {
Self::default()
}
pub fn transform(mut self, transform: InstanceTransform) -> Self {
self.transform = Some(transform);
self
}
pub fn clips_children(mut self, clips_children: bool) -> Self {
self.clips_children = clips_children;
self
}
pub fn background_texture(mut self, background_texture: ShapeTextureOptions) -> Self {
self.background_texture = background_texture;
self
}
pub fn foreground_texture(mut self, foreground_texture: ShapeTextureOptions) -> Self {
self.foreground_texture = foreground_texture;
self
}
pub fn background_texture_id(mut self, background_texture_id: u64) -> Self {
self.background_texture.texture_id = Some(background_texture_id);
self
}
pub fn foreground_texture_id(mut self, foreground_texture_id: u64) -> Self {
self.foreground_texture.texture_id = Some(foreground_texture_id);
self
}
pub fn texture_fit_mode(mut self, texture_fit_mode: ShapeTextureFitMode) -> Self {
self.background_texture.fit_mode = texture_fit_mode;
self.foreground_texture.fit_mode = texture_fit_mode;
self
}
pub fn background_texture_fit_mode(
mut self,
background_texture_fit_mode: ShapeTextureFitMode,
) -> Self {
self.background_texture.fit_mode = background_texture_fit_mode;
self
}
pub fn foreground_texture_fit_mode(
mut self,
foreground_texture_fit_mode: ShapeTextureFitMode,
) -> Self {
self.foreground_texture.fit_mode = foreground_texture_fit_mode;
self
}
pub fn fill(mut self, fill: Fill) -> Self {
self.fill = Some(fill);
self
}
pub fn color(mut self, color: Color) -> Self {
self.fill = Some(Fill::Solid(color));
self
}
}
#[derive(Debug)]
pub(crate) struct CachedShapeDrawData {
pub(crate) cached_shape: CachedShapeHandle,
pub(crate) index_buffer_range: Option<(usize, usize)>,
pub(crate) is_empty: bool,
pub(crate) stencil_ref: Option<u32>,
pub(crate) instance_index: Option<usize>,
pub(crate) transform: Option<InstanceTransform>,
pub(crate) texture_ids: [Option<u64>; 2],
pub(crate) color_override: Option<[f32; 4]>,
pub(crate) fill: Option<Fill>,
pub(crate) gradient_bind_group: Option<Arc<wgpu::BindGroup>>,
pub(crate) backdrop_material_params_buffer: Option<wgpu::Buffer>,
pub(crate) backdrop_gradient_bind_group: Option<wgpu::BindGroup>,
pub(crate) backdrop_gradient_texture_id: Option<u64>,
pub(crate) is_leaf: bool,
pub(crate) clips_children: bool,
}
impl CachedShapeDrawData {
pub fn new(cached_shape: CachedShapeHandle, options: &ShapeDrawCommandOptions) -> Self {
Self {
cached_shape,
index_buffer_range: None,
is_empty: false,
transform: options.transform,
texture_ids: [
options.background_texture.texture_id,
options.foreground_texture.texture_id,
],
clips_children: options.clips_children,
color_override: match options.fill.as_ref() {
Some(Fill::Solid(color)) => Some(color.normalize()),
_ => None,
},
fill: options.fill.clone(),
instance_index: None,
stencil_ref: None,
gradient_bind_group: None,
backdrop_material_params_buffer: None,
backdrop_gradient_bind_group: None,
backdrop_gradient_texture_id: None,
is_leaf: true,
}
}
pub fn refresh_gradient_bind_group(
&mut self,
gradient_cache: &mut GradientCache,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
sampler: &wgpu::Sampler,
layout_epoch: u64,
) {
self.gradient_bind_group = match self.fill.as_mut() {
Some(Fill::Gradient(gradient)) => Some(gradient_cache.get_or_create_bind_group(
&mut gradient.data,
device,
queue,
layout,
sampler,
layout_epoch,
)),
_ => None,
};
}
pub fn prepare_gradient_backdrop_material_params_buffer(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
backdrop_sampling_uniform: crate::pipeline::BackdropSamplingUniform,
) -> Option<wgpu::Buffer> {
let params = {
let gradient = match self.fill.as_ref() {
Some(Fill::Gradient(gradient)) => gradient,
_ => return None,
};
crate::gradient::gpu::GpuMaterialParams::from_gradient_data(&gradient.data)
.with_backdrop_sampling(backdrop_sampling_uniform)
};
if let Some(existing_buffer) = self.backdrop_material_params_buffer.as_ref() {
queue.write_buffer(existing_buffer, 0, bytemuck::bytes_of(¶ms));
} else {
self.backdrop_material_params_buffer = Some(crate::pipeline::create_buffer_init(
device,
Some("gradient_backdrop_material_params_buffer"),
bytemuck::bytes_of(¶ms),
wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
));
}
self.backdrop_material_params_buffer.clone()
}
#[allow(clippy::too_many_arguments)]
pub fn prepare_backdrop_gradient_bind_group(
&mut self,
gradient_cache: &mut GradientCache,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
material_params_buffer: &wgpu::Buffer,
gradient_sampler: &wgpu::Sampler,
backdrop_texture_id: u64,
backdrop_view: &wgpu::TextureView,
backdrop_sampler: &wgpu::Sampler,
) -> Option<&wgpu::BindGroup> {
if self.backdrop_gradient_texture_id != Some(backdrop_texture_id) {
let gradient = match self.fill.as_mut() {
Some(Fill::Gradient(gradient)) => gradient,
_ => return None,
};
self.backdrop_gradient_bind_group =
Some(gradient_cache.create_backdrop_gradient_bind_group(
&mut gradient.data,
device,
queue,
layout,
material_params_buffer,
gradient_sampler,
backdrop_view,
backdrop_sampler,
));
self.backdrop_gradient_texture_id = Some(backdrop_texture_id);
}
self.backdrop_gradient_bind_group.as_ref()
}
}
#[derive(Clone)]
pub struct ShapeBuilder {
stroke: Stroke,
path_builder: lyon::path::Builder,
}
impl Default for ShapeBuilder {
fn default() -> Self {
Self::new()
}
}
impl ShapeBuilder {
pub fn new() -> Self {
Self {
stroke: Stroke::new(1.0, Color::rgb(0, 0, 0)),
path_builder: lyon::path::Path::builder(),
}
}
pub fn stroke(mut self, stroke: Stroke) -> Self {
self.stroke = stroke;
self
}
pub fn begin(mut self, point: (f32, f32)) -> Self {
self.path_builder.begin(point.into());
self
}
pub fn line_to(mut self, point: (f32, f32)) -> Self {
self.path_builder.line_to(point.into());
self
}
pub fn cubic_bezier_to(mut self, ctrl: (f32, f32), ctrl2: (f32, f32), to: (f32, f32)) -> Self {
self.path_builder
.cubic_bezier_to(ctrl.into(), ctrl2.into(), to.into());
self
}
pub fn quadratic_bezier_to(mut self, ctrl: (f32, f32), to: (f32, f32)) -> Self {
self.path_builder
.quadratic_bezier_to(ctrl.into(), to.into());
self
}
pub fn close(mut self) -> Self {
self.path_builder.close();
self
}
pub fn build(self) -> Shape {
let path = self.path_builder.build();
Shape::Path(PathShape {
path,
stroke: self.stroke,
})
}
}
impl From<ShapeBuilder> for Shape {
fn from(value: ShapeBuilder) -> Self {
value.build()
}
}
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Default)]
pub struct BorderRadii {
pub top_left: f32,
pub top_right: f32,
pub bottom_left: f32,
pub bottom_right: f32,
}
impl BorderRadii {
pub fn new(radius: f32) -> Self {
let r = radius.abs();
BorderRadii {
top_left: r,
top_right: r,
bottom_left: r,
bottom_right: r,
}
}
}
impl core::fmt::Display for BorderRadii {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"BorderRadii({}, {}, {}, {})",
self.top_left, self.top_right, self.bottom_left, self.bottom_right
)
}
}
impl From<BorderRadii> for lyon::path::builder::BorderRadii {
fn from(val: BorderRadii) -> Self {
lyon::path::builder::BorderRadii {
top_left: val.top_left,
top_right: val.top_right,
bottom_left: val.bottom_left,
bottom_right: val.bottom_right,
}
}
}
pub(crate) trait DrawShapeCommand {
fn index_buffer_range(&self) -> Option<(usize, usize)>; fn is_empty(&self) -> bool;
fn stencil_ref_mut(&mut self) -> &mut Option<u32>;
fn instance_index_mut(&mut self) -> &mut Option<usize>;
fn instance_index(&self) -> Option<usize>;
fn transform(&self) -> Option<InstanceTransform>;
fn texture_id(&self, layer: usize) -> Option<u64>;
fn local_bounds(&self) -> [(f32, f32); 2];
fn instance_color_override(&self) -> Option<[f32; 4]>;
fn has_gradient_fill(&self) -> bool;
fn gradient_bind_group(&self) -> Option<&std::sync::Arc<wgpu::BindGroup>>;
fn clips_children(&self) -> bool;
fn is_rect(&self) -> bool;
fn rect_bounds(&self) -> Option<[(f32, f32); 2]>;
}
impl DrawShapeCommand for CachedShapeDrawData {
#[inline]
fn index_buffer_range(&self) -> Option<(usize, usize)> {
self.index_buffer_range
}
#[inline]
fn is_empty(&self) -> bool {
self.is_empty
}
#[inline]
fn stencil_ref_mut(&mut self) -> &mut Option<u32> {
&mut self.stencil_ref
}
#[inline]
fn instance_index_mut(&mut self) -> &mut Option<usize> {
&mut self.instance_index
}
#[inline]
fn instance_index(&self) -> Option<usize> {
self.instance_index
}
#[inline]
fn transform(&self) -> Option<InstanceTransform> {
self.transform
}
#[inline]
fn texture_id(&self, layer: usize) -> Option<u64> {
self.texture_ids.get(layer).copied().unwrap_or(None)
}
#[inline]
fn local_bounds(&self) -> [(f32, f32); 2] {
self.cached_shape.local_bounds()
}
#[inline]
fn instance_color_override(&self) -> Option<[f32; 4]> {
self.color_override
}
#[inline]
fn has_gradient_fill(&self) -> bool {
matches!(&self.fill, Some(Fill::Gradient(_)))
}
#[inline]
fn gradient_bind_group(&self) -> Option<&std::sync::Arc<wgpu::BindGroup>> {
self.gradient_bind_group.as_ref()
}
#[inline]
fn clips_children(&self) -> bool {
self.clips_children
}
#[inline]
fn is_rect(&self) -> bool {
self.cached_shape.is_rect
}
#[inline]
fn rect_bounds(&self) -> Option<[(f32, f32); 2]> {
self.cached_shape.rect_bounds
}
}
#[cfg(test)]
mod tests {
use super::{
find_boundary_edges, generate_aa_fringe, AaFringeScratch, BoundaryVertexKey, CustomVertex,
RectShape, Shape,
};
use crate::{util::PoolManager, Stroke};
use lyon::lyon_tessellation::FillTessellator;
use std::num::NonZeroUsize;
fn test_vertex(position: [f32; 2]) -> CustomVertex {
CustomVertex {
position,
tex_coords: [0.0, 0.0],
normal: [0.0, 0.0],
coverage: 1.0,
}
}
#[test]
fn aa_fringe_ignores_internal_seams_with_duplicate_vertices() {
let mut vertices = vec![
test_vertex([0.0, 0.0]),
test_vertex([1.0, 0.0]),
test_vertex([1.0, 1.0]),
test_vertex([0.0, 0.0]),
test_vertex([1.0, 1.0]),
test_vertex([0.0, 1.0]),
];
let mut indices = vec![0, 1, 2, 3, 4, 5];
let mut aa_fringe_scratch = AaFringeScratch::new();
let boundary_edges = find_boundary_edges(&vertices, &indices, &mut aa_fringe_scratch);
assert_eq!(boundary_edges.len(), 4);
generate_aa_fringe(&mut vertices, &mut indices, &mut aa_fringe_scratch);
assert_eq!(vertices.len(), 10);
assert_eq!(indices.len(), 30);
let outer_vertex_count = vertices
.iter()
.filter(|vertex| vertex.coverage == 0.0)
.count();
assert_eq!(outer_vertex_count, 4);
let unique_outer_vertex_positions = vertices
.iter()
.filter(|vertex| vertex.coverage == 0.0)
.map(|vertex| BoundaryVertexKey::from_position(vertex.position))
.collect::<std::collections::BTreeSet<_>>();
assert_eq!(unique_outer_vertex_positions.len(), 4);
}
#[test]
fn rect_tessellation_uses_shared_quad_corners() {
let rect_shape = RectShape::new([(10.0, 20.0), (30.0, 50.0)], Stroke::default());
let mut tessellator = FillTessellator::new();
let mut pool_manager = PoolManager::new(NonZeroUsize::new(1).unwrap());
let tessellated_geometry =
Shape::Rect(rect_shape).tessellate(&mut tessellator, &mut pool_manager, None);
assert_eq!(tessellated_geometry.vertex_buffers.vertices.len(), 8);
assert_eq!(tessellated_geometry.vertex_buffers.indices.len(), 30);
let fill_vertex_count = tessellated_geometry
.vertex_buffers
.vertices
.iter()
.filter(|vertex| vertex.coverage == 1.0)
.count();
assert_eq!(fill_vertex_count, 4);
}
#[test]
fn aa_fringe_keeps_distinct_corners_for_point_touching_triangles() {
let mut vertices = vec![
test_vertex([0.0, 0.0]),
test_vertex([1.0, 0.0]),
test_vertex([0.0, 1.0]),
test_vertex([0.0, 0.0]),
test_vertex([-1.0, 0.0]),
test_vertex([0.0, -1.0]),
];
let mut indices = vec![0, 1, 2, 3, 4, 5];
let mut aa_fringe_scratch = AaFringeScratch::new();
generate_aa_fringe(&mut vertices, &mut indices, &mut aa_fringe_scratch);
let outer_vertices = vertices
.iter()
.filter(|vertex| vertex.coverage == 0.0)
.collect::<Vec<_>>();
assert_eq!(outer_vertices.len(), 6);
let shared_point_outer_vertices = outer_vertices
.iter()
.filter(|vertex| {
BoundaryVertexKey::from_position(vertex.position)
== BoundaryVertexKey::from_position([0.0, 0.0])
})
.count();
assert_eq!(shared_point_outer_vertices, 2);
}
#[test]
fn aa_fringe_skips_zero_length_boundary_edges() {
let mut vertices = vec![
test_vertex([5.0, 5.0]),
test_vertex([5.0, 5.0]),
test_vertex([6.0, 5.0]),
];
let mut indices = vec![0, 1, 2];
let mut aa_fringe_scratch = AaFringeScratch::new();
generate_aa_fringe(&mut vertices, &mut indices, &mut aa_fringe_scratch);
assert_eq!(vertices.len(), 3);
assert_eq!(indices.len(), 3);
}
}