Checkpoint: beziers

This commit is contained in:
Antonio Scandurra 2023-10-10 13:01:35 +02:00
parent fe60f264c4
commit a4afb72535
6 changed files with 397 additions and 97 deletions

View file

@ -50,6 +50,8 @@ fn generate_shader_bindings() -> PathBuf {
"ContentMask".into(),
"Uniforms".into(),
"AtlasTile".into(),
"PathRasterizationInputIndex".into(),
"PathVertex_ScaledPixels".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
"QuadInputIndex".into(),
@ -59,6 +61,7 @@ fn generate_shader_bindings() -> PathBuf {
"SpriteInputIndex".into(),
"MonochromeSprite".into(),
"PolychromeSprite".into(),
"PathSprite".into(),
]);
config.no_includes = true;
config.enumeration.prefix_with_name = true;

View file

@ -99,12 +99,24 @@ impl MetalAtlasState {
let texture_descriptor = metal::TextureDescriptor::new();
texture_descriptor.set_width(size.width.into());
texture_descriptor.set_height(size.height.into());
let pixel_format = match kind {
AtlasTextureKind::Monochrome => metal::MTLPixelFormat::A8Unorm,
AtlasTextureKind::Polychrome => metal::MTLPixelFormat::BGRA8Unorm,
AtlasTextureKind::Path => metal::MTLPixelFormat::R16Float,
};
let pixel_format;
let usage;
match kind {
AtlasTextureKind::Monochrome => {
pixel_format = metal::MTLPixelFormat::A8Unorm;
usage = metal::MTLTextureUsage::ShaderRead;
}
AtlasTextureKind::Polychrome => {
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
usage = metal::MTLTextureUsage::ShaderRead;
}
AtlasTextureKind::Path => {
pixel_format = metal::MTLPixelFormat::R16Float;
usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
}
}
texture_descriptor.set_pixel_format(pixel_format);
texture_descriptor.set_usage(usage);
let metal_texture = self.device.new_texture(&texture_descriptor);
let textures = match kind {

View file

@ -1,7 +1,7 @@
use crate::{
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
ScaledPixels, Scene, Shadow, Size, Underline,
Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
Quad, ScaledPixels, Scene, Shadow, Size, Underline,
};
use cocoa::{
base::{NO, YES},
@ -11,6 +11,7 @@ use cocoa::{
use collections::HashMap;
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
use objc::{self, msg_send, sel, sel_impl};
use smallvec::SmallVec;
use std::{ffi::c_void, mem, ptr, sync::Arc};
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
@ -19,6 +20,8 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio
pub(crate) struct MetalRenderer {
layer: metal::MetalLayer,
command_queue: CommandQueue,
paths_rasterization_pipeline_state: metal::RenderPipelineState,
path_sprites_pipeline_state: metal::RenderPipelineState,
shadows_pipeline_state: metal::RenderPipelineState,
quads_pipeline_state: metal::RenderPipelineState,
underlines_pipeline_state: metal::RenderPipelineState,
@ -31,8 +34,6 @@ pub(crate) struct MetalRenderer {
impl MetalRenderer {
pub fn new(is_opaque: bool) -> Self {
const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
let device: metal::Device = if let Some(device) = metal::Device::system_default() {
device
} else {
@ -42,7 +43,7 @@ impl MetalRenderer {
let layer = metal::MetalLayer::new();
layer.set_device(&device);
layer.set_pixel_format(PIXEL_FORMAT);
layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
layer.set_presents_with_transaction(true);
layer.set_opaque(is_opaque);
unsafe {
@ -86,13 +87,29 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged,
);
let paths_rasterization_pipeline_state = build_pipeline_state(
&device,
&library,
"paths_rasterization",
"path_rasterization_vertex",
"path_rasterization_fragment",
MTLPixelFormat::R16Float,
);
let path_sprites_pipeline_state = build_pipeline_state(
&device,
&library,
"path_sprites",
"path_sprite_vertex",
"path_sprite_fragment",
MTLPixelFormat::BGRA8Unorm,
);
let shadows_pipeline_state = build_pipeline_state(
&device,
&library,
"shadows",
"shadow_vertex",
"shadow_fragment",
PIXEL_FORMAT,
MTLPixelFormat::BGRA8Unorm,
);
let quads_pipeline_state = build_pipeline_state(
&device,
@ -100,7 +117,7 @@ impl MetalRenderer {
"quads",
"quad_vertex",
"quad_fragment",
PIXEL_FORMAT,
MTLPixelFormat::BGRA8Unorm,
);
let underlines_pipeline_state = build_pipeline_state(
&device,
@ -108,7 +125,7 @@ impl MetalRenderer {
"underlines",
"underline_vertex",
"underline_fragment",
PIXEL_FORMAT,
MTLPixelFormat::BGRA8Unorm,
);
let monochrome_sprites_pipeline_state = build_pipeline_state(
&device,
@ -116,7 +133,7 @@ impl MetalRenderer {
"monochrome_sprites",
"monochrome_sprite_vertex",
"monochrome_sprite_fragment",
PIXEL_FORMAT,
MTLPixelFormat::BGRA8Unorm,
);
let polychrome_sprites_pipeline_state = build_pipeline_state(
&device,
@ -124,7 +141,7 @@ impl MetalRenderer {
"polychrome_sprites",
"polychrome_sprite_vertex",
"polychrome_sprite_fragment",
PIXEL_FORMAT,
MTLPixelFormat::BGRA8Unorm,
);
let command_queue = device.new_command_queue();
@ -133,6 +150,8 @@ impl MetalRenderer {
Self {
layer,
command_queue,
paths_rasterization_pipeline_state,
path_sprites_pipeline_state,
shadows_pipeline_state,
quads_pipeline_state,
underlines_pipeline_state,
@ -172,7 +191,7 @@ impl MetalRenderer {
let command_buffer = command_queue.new_command_buffer();
let mut instance_offset = 0;
// let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer);
let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer);
let render_pass_descriptor = metal::RenderPassDescriptor::new();
let color_attachment = render_pass_descriptor
@ -208,8 +227,14 @@ impl MetalRenderer {
PrimitiveBatch::Quads(quads) => {
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
}
PrimitiveBatch::Paths(_paths) => {
// self.draw_paths(paths, &mut instance_offset, viewport_size, command_encoder);
PrimitiveBatch::Paths(paths) => {
self.draw_paths(
paths,
&path_tiles,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
PrimitiveBatch::Underlines(underlines) => {
self.draw_underlines(
@ -258,19 +283,20 @@ impl MetalRenderer {
drawable.present();
}
#[allow(dead_code)]
fn rasterize_paths(
&mut self,
paths: &[Path<ScaledPixels>],
_offset: &mut usize,
_command_buffer: &metal::CommandBufferRef,
offset: &mut usize,
command_buffer: &metal::CommandBufferRef,
) -> HashMap<PathId, AtlasTile> {
let mut tiles = HashMap::default();
let mut vertices_by_texture_id = HashMap::default();
for path in paths {
let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
let tile = self
.sprite_atlas
.allocate(path.bounds.size.map(Into::into), AtlasTextureKind::Path);
.allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
vertices_by_texture_id
.entry(tile.texture_id)
.or_insert(Vec::new())
@ -279,68 +305,65 @@ impl MetalRenderer {
+ tile.bounds.origin.map(Into::into),
st_position: vertex.st_position,
content_mask: ContentMask {
bounds: Bounds {
origin: vertex.xy_position - path.bounds.origin
+ tile.bounds.origin.map(Into::into),
size: vertex.content_mask.bounds.size,
},
bounds: tile.bounds.map(Into::into),
},
}));
tiles.insert(path.id, tile);
}
for (_texture_id, _vertices) in vertices_by_texture_id {
todo!();
// align_offset(offset);
// let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
// assert!(
// next_offset <= INSTANCE_BUFFER_SIZE,
// "instance buffer exhausted"
// );
for (texture_id, vertices) in vertices_by_texture_id {
align_offset(offset);
let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
// let render_pass_descriptor = metal::RenderPassDescriptor::new();
// let color_attachment = render_pass_descriptor
// .color_attachments()
// .object_at(0)
// .unwrap();
let render_pass_descriptor = metal::RenderPassDescriptor::new();
let color_attachment = render_pass_descriptor
.color_attachments()
.object_at(0)
.unwrap();
// let texture = self.sprite_atlas.metal_texture(texture_id);
// color_attachment.set_texture(Some(&texture));
// color_attachment.set_load_action(metal::MTLLoadAction::Clear);
// color_attachment.set_store_action(metal::MTLStoreAction::Store);
// color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
// let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
// command_encoder.set_render_pipeline_state(&self.path_atlas_pipeline_state);
// command_encoder.set_vertex_buffer(
// shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexVertices as u64,
// Some(&self.instances),
// *offset as u64,
// );
// command_encoder.set_vertex_bytes(
// shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexAtlasSize
// as u64,
// mem::size_of::<shaders::vector_float2>() as u64,
// [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
// as *const c_void,
// );
let texture = self.sprite_atlas.metal_texture(texture_id);
color_attachment.set_texture(Some(&texture));
color_attachment.set_load_action(metal::MTLLoadAction::Clear);
color_attachment.set_store_action(metal::MTLStoreAction::Store);
color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
command_encoder.set_vertex_buffer(
PathRasterizationInputIndex::Vertices as u64,
Some(&self.instances),
*offset as u64,
);
let texture_size = Size {
width: DevicePixels::from(texture.width()),
height: DevicePixels::from(texture.height()),
};
command_encoder.set_vertex_bytes(
PathRasterizationInputIndex::AtlasTextureSize as u64,
mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _,
);
// let buffer_contents = unsafe {
// (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIPathVertex
// };
let vertices_bytes_len = mem::size_of::<PathVertex<ScaledPixels>>() * vertices.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
vertices.as_ptr() as *const u8,
buffer_contents,
vertices_bytes_len,
);
}
// for (ix, vertex) in vertices.iter().enumerate() {
// unsafe {
// *buffer_contents.add(ix) = *vertex;
// }
// }
// command_encoder.draw_primitives(
// metal::MTLPrimitiveType::Triangle,
// 0,
// vertices.len() as u64,
// );
// command_encoder.end_encoding();
// *offset = next_offset;
command_encoder.draw_primitives(
metal::MTLPrimitiveType::Triangle,
0,
vertices.len() as u64,
);
command_encoder.end_encoding();
*offset = next_offset;
}
tiles
@ -462,6 +485,112 @@ impl MetalRenderer {
*offset = next_offset;
}
fn draw_paths(
&mut self,
paths: &[Path<ScaledPixels>],
tiles_by_path_id: &HashMap<PathId, AtlasTile>,
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if paths.is_empty() {
return;
}
command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
command_encoder.set_vertex_buffer(
SpriteInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_bytes(
SpriteInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let mut prev_texture_id = None;
let mut sprites = SmallVec::<[_; 1]>::new();
let mut paths_and_tiles = paths
.into_iter()
.map(|path| (path, tiles_by_path_id.get(&path.id).unwrap()))
.peekable();
loop {
if let Some((path, tile)) = paths_and_tiles.peek() {
if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
prev_texture_id = Some(tile.texture_id);
sprites.push(PathSprite {
bounds: Bounds {
origin: path.bounds.origin.map(|p| p.floor()),
size: tile.bounds.size.map(Into::into),
},
color: path.color,
tile: (*tile).clone(),
});
paths_and_tiles.next();
continue;
}
}
if sprites.is_empty() {
break;
} else {
align_offset(offset);
let texture_id = prev_texture_id.take().unwrap();
let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
let texture_size = size(
DevicePixels(texture.width() as i32),
DevicePixels(texture.height() as i32),
);
command_encoder.set_vertex_buffer(
SpriteInputIndex::Sprites as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
SpriteInputIndex::AtlasTextureSize as u64,
mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _,
);
command_encoder.set_fragment_buffer(
SpriteInputIndex::Sprites as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder
.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
let buffer_contents =
unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
sprites.as_ptr() as *const u8,
buffer_contents,
sprite_bytes_len,
);
}
let next_offset = *offset + sprite_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
sprites.len() as u64,
);
*offset = next_offset;
sprites.clear();
}
}
}
fn draw_underlines(
&mut self,
underlines: &[Underline],
@ -734,3 +863,17 @@ enum SpriteInputIndex {
AtlasTextureSize = 3,
AtlasTexture = 4,
}
#[repr(C)]
enum PathRasterizationInputIndex {
Vertices = 0,
AtlasTextureSize = 1,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct PathSprite {
pub bounds: Bounds<ScaledPixels>,
pub color: Hsla,
pub tile: AtlasTile,
}

View file

@ -336,6 +336,94 @@ fragment float4 polychrome_sprite_fragment(
return color;
}
struct PathRasterizationVertexOutput {
float4 position [[position]];
float2 st_position;
float clip_rect_distance [[clip_distance]][4];
};
struct PathRasterizationFragmentInput {
float4 position [[position]];
float2 st_position;
};
vertex PathRasterizationVertexOutput path_rasterization_vertex(
uint vertex_id [[vertex_id]],
constant PathVertex_ScaledPixels *vertices
[[buffer(PathRasterizationInputIndex_Vertices)]],
constant Size_DevicePixels *atlas_size
[[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
PathVertex_ScaledPixels v = vertices[vertex_id];
float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
float2 viewport_size = float2(atlas_size->width, atlas_size->height);
return PathRasterizationVertexOutput{
float4(vertex_position / viewport_size * float2(2., -2.) +
float2(-1., 1.),
0., 1.),
float2(v.st_position.x, v.st_position.y),
{v.xy_position.x - v.content_mask.bounds.origin.x,
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
v.xy_position.x,
v.xy_position.y - v.content_mask.bounds.origin.y,
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
v.xy_position.y}};
}
fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
[[stage_in]]) {
float2 dx = dfdx(input.st_position);
float2 dy = dfdy(input.st_position);
float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
(2. * input.st_position.x) * dy.x - dy.y);
float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
float distance = f / length(gradient);
float alpha = saturate(0.5 - distance);
return float4(alpha, 0., 0., 1.);
}
struct PathSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
float4 color [[flat]];
uint sprite_id [[flat]];
};
vertex PathSpriteVertexOutput path_sprite_vertex(
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
constant Size_DevicePixels *viewport_size
[[buffer(SpriteInputIndex_ViewportSize)]],
constant Size_DevicePixels *atlas_size
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
PathSprite sprite = sprites[sprite_id];
// Don't apply content mask because it was already accounted for when
// rasterizing the path.
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
float4 color = hsla_to_rgba(sprite.color);
return PathSpriteVertexOutput{device_position, tile_position, color,
sprite_id};
}
fragment float4 path_sprite_fragment(
PathSpriteVertexOutput input [[stage_in]],
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
PathSprite sprite = sprites[input.sprite_id];
constexpr sampler atlas_texture_sampler(mag_filter::linear,
min_filter::linear);
float4 sample =
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
float mask = 1. - abs(1. - fmod(sample.r, 2.));
float4 color = input.color;
color.a *= mask;
return color;
}
float4 hsla_to_rgba(Hsla hsla) {
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
float s = hsla.s;

View file

@ -8,7 +8,9 @@ use plane_split::{BspSplitter, Polygon as BspPolygon};
use std::{fmt::Debug, iter::Peekable, mem, slice};
// Exported to metal
pub type PointF = Point<f32>;
pub(crate) type PointF = Point<f32>;
#[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
pub type LayerId = u32;
@ -62,6 +64,12 @@ impl SceneBuilder {
.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
}
for (ix, path) in self.paths.iter().enumerate() {
let z = layer_z_values[path.order as LayerId as usize];
self.splitter
.add(path.bounds.to_bsp_polygon(z, (PrimitiveKind::Path, ix)));
}
for (ix, underline) in self.underlines.iter().enumerate() {
let z = layer_z_values[underline.order as LayerId as usize];
self.splitter.add(
@ -131,6 +139,16 @@ impl SceneBuilder {
}
pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
let primitive = primitive.into();
let clipped_bounds = primitive
.bounds()
.intersect(&primitive.content_mask().bounds);
if clipped_bounds.size.width <= ScaledPixels(0.)
|| clipped_bounds.size.height <= ScaledPixels(0.)
{
return;
}
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
*layer_id
} else {
@ -139,7 +157,6 @@ impl SceneBuilder {
next_id
};
let primitive = primitive.into();
match primitive {
Primitive::Shadow(mut shadow) => {
shadow.order = layer_id;
@ -240,6 +257,7 @@ impl<'a> Iterator for BatchIterator<'a> {
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,
@ -382,6 +400,30 @@ pub enum Primitive {
PolychromeSprite(PolychromeSprite),
}
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,
}
}
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,
}
}
}
#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> {
Shadows(&'a [Shadow]),
@ -557,20 +599,29 @@ pub struct Path<P: Clone + Debug> {
pub(crate) id: PathId,
order: u32,
pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>,
start: Option<Point<P>>,
pub(crate) color: Hsla,
start: Point<P>,
current: Point<P>,
contour_count: usize,
}
impl Path<Pixels> {
pub fn new() -> Self {
pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
order: 0,
vertices: Vec::new(),
start: Default::default(),
current: Default::default(),
bounds: Default::default(),
start,
current: start,
bounds: Bounds {
origin: start,
size: Default::default(),
},
content_mask: Default::default(),
color: Default::default(),
contour_count: 0,
}
}
@ -579,6 +630,7 @@ impl Path<Pixels> {
id: self.id,
order: self.order,
bounds: self.bounds.scale(factor),
content_mask: self.content_mask.scale(factor),
vertices: self
.vertices
.iter()
@ -586,27 +638,36 @@ impl Path<Pixels> {
.collect(),
start: self.start.map(|start| start.scale(factor)),
current: self.current.scale(factor),
contour_count: self.contour_count,
color: self.color,
}
}
pub fn line_to(&mut self, to: Point<Pixels>) {
if let Some(start) = self.start {
self.contour_count += 1;
if self.contour_count > 1 {
self.push_triangle(
(start, self.current, to),
(self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)),
);
} else {
self.start = Some(to);
}
self.current = to;
}
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.line_to(to);
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;
}
fn push_triangle(
@ -614,12 +675,6 @@ impl Path<Pixels> {
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>),
) {
if self.vertices.is_empty() {
self.bounds = Bounds {
origin: xy.0,
size: Default::default(),
};
}
self.bounds = self
.bounds
.union(&Bounds {

View file

@ -363,12 +363,11 @@ impl<'a, 'w> WindowContext<'a, 'w> {
);
}
pub fn paint_path(&mut self, mut path: Path<Pixels>) {
pub fn paint_path(&mut self, mut path: Path<Pixels>, color: impl Into<Hsla>) {
let scale_factor = self.scale_factor();
let content_mask = self.content_mask();
for vertex in &mut path.vertices {
vertex.content_mask = content_mask.clone();
}
path.content_mask = content_mask;
path.color = color.into();
let window = &mut *self.window;
window
.scene_builder