Files
oak-gpui/crates/gpui/src/scene.rs
T
Max BrunsfeldandJunkui Zhang 4d00d07df1 Render paths to a single fixed-size MSAA texture (#34992)
This is another attempt to solve the same problem as
https://github.com/zed-industries/zed/pull/29718, while avoiding the
regression on Intel GPUs.

###  Background

Currently, on main, all paths are first rendered to an intermediate
"atlas" texture, similar to what we use for rendering glyphs, but with
multi-sample antialiasing enabled. They are then drawn into our actual
frame buffer in a separate pass, via the "path sprite" shaders.

Notably, the intermediate texture acts as an "atlas" - the paths are
laid out in a non-overlapping way, so that each path could be copied to
an arbitrary position in the final scene. This non-overlapping approach
makes a lot sense for Glyphs (which are frequently re-used in multiple
places within a frame, and even across frames), but paths do not have
these properties.
* we clear the atlas every frame
* we rasterize each path separately. there is no deduping.

The problem with our current approach is that the path atlas textures
can end up using lots of VRAM if the scene contains many paths. This is
more of a problem in other apps that use GPUI than it is in Zed, but I
do think it's an issue for Zed as well. On Windows, I have hit some
crashes related to GPU memory.

In https://github.com/zed-industries/zed/pull/29718, @sunli829
simplified path rendering to just draw directly to the frame buffer, and
enabled msaa for the whole frame buffer. But apparently this doesn't
work well on Intel GPUs because MSAA is slow on those GPUs. So we
reverted that PR.

### Solution

With this PR, we rasterize paths to an intermediate texture with MSAA.
But rather than treating this intermediate texture like an *atlas*
(growing it in order to allocate non-overlapping rectangles for every
path), we simply use a single fixed-size, color texture that is the same
size as thew viewport. In this texture, we rasterize the paths in their
final screen position, allowing them to overlap. Then we simply blit
them from the resolved texture to the frame buffer.

### To do

* [x] Implement for Metal
* [x] Implement for Blade
* [x] Fix content masking for paths
* [x] Fix rendering of partially transparent paths
* [x] Verify that this performs well on Intel GPUs (help @notpeter 🙏 )
* [ ] Profile and optimize

Release Notes:

- N/A

---------

Co-authored-by: Junkui Zhang <364772080@qq.com>
2025-07-25 14:39:24 -07:00

819 lines
26 KiB
Rust

// todo("windows"): remove
#![cfg_attr(windows, allow(dead_code))]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{
AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point,
};
use std::{fmt::Debug, iter::Peekable, ops::Range, slice};
#[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
pub(crate) type DrawOrder = u32;
#[derive(Default)]
pub(crate) struct Scene {
pub(crate) paint_operations: Vec<PaintOperation>,
primitive_bounds: BoundsTree<ScaledPixels>,
layer_stack: Vec<DrawOrder>,
pub(crate) shadows: Vec<Shadow>,
pub(crate) quads: Vec<Quad>,
pub(crate) paths: Vec<Path<ScaledPixels>>,
pub(crate) underlines: Vec<Underline>,
pub(crate) monochrome_sprites: Vec<MonochromeSprite>,
pub(crate) polychrome_sprites: Vec<PolychromeSprite>,
pub(crate) surfaces: Vec<PaintSurface>,
}
impl Scene {
pub fn clear(&mut self) {
self.paint_operations.clear();
self.primitive_bounds.clear();
self.layer_stack.clear();
self.paths.clear();
self.shadows.clear();
self.quads.clear();
self.underlines.clear();
self.monochrome_sprites.clear();
self.polychrome_sprites.clear();
self.surfaces.clear();
}
pub fn len(&self) -> usize {
self.paint_operations.len()
}
pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
let order = self.primitive_bounds.insert(bounds);
self.layer_stack.push(order);
self.paint_operations
.push(PaintOperation::StartLayer(bounds));
}
pub fn pop_layer(&mut self) {
self.layer_stack.pop();
self.paint_operations.push(PaintOperation::EndLayer);
}
pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
let mut primitive = primitive.into();
let clipped_bounds = primitive
.bounds()
.intersect(&primitive.content_mask().bounds);
if clipped_bounds.is_empty() {
return;
}
let order = self
.layer_stack
.last()
.copied()
.unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds));
match &mut primitive {
Primitive::Shadow(shadow) => {
shadow.order = order;
self.shadows.push(shadow.clone());
}
Primitive::Quad(quad) => {
quad.order = order;
self.quads.push(quad.clone());
}
Primitive::Path(path) => {
path.order = order;
path.id = PathId(self.paths.len());
self.paths.push(path.clone());
}
Primitive::Underline(underline) => {
underline.order = order;
self.underlines.push(underline.clone());
}
Primitive::MonochromeSprite(sprite) => {
sprite.order = order;
self.monochrome_sprites.push(sprite.clone());
}
Primitive::PolychromeSprite(sprite) => {
sprite.order = order;
self.polychrome_sprites.push(sprite.clone());
}
Primitive::Surface(surface) => {
surface.order = order;
self.surfaces.push(surface.clone());
}
}
self.paint_operations
.push(PaintOperation::Primitive(primitive));
}
pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
for operation in &prev_scene.paint_operations[range] {
match operation {
PaintOperation::Primitive(primitive) => self.insert_primitive(primitive.clone()),
PaintOperation::StartLayer(bounds) => self.push_layer(*bounds),
PaintOperation::EndLayer => self.pop_layer(),
}
}
}
pub fn finish(&mut self) {
self.shadows.sort_by_key(|shadow| shadow.order);
self.quads.sort_by_key(|quad| quad.order);
self.paths.sort_by_key(|path| path.order);
self.underlines.sort_by_key(|underline| underline.order);
self.monochrome_sprites
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
self.polychrome_sprites
.sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
self.surfaces.sort_by_key(|surface| surface.order);
}
#[cfg_attr(
all(
any(target_os = "linux", target_os = "freebsd"),
not(any(feature = "x11", feature = "wayland"))
),
allow(dead_code)
)]
pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch<'_>> {
BatchIterator {
shadows: &self.shadows,
shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(),
quads: &self.quads,
quads_start: 0,
quads_iter: self.quads.iter().peekable(),
paths: &self.paths,
paths_start: 0,
paths_iter: self.paths.iter().peekable(),
underlines: &self.underlines,
underlines_start: 0,
underlines_iter: self.underlines.iter().peekable(),
monochrome_sprites: &self.monochrome_sprites,
monochrome_sprites_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
polychrome_sprites: &self.polychrome_sprites,
polychrome_sprites_start: 0,
polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
surfaces: &self.surfaces,
surfaces_start: 0,
surfaces_iter: self.surfaces.iter().peekable(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
#[cfg_attr(
all(
any(target_os = "linux", target_os = "freebsd"),
not(any(feature = "x11", feature = "wayland"))
),
allow(dead_code)
)]
pub(crate) enum PrimitiveKind {
Shadow,
#[default]
Quad,
Path,
Underline,
MonochromeSprite,
PolychromeSprite,
Surface,
}
pub(crate) enum PaintOperation {
Primitive(Primitive),
StartLayer(Bounds<ScaledPixels>),
EndLayer,
}
#[derive(Clone)]
pub(crate) enum Primitive {
Shadow(Shadow),
Quad(Quad),
Path(Path<ScaledPixels>),
Underline(Underline),
MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite),
Surface(PaintSurface),
}
impl Primitive {
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.bounds,
Primitive::Quad(quad) => &quad.bounds,
Primitive::Path(path) => &path.bounds,
Primitive::Underline(underline) => &underline.bounds,
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
Primitive::Surface(surface) => &surface.bounds,
}
}
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.content_mask,
Primitive::Quad(quad) => &quad.content_mask,
Primitive::Path(path) => &path.content_mask,
Primitive::Underline(underline) => &underline.content_mask,
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
Primitive::Surface(surface) => &surface.content_mask,
}
}
}
#[cfg_attr(
all(
any(target_os = "linux", target_os = "freebsd"),
not(any(feature = "x11", feature = "wayland"))
),
allow(dead_code)
)]
struct BatchIterator<'a> {
shadows: &'a [Shadow],
shadows_start: usize,
shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
quads: &'a [Quad],
quads_start: usize,
quads_iter: Peekable<slice::Iter<'a, Quad>>,
paths: &'a [Path<ScaledPixels>],
paths_start: usize,
paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
underlines: &'a [Underline],
underlines_start: usize,
underlines_iter: Peekable<slice::Iter<'a, Underline>>,
monochrome_sprites: &'a [MonochromeSprite],
monochrome_sprites_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
polychrome_sprites: &'a [PolychromeSprite],
polychrome_sprites_start: usize,
polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
surfaces: &'a [PaintSurface],
surfaces_start: usize,
surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
}
impl<'a> Iterator for BatchIterator<'a> {
type Item = PrimitiveBatch<'a>;
fn next(&mut self) -> Option<Self::Item> {
let mut orders_and_kinds = [
(
self.shadows_iter.peek().map(|s| s.order),
PrimitiveKind::Shadow,
),
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
(self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
(
self.underlines_iter.peek().map(|u| u.order),
PrimitiveKind::Underline,
),
(
self.monochrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::MonochromeSprite,
),
(
self.polychrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::PolychromeSprite,
),
(
self.surfaces_iter.peek().map(|s| s.order),
PrimitiveKind::Surface,
),
];
orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
let first = orders_and_kinds[0];
let second = orders_and_kinds[1];
let (batch_kind, max_order_and_kind) = if first.0.is_some() {
(first.1, (second.0.unwrap_or(u32::MAX), second.1))
} else {
return None;
};
match batch_kind {
PrimitiveKind::Shadow => {
let shadows_start = self.shadows_start;
let mut shadows_end = shadows_start + 1;
self.shadows_iter.next();
while self
.shadows_iter
.next_if(|shadow| (shadow.order, batch_kind) < max_order_and_kind)
.is_some()
{
shadows_end += 1;
}
self.shadows_start = shadows_end;
Some(PrimitiveBatch::Shadows(
&self.shadows[shadows_start..shadows_end],
))
}
PrimitiveKind::Quad => {
let quads_start = self.quads_start;
let mut quads_end = quads_start + 1;
self.quads_iter.next();
while self
.quads_iter
.next_if(|quad| (quad.order, batch_kind) < max_order_and_kind)
.is_some()
{
quads_end += 1;
}
self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
}
PrimitiveKind::Path => {
let paths_start = self.paths_start;
let mut paths_end = paths_start + 1;
self.paths_iter.next();
while self
.paths_iter
.next_if(|path| (path.order, batch_kind) < max_order_and_kind)
.is_some()
{
paths_end += 1;
}
self.paths_start = paths_end;
Some(PrimitiveBatch::Paths(&self.paths[paths_start..paths_end]))
}
PrimitiveKind::Underline => {
let underlines_start = self.underlines_start;
let mut underlines_end = underlines_start + 1;
self.underlines_iter.next();
while self
.underlines_iter
.next_if(|underline| (underline.order, batch_kind) < max_order_and_kind)
.is_some()
{
underlines_end += 1;
}
self.underlines_start = underlines_end;
Some(PrimitiveBatch::Underlines(
&self.underlines[underlines_start..underlines_end],
))
}
PrimitiveKind::MonochromeSprite => {
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.monochrome_sprites_start;
let mut sprites_end = sprites_start + 1;
self.monochrome_sprites_iter.next();
while self
.monochrome_sprites_iter
.next_if(|sprite| {
(sprite.order, batch_kind) < max_order_and_kind
&& sprite.tile.texture_id == texture_id
})
.is_some()
{
sprites_end += 1;
}
self.monochrome_sprites_start = sprites_end;
Some(PrimitiveBatch::MonochromeSprites {
texture_id,
sprites: &self.monochrome_sprites[sprites_start..sprites_end],
})
}
PrimitiveKind::PolychromeSprite => {
let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.polychrome_sprites_start;
let mut sprites_end = self.polychrome_sprites_start + 1;
self.polychrome_sprites_iter.next();
while self
.polychrome_sprites_iter
.next_if(|sprite| {
(sprite.order, batch_kind) < max_order_and_kind
&& sprite.tile.texture_id == texture_id
})
.is_some()
{
sprites_end += 1;
}
self.polychrome_sprites_start = sprites_end;
Some(PrimitiveBatch::PolychromeSprites {
texture_id,
sprites: &self.polychrome_sprites[sprites_start..sprites_end],
})
}
PrimitiveKind::Surface => {
let surfaces_start = self.surfaces_start;
let mut surfaces_end = surfaces_start + 1;
self.surfaces_iter.next();
while self
.surfaces_iter
.next_if(|surface| (surface.order, batch_kind) < max_order_and_kind)
.is_some()
{
surfaces_end += 1;
}
self.surfaces_start = surfaces_end;
Some(PrimitiveBatch::Surfaces(
&self.surfaces[surfaces_start..surfaces_end],
))
}
}
}
}
#[derive(Debug)]
#[cfg_attr(
all(
any(target_os = "linux", target_os = "freebsd"),
not(any(feature = "x11", feature = "wayland"))
),
allow(dead_code)
)]
pub(crate) enum PrimitiveBatch<'a> {
Shadows(&'a [Shadow]),
Quads(&'a [Quad]),
Paths(&'a [Path<ScaledPixels>]),
Underlines(&'a [Underline]),
MonochromeSprites {
texture_id: AtlasTextureId,
sprites: &'a [MonochromeSprite],
},
PolychromeSprites {
texture_id: AtlasTextureId,
sprites: &'a [PolychromeSprite],
},
Surfaces(&'a [PaintSurface]),
}
#[derive(Default, Debug, Clone)]
#[repr(C)]
pub(crate) struct Quad {
pub order: DrawOrder,
pub border_style: BorderStyle,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub background: Background,
pub border_color: Hsla,
pub corner_radii: Corners<ScaledPixels>,
pub border_widths: Edges<ScaledPixels>,
}
impl From<Quad> for Primitive {
fn from(quad: Quad) -> Self {
Primitive::Quad(quad)
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub(crate) struct Underline {
pub order: DrawOrder,
pub pad: u32, // align to 8 bytes
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub thickness: ScaledPixels,
pub wavy: bool,
}
impl From<Underline> for Primitive {
fn from(underline: Underline) -> Self {
Primitive::Underline(underline)
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub(crate) struct Shadow {
pub order: DrawOrder,
pub blur_radius: ScaledPixels,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
}
impl From<Shadow> for Primitive {
fn from(shadow: Shadow) -> Self {
Primitive::Shadow(shadow)
}
}
/// The style of a border.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub enum BorderStyle {
/// A solid border.
#[default]
Solid = 0,
/// A dashed border.
Dashed = 1,
}
/// A data type representing a 2 dimensional transformation that can be applied to an element.
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct TransformationMatrix {
/// 2x2 matrix containing rotation and scale,
/// stored row-major
pub rotation_scale: [[f32; 2]; 2],
/// translation vector
pub translation: [f32; 2],
}
impl Eq for TransformationMatrix {}
impl TransformationMatrix {
/// The unit matrix, has no effect.
pub fn unit() -> Self {
Self {
rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
translation: [0.0, 0.0],
}
}
/// Move the origin by a given point
pub fn translate(mut self, point: Point<ScaledPixels>) -> Self {
self.compose(Self {
rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
translation: [point.x.0, point.y.0],
})
}
/// Clockwise rotation in radians around the origin
pub fn rotate(self, angle: Radians) -> Self {
self.compose(Self {
rotation_scale: [
[angle.0.cos(), -angle.0.sin()],
[angle.0.sin(), angle.0.cos()],
],
translation: [0.0, 0.0],
})
}
/// Scale around the origin
pub fn scale(self, size: Size<f32>) -> Self {
self.compose(Self {
rotation_scale: [[size.width, 0.0], [0.0, size.height]],
translation: [0.0, 0.0],
})
}
/// Perform matrix multiplication with another transformation
/// to produce a new transformation that is the result of
/// applying both transformations: first, `other`, then `self`.
#[inline]
pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
if other == Self::unit() {
return self;
}
// Perform matrix multiplication
TransformationMatrix {
rotation_scale: [
[
self.rotation_scale[0][0] * other.rotation_scale[0][0]
+ self.rotation_scale[0][1] * other.rotation_scale[1][0],
self.rotation_scale[0][0] * other.rotation_scale[0][1]
+ self.rotation_scale[0][1] * other.rotation_scale[1][1],
],
[
self.rotation_scale[1][0] * other.rotation_scale[0][0]
+ self.rotation_scale[1][1] * other.rotation_scale[1][0],
self.rotation_scale[1][0] * other.rotation_scale[0][1]
+ self.rotation_scale[1][1] * other.rotation_scale[1][1],
],
],
translation: [
self.translation[0]
+ self.rotation_scale[0][0] * other.translation[0]
+ self.rotation_scale[0][1] * other.translation[1],
self.translation[1]
+ self.rotation_scale[1][0] * other.translation[0]
+ self.rotation_scale[1][1] * other.translation[1],
],
}
}
/// Apply transformation to a point, mainly useful for debugging
pub fn apply(&self, point: Point<Pixels>) -> Point<Pixels> {
let input = [point.x.0, point.y.0];
let mut output = self.translation;
for (i, output_cell) in output.iter_mut().enumerate() {
for (k, input_cell) in input.iter().enumerate() {
*output_cell += self.rotation_scale[i][k] * *input_cell;
}
}
Point::new(output[0].into(), output[1].into())
}
}
impl Default for TransformationMatrix {
fn default() -> Self {
Self::unit()
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub(crate) struct MonochromeSprite {
pub order: DrawOrder,
pub pad: u32, // align to 8 bytes
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub color: Hsla,
pub tile: AtlasTile,
pub transformation: TransformationMatrix,
}
impl From<MonochromeSprite> for Primitive {
fn from(sprite: MonochromeSprite) -> Self {
Primitive::MonochromeSprite(sprite)
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub(crate) struct PolychromeSprite {
pub order: DrawOrder,
pub pad: u32, // align to 8 bytes
pub grayscale: bool,
pub opacity: f32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub tile: AtlasTile,
}
impl From<PolychromeSprite> for Primitive {
fn from(sprite: PolychromeSprite) -> Self {
Primitive::PolychromeSprite(sprite)
}
}
#[derive(Clone, Debug)]
pub(crate) struct PaintSurface {
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
#[cfg(target_os = "macos")]
pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
}
impl From<PaintSurface> for Primitive {
fn from(surface: PaintSurface) -> Self {
Primitive::Surface(surface)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct PathId(pub(crate) usize);
/// A line made up of a series of vertices and control points.
#[derive(Clone, Debug)]
pub struct Path<P: Clone + Debug + Default + PartialEq> {
pub(crate) id: PathId,
pub(crate) order: DrawOrder,
pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>,
pub(crate) color: Background,
start: Point<P>,
current: Point<P>,
contour_count: usize,
}
impl Path<Pixels> {
/// Create a new path with the given starting point.
pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
order: DrawOrder::default(),
vertices: Vec::new(),
start,
current: start,
bounds: Bounds {
origin: start,
size: Default::default(),
},
content_mask: Default::default(),
color: Default::default(),
contour_count: 0,
}
}
/// Scale this path by the given factor.
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
Path {
id: self.id,
order: self.order,
bounds: self.bounds.scale(factor),
content_mask: self.content_mask.scale(factor),
vertices: self
.vertices
.iter()
.map(|vertex| vertex.scale(factor))
.collect(),
start: self.start.map(|start| start.scale(factor)),
current: self.current.scale(factor),
contour_count: self.contour_count,
color: self.color,
}
}
/// Move the start, current point to the given point.
pub fn move_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
self.start = to;
self.current = to;
}
/// Draw a straight line from the current point to the given point.
pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.current = to;
}
/// Draw a curve from the current point to the given point, using the given control point.
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
}
self.push_triangle(
(self.current, ctrl, to),
(point(0., 0.), point(0.5, 0.), point(1., 1.)),
);
self.current = to;
}
/// Push a triangle to the Path.
pub fn push_triangle(
&mut self,
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>),
) {
self.bounds = self
.bounds
.union(&Bounds {
origin: xy.0,
size: Default::default(),
})
.union(&Bounds {
origin: xy.1,
size: Default::default(),
})
.union(&Bounds {
origin: xy.2,
size: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.0,
st_position: st.0,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.1,
st_position: st.1,
content_mask: Default::default(),
});
self.vertices.push(PathVertex {
xy_position: xy.2,
st_position: st.2,
content_mask: Default::default(),
});
}
}
impl From<Path<ScaledPixels>> for Primitive {
fn from(path: Path<ScaledPixels>) -> Self {
Primitive::Path(path)
}
}
#[derive(Clone, Debug)]
#[repr(C)]
pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> {
pub(crate) xy_position: Point<P>,
pub(crate) st_position: Point<f32>,
pub(crate) content_mask: ContentMask<P>,
}
impl PathVertex<Pixels> {
pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
PathVertex {
xy_position: self.xy_position.scale(factor),
st_position: self.st_position,
content_mask: self.content_mask.scale(factor),
}
}
}