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

View file

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

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels, point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
ScaledPixels, Scene, Shadow, Size, Underline, Quad, ScaledPixels, Scene, Shadow, Size, Underline,
}; };
use cocoa::{ use cocoa::{
base::{NO, YES}, base::{NO, YES},
@ -11,6 +11,7 @@ use cocoa::{
use collections::HashMap; use collections::HashMap;
use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
use objc::{self, msg_send, sel, sel_impl}; use objc::{self, msg_send, sel, sel_impl};
use smallvec::SmallVec;
use std::{ffi::c_void, mem, ptr, sync::Arc}; use std::{ffi::c_void, mem, ptr, sync::Arc};
const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); 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 { pub(crate) struct MetalRenderer {
layer: metal::MetalLayer, layer: metal::MetalLayer,
command_queue: CommandQueue, command_queue: CommandQueue,
paths_rasterization_pipeline_state: metal::RenderPipelineState,
path_sprites_pipeline_state: metal::RenderPipelineState,
shadows_pipeline_state: metal::RenderPipelineState, shadows_pipeline_state: metal::RenderPipelineState,
quads_pipeline_state: metal::RenderPipelineState, quads_pipeline_state: metal::RenderPipelineState,
underlines_pipeline_state: metal::RenderPipelineState, underlines_pipeline_state: metal::RenderPipelineState,
@ -31,8 +34,6 @@ pub(crate) struct MetalRenderer {
impl MetalRenderer { impl MetalRenderer {
pub fn new(is_opaque: bool) -> Self { 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() { let device: metal::Device = if let Some(device) = metal::Device::system_default() {
device device
} else { } else {
@ -42,7 +43,7 @@ impl MetalRenderer {
let layer = metal::MetalLayer::new(); let layer = metal::MetalLayer::new();
layer.set_device(&device); layer.set_device(&device);
layer.set_pixel_format(PIXEL_FORMAT); layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
layer.set_presents_with_transaction(true); layer.set_presents_with_transaction(true);
layer.set_opaque(is_opaque); layer.set_opaque(is_opaque);
unsafe { unsafe {
@ -86,13 +87,29 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged, 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( let shadows_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
"shadows", "shadows",
"shadow_vertex", "shadow_vertex",
"shadow_fragment", "shadow_fragment",
PIXEL_FORMAT, MTLPixelFormat::BGRA8Unorm,
); );
let quads_pipeline_state = build_pipeline_state( let quads_pipeline_state = build_pipeline_state(
&device, &device,
@ -100,7 +117,7 @@ impl MetalRenderer {
"quads", "quads",
"quad_vertex", "quad_vertex",
"quad_fragment", "quad_fragment",
PIXEL_FORMAT, MTLPixelFormat::BGRA8Unorm,
); );
let underlines_pipeline_state = build_pipeline_state( let underlines_pipeline_state = build_pipeline_state(
&device, &device,
@ -108,7 +125,7 @@ impl MetalRenderer {
"underlines", "underlines",
"underline_vertex", "underline_vertex",
"underline_fragment", "underline_fragment",
PIXEL_FORMAT, MTLPixelFormat::BGRA8Unorm,
); );
let monochrome_sprites_pipeline_state = build_pipeline_state( let monochrome_sprites_pipeline_state = build_pipeline_state(
&device, &device,
@ -116,7 +133,7 @@ impl MetalRenderer {
"monochrome_sprites", "monochrome_sprites",
"monochrome_sprite_vertex", "monochrome_sprite_vertex",
"monochrome_sprite_fragment", "monochrome_sprite_fragment",
PIXEL_FORMAT, MTLPixelFormat::BGRA8Unorm,
); );
let polychrome_sprites_pipeline_state = build_pipeline_state( let polychrome_sprites_pipeline_state = build_pipeline_state(
&device, &device,
@ -124,7 +141,7 @@ impl MetalRenderer {
"polychrome_sprites", "polychrome_sprites",
"polychrome_sprite_vertex", "polychrome_sprite_vertex",
"polychrome_sprite_fragment", "polychrome_sprite_fragment",
PIXEL_FORMAT, MTLPixelFormat::BGRA8Unorm,
); );
let command_queue = device.new_command_queue(); let command_queue = device.new_command_queue();
@ -133,6 +150,8 @@ impl MetalRenderer {
Self { Self {
layer, layer,
command_queue, command_queue,
paths_rasterization_pipeline_state,
path_sprites_pipeline_state,
shadows_pipeline_state, shadows_pipeline_state,
quads_pipeline_state, quads_pipeline_state,
underlines_pipeline_state, underlines_pipeline_state,
@ -172,7 +191,7 @@ impl MetalRenderer {
let command_buffer = command_queue.new_command_buffer(); let command_buffer = command_queue.new_command_buffer();
let mut instance_offset = 0; 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 render_pass_descriptor = metal::RenderPassDescriptor::new();
let color_attachment = render_pass_descriptor let color_attachment = render_pass_descriptor
@ -208,8 +227,14 @@ impl MetalRenderer {
PrimitiveBatch::Quads(quads) => { PrimitiveBatch::Quads(quads) => {
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder); self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
} }
PrimitiveBatch::Paths(_paths) => { PrimitiveBatch::Paths(paths) => {
// self.draw_paths(paths, &mut instance_offset, viewport_size, command_encoder); self.draw_paths(
paths,
&path_tiles,
&mut instance_offset,
viewport_size,
command_encoder,
);
} }
PrimitiveBatch::Underlines(underlines) => { PrimitiveBatch::Underlines(underlines) => {
self.draw_underlines( self.draw_underlines(
@ -258,19 +283,20 @@ impl MetalRenderer {
drawable.present(); drawable.present();
} }
#[allow(dead_code)]
fn rasterize_paths( fn rasterize_paths(
&mut self, &mut self,
paths: &[Path<ScaledPixels>], paths: &[Path<ScaledPixels>],
_offset: &mut usize, offset: &mut usize,
_command_buffer: &metal::CommandBufferRef, command_buffer: &metal::CommandBufferRef,
) -> HashMap<PathId, AtlasTile> { ) -> HashMap<PathId, AtlasTile> {
let mut tiles = HashMap::default(); let mut tiles = HashMap::default();
let mut vertices_by_texture_id = HashMap::default(); let mut vertices_by_texture_id = HashMap::default();
for path in paths { for path in paths {
let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
let tile = self let tile = self
.sprite_atlas .sprite_atlas
.allocate(path.bounds.size.map(Into::into), AtlasTextureKind::Path); .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
vertices_by_texture_id vertices_by_texture_id
.entry(tile.texture_id) .entry(tile.texture_id)
.or_insert(Vec::new()) .or_insert(Vec::new())
@ -279,68 +305,65 @@ impl MetalRenderer {
+ tile.bounds.origin.map(Into::into), + tile.bounds.origin.map(Into::into),
st_position: vertex.st_position, st_position: vertex.st_position,
content_mask: ContentMask { content_mask: ContentMask {
bounds: Bounds { bounds: tile.bounds.map(Into::into),
origin: vertex.xy_position - path.bounds.origin
+ tile.bounds.origin.map(Into::into),
size: vertex.content_mask.bounds.size,
},
}, },
})); }));
tiles.insert(path.id, tile); tiles.insert(path.id, tile);
} }
for (_texture_id, _vertices) in vertices_by_texture_id { for (texture_id, vertices) in vertices_by_texture_id {
todo!(); align_offset(offset);
// align_offset(offset); let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
// let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>(); assert!(
// assert!( next_offset <= INSTANCE_BUFFER_SIZE,
// next_offset <= INSTANCE_BUFFER_SIZE, "instance buffer exhausted"
// "instance buffer exhausted" );
// );
// let render_pass_descriptor = metal::RenderPassDescriptor::new(); let render_pass_descriptor = metal::RenderPassDescriptor::new();
// let color_attachment = render_pass_descriptor let color_attachment = render_pass_descriptor
// .color_attachments() .color_attachments()
// .object_at(0) .object_at(0)
// .unwrap(); .unwrap();
// let texture = self.sprite_atlas.metal_texture(texture_id); let texture = self.sprite_atlas.metal_texture(texture_id);
// color_attachment.set_texture(Some(&texture)); color_attachment.set_texture(Some(&texture));
// color_attachment.set_load_action(metal::MTLLoadAction::Clear); color_attachment.set_load_action(metal::MTLLoadAction::Clear);
// color_attachment.set_store_action(metal::MTLStoreAction::Store); color_attachment.set_store_action(metal::MTLStoreAction::Store);
// color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.)); color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
// let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor); 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_render_pipeline_state(&self.paths_rasterization_pipeline_state);
// command_encoder.set_vertex_buffer( command_encoder.set_vertex_buffer(
// shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexVertices as u64, PathRasterizationInputIndex::Vertices as u64,
// Some(&self.instances), Some(&self.instances),
// *offset as u64, *offset as u64,
// ); );
// command_encoder.set_vertex_bytes( let texture_size = Size {
// shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexAtlasSize width: DevicePixels::from(texture.width()),
// as u64, height: DevicePixels::from(texture.height()),
// mem::size_of::<shaders::vector_float2>() as u64, };
// [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr() command_encoder.set_vertex_bytes(
// as *const c_void, PathRasterizationInputIndex::AtlasTextureSize as u64,
// ); mem::size_of_val(&texture_size) as u64,
&texture_size as *const Size<DevicePixels> as *const _,
);
// let buffer_contents = unsafe { let vertices_bytes_len = mem::size_of::<PathVertex<ScaledPixels>>() * vertices.len();
// (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIPathVertex 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() { command_encoder.draw_primitives(
// unsafe { metal::MTLPrimitiveType::Triangle,
// *buffer_contents.add(ix) = *vertex; 0,
// } vertices.len() as u64,
// } );
command_encoder.end_encoding();
// command_encoder.draw_primitives( *offset = next_offset;
// metal::MTLPrimitiveType::Triangle,
// 0,
// vertices.len() as u64,
// );
// command_encoder.end_encoding();
// *offset = next_offset;
} }
tiles tiles
@ -462,6 +485,112 @@ impl MetalRenderer {
*offset = next_offset; *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( fn draw_underlines(
&mut self, &mut self,
underlines: &[Underline], underlines: &[Underline],
@ -734,3 +863,17 @@ enum SpriteInputIndex {
AtlasTextureSize = 3, AtlasTextureSize = 3,
AtlasTexture = 4, 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; 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) { float4 hsla_to_rgba(Hsla hsla) {
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
float s = hsla.s; 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}; use std::{fmt::Debug, iter::Peekable, mem, slice};
// Exported to metal // 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; pub type LayerId = u32;
@ -62,6 +64,12 @@ impl SceneBuilder {
.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix))); .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() { for (ix, underline) in self.underlines.iter().enumerate() {
let z = layer_z_values[underline.order as LayerId as usize]; let z = layer_z_values[underline.order as LayerId as usize];
self.splitter.add( self.splitter.add(
@ -131,6 +139,16 @@ impl SceneBuilder {
} }
pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) { 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) { let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
*layer_id *layer_id
} else { } else {
@ -139,7 +157,6 @@ impl SceneBuilder {
next_id next_id
}; };
let primitive = primitive.into();
match primitive { match primitive {
Primitive::Shadow(mut shadow) => { Primitive::Shadow(mut shadow) => {
shadow.order = layer_id; shadow.order = layer_id;
@ -240,6 +257,7 @@ impl<'a> Iterator for BatchIterator<'a> {
PrimitiveKind::Shadow, PrimitiveKind::Shadow,
), ),
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad), (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), self.underlines_iter.peek().map(|u| u.order),
PrimitiveKind::Underline, PrimitiveKind::Underline,
@ -382,6 +400,30 @@ pub enum Primitive {
PolychromeSprite(PolychromeSprite), 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)] #[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> { pub(crate) enum PrimitiveBatch<'a> {
Shadows(&'a [Shadow]), Shadows(&'a [Shadow]),
@ -557,20 +599,29 @@ pub struct Path<P: Clone + Debug> {
pub(crate) id: PathId, pub(crate) id: PathId,
order: u32, order: u32,
pub(crate) bounds: Bounds<P>, pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
pub(crate) vertices: Vec<PathVertex<P>>, pub(crate) vertices: Vec<PathVertex<P>>,
start: Option<Point<P>>, pub(crate) color: Hsla,
start: Point<P>,
current: Point<P>, current: Point<P>,
contour_count: usize,
} }
impl Path<Pixels> { impl Path<Pixels> {
pub fn new() -> Self { pub fn new(start: Point<Pixels>) -> Self {
Self { Self {
id: PathId(0), id: PathId(0),
order: 0, order: 0,
vertices: Vec::new(), vertices: Vec::new(),
start: Default::default(), start,
current: Default::default(), current: start,
bounds: Default::default(), 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, id: self.id,
order: self.order, order: self.order,
bounds: self.bounds.scale(factor), bounds: self.bounds.scale(factor),
content_mask: self.content_mask.scale(factor),
vertices: self vertices: self
.vertices .vertices
.iter() .iter()
@ -586,27 +638,36 @@ impl Path<Pixels> {
.collect(), .collect(),
start: self.start.map(|start| start.scale(factor)), start: self.start.map(|start| start.scale(factor)),
current: self.current.scale(factor), current: self.current.scale(factor),
contour_count: self.contour_count,
color: self.color,
} }
} }
pub fn line_to(&mut self, to: Point<Pixels>) { 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( self.push_triangle(
(start, self.current, to), (self.start, self.current, to),
(point(0., 1.), point(0., 1.), point(0., 1.)), (point(0., 1.), point(0., 1.), point(0., 1.)),
); );
} else {
self.start = Some(to);
} }
self.current = to; self.current = to;
} }
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) { 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.push_triangle(
(self.current, ctrl, to), (self.current, ctrl, to),
(point(0., 0.), point(0.5, 0.), point(1., 1.)), (point(0., 0.), point(0.5, 0.), point(1., 1.)),
); );
self.current = to;
} }
fn push_triangle( fn push_triangle(
@ -614,12 +675,6 @@ impl Path<Pixels> {
xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>), xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
st: (Point<f32>, Point<f32>, Point<f32>), st: (Point<f32>, Point<f32>, Point<f32>),
) { ) {
if self.vertices.is_empty() {
self.bounds = Bounds {
origin: xy.0,
size: Default::default(),
};
}
self.bounds = self self.bounds = self
.bounds .bounds
.union(&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 scale_factor = self.scale_factor();
let content_mask = self.content_mask(); let content_mask = self.content_mask();
for vertex in &mut path.vertices { path.content_mask = content_mask;
vertex.content_mask = content_mask.clone(); path.color = color.into();
}
let window = &mut *self.window; let window = &mut *self.window;
window window
.scene_builder .scene_builder