Checkpoint: underlines

This commit is contained in:
Antonio Scandurra 2023-10-06 15:34:37 +02:00
parent 65c7765c07
commit ca6eb5511c
9 changed files with 495 additions and 155 deletions

View file

@ -50,10 +50,12 @@ fn generate_shader_bindings() -> PathBuf {
"ScaledContentMask".into(), "ScaledContentMask".into(),
"Uniforms".into(), "Uniforms".into(),
"AtlasTile".into(), "AtlasTile".into(),
"QuadInputIndex".into(),
"Quad".into(),
"ShadowInputIndex".into(), "ShadowInputIndex".into(),
"Shadow".into(), "Shadow".into(),
"QuadInputIndex".into(),
"Underline".into(),
"UnderlineInputIndex".into(),
"Quad".into(),
"SpriteInputIndex".into(), "SpriteInputIndex".into(),
"MonochromeSprite".into(), "MonochromeSprite".into(),
"PolychromeSprite".into(), "PolychromeSprite".into(),

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite, point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
Quad, Scene, Shadow, Size, PrimitiveBatch, Quad, Scene, Shadow, Size, Underline,
}; };
use cocoa::{ use cocoa::{
base::{NO, YES}, base::{NO, YES},
@ -17,8 +17,9 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio
pub struct MetalRenderer { pub struct MetalRenderer {
layer: metal::MetalLayer, layer: metal::MetalLayer,
command_queue: CommandQueue, command_queue: CommandQueue,
quads_pipeline_state: metal::RenderPipelineState,
shadows_pipeline_state: metal::RenderPipelineState, shadows_pipeline_state: metal::RenderPipelineState,
quads_pipeline_state: metal::RenderPipelineState,
underlines_pipeline_state: metal::RenderPipelineState,
monochrome_sprites_pipeline_state: metal::RenderPipelineState, monochrome_sprites_pipeline_state: metal::RenderPipelineState,
polychrome_sprites_pipeline_state: metal::RenderPipelineState, polychrome_sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer, unit_vertices: metal::Buffer,
@ -83,6 +84,14 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged, MTLResourceOptions::StorageModeManaged,
); );
let shadows_pipeline_state = build_pipeline_state(
&device,
&library,
"shadows",
"shadow_vertex",
"shadow_fragment",
PIXEL_FORMAT,
);
let quads_pipeline_state = build_pipeline_state( let quads_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
@ -91,12 +100,12 @@ impl MetalRenderer {
"quad_fragment", "quad_fragment",
PIXEL_FORMAT, PIXEL_FORMAT,
); );
let shadows_pipeline_state = build_pipeline_state( let underlines_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
"shadows", "underlines",
"shadow_vertex", "underline_vertex",
"shadow_fragment", "underline_fragment",
PIXEL_FORMAT, PIXEL_FORMAT,
); );
let monochrome_sprites_pipeline_state = build_pipeline_state( let monochrome_sprites_pipeline_state = build_pipeline_state(
@ -122,8 +131,9 @@ impl MetalRenderer {
Self { Self {
layer, layer,
command_queue, command_queue,
quads_pipeline_state,
shadows_pipeline_state, shadows_pipeline_state,
quads_pipeline_state,
underlines_pipeline_state,
monochrome_sprites_pipeline_state, monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state, polychrome_sprites_pipeline_state,
unit_vertices, unit_vertices,
@ -184,10 +194,7 @@ impl MetalRenderer {
let mut instance_offset = 0; let mut instance_offset = 0;
for batch in scene.batches() { for batch in scene.batches() {
match batch { match batch {
crate::PrimitiveBatch::Quads(quads) => { PrimitiveBatch::Shadows(shadows) => {
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
}
crate::PrimitiveBatch::Shadows(shadows) => {
self.draw_shadows( self.draw_shadows(
shadows, shadows,
&mut instance_offset, &mut instance_offset,
@ -195,7 +202,18 @@ impl MetalRenderer {
command_encoder, command_encoder,
); );
} }
crate::PrimitiveBatch::MonochromeSprites { PrimitiveBatch::Quads(quads) => {
self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
}
PrimitiveBatch::Underlines(underlines) => {
self.draw_underlines(
underlines,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
PrimitiveBatch::MonochromeSprites {
texture_id, texture_id,
sprites, sprites,
} => { } => {
@ -207,7 +225,7 @@ impl MetalRenderer {
command_encoder, command_encoder,
); );
} }
crate::PrimitiveBatch::PolychromeSprites { PrimitiveBatch::PolychromeSprites {
texture_id, texture_id,
sprites, sprites,
} => { } => {
@ -234,62 +252,6 @@ impl MetalRenderer {
drawable.present(); drawable.present();
} }
fn draw_quads(
&mut self,
quads: &[Quad],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if quads.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
command_encoder.set_vertex_buffer(
QuadInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
QuadInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let quad_bytes_len = mem::size_of::<Quad>() * quads.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
}
let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
quads.len() as u64,
);
*offset = next_offset;
}
fn draw_shadows( fn draw_shadows(
&mut self, &mut self,
shadows: &[Shadow], shadows: &[Shadow],
@ -350,6 +312,122 @@ impl MetalRenderer {
*offset = next_offset; *offset = next_offset;
} }
fn draw_quads(
&mut self,
quads: &[Quad],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if quads.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
command_encoder.set_vertex_buffer(
QuadInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
QuadInputIndex::Quads as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
QuadInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let quad_bytes_len = mem::size_of::<Quad>() * quads.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
}
let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
quads.len() as u64,
);
*offset = next_offset;
}
fn draw_underlines(
&mut self,
underlines: &[Underline],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if underlines.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state);
command_encoder.set_vertex_buffer(
UnderlineInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
UnderlineInputIndex::Underlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
UnderlineInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let quad_bytes_len = mem::size_of::<Underline>() * underlines.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
underlines.as_ptr() as *const u8,
buffer_contents,
quad_bytes_len,
);
}
let next_offset = *offset + quad_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
underlines.len() as u64,
);
*offset = next_offset;
}
fn draw_monochrome_sprites( fn draw_monochrome_sprites(
&mut self, &mut self,
texture_id: AtlasTextureId, texture_id: AtlasTextureId,
@ -533,6 +611,13 @@ fn align_offset(offset: &mut usize) {
*offset = ((*offset + 255) / 256) * 256; *offset = ((*offset + 255) / 256) * 256;
} }
#[repr(C)]
enum ShadowInputIndex {
Vertices = 0,
Shadows = 1,
ViewportSize = 2,
}
#[repr(C)] #[repr(C)]
enum QuadInputIndex { enum QuadInputIndex {
Vertices = 0, Vertices = 0,
@ -541,9 +626,9 @@ enum QuadInputIndex {
} }
#[repr(C)] #[repr(C)]
enum ShadowInputIndex { enum UnderlineInputIndex {
Vertices = 0, Vertices = 0,
Shadows = 1, Underlines = 1,
ViewportSize = 2, ViewportSize = 2,
} }

View file

@ -193,6 +193,53 @@ fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]],
return input.color * float4(1., 1., 1., alpha); return input.color * float4(1., 1., 1., alpha);
} }
struct UnderlineVertexOutput {
float4 position [[position]];
float4 color [[flat]];
uint underline_id [[flat]];
};
vertex UnderlineVertexOutput underline_vertex(
uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
constant Size_DevicePixels *viewport_size
[[buffer(ShadowInputIndex_ViewportSize)]]) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Underline underline = underlines[underline_id];
float4 device_position =
to_device_position(unit_vertex, underline.bounds,
underline.content_mask.bounds, viewport_size);
float4 color = hsla_to_rgba(underline.color);
return UnderlineVertexOutput{device_position, color, underline_id};
}
fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]],
constant Underline *underlines
[[buffer(UnderlineInputIndex_Underlines)]]) {
Underline underline = underlines[input.underline_id];
if (underline.wavy) {
float half_thickness = underline.thickness * 0.5;
float2 origin =
float2(underline.bounds.origin.x, underline.bounds.origin.y);
float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
float2(0., 0.5);
float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
float amplitude = 1. / (2. * underline.thickness);
float sine = sin(st.x * frequency) * amplitude;
float dSine = cos(st.x * frequency) * amplitude * frequency;
float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
float distance_in_pixels = distance * underline.bounds.size.height;
float distance_from_top_border = distance_in_pixels - half_thickness;
float distance_from_bottom_border = distance_in_pixels + half_thickness;
float alpha = saturate(
0.5 - max(-distance_from_bottom_border, distance_from_top_border));
return input.color * float4(1., 1., 1., alpha);
} else {
return input.color;
}
}
struct MonochromeSpriteVertexOutput { struct MonochromeSpriteVertexOutput {
float4 position [[position]]; float4 position [[position]];
float2 tile_position; float2 tile_position;
@ -211,8 +258,8 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
MonochromeSprite sprite = sprites[sprite_id]; MonochromeSprite sprite = sprites[sprite_id];
// Don't apply content mask at the vertex level because we don't have time to // Don't apply content mask at the vertex level because we don't have time
// make sampling from the texture match the mask. // to make sampling from the texture match the mask.
float4 device_position = to_device_position(unit_vertex, sprite.bounds, float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size); sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
@ -254,8 +301,8 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
float2 unit_vertex = unit_vertices[unit_vertex_id]; float2 unit_vertex = unit_vertices[unit_vertex_id];
PolychromeSprite sprite = sprites[sprite_id]; PolychromeSprite sprite = sprites[sprite_id];
// Don't apply content mask at the vertex level because we don't have time to // Don't apply content mask at the vertex level because we don't have time
// make sampling from the texture match the mask. // to make sampling from the texture match the mask.
float4 device_position = to_device_position(unit_vertex, sprite.bounds, float4 device_position = to_device_position(unit_vertex, sprite.bounds,
sprite.bounds, viewport_size); sprite.bounds, viewport_size);
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);

View file

@ -17,8 +17,9 @@ pub type DrawOrder = u32;
pub struct Scene { pub struct Scene {
pub(crate) scale_factor: f32, pub(crate) scale_factor: f32,
pub(crate) layers: BTreeMap<StackingOrder, LayerId>, pub(crate) layers: BTreeMap<StackingOrder, LayerId>,
pub quads: Vec<Quad>,
pub shadows: Vec<Shadow>, pub shadows: Vec<Shadow>,
pub quads: Vec<Quad>,
pub underlines: Vec<Underline>,
pub monochrome_sprites: Vec<MonochromeSprite>, pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>, pub polychrome_sprites: Vec<PolychromeSprite>,
} }
@ -28,8 +29,9 @@ impl Scene {
Scene { Scene {
scale_factor, scale_factor,
layers: BTreeMap::new(), layers: BTreeMap::new(),
quads: Vec::new(),
shadows: Vec::new(), shadows: Vec::new(),
quads: Vec::new(),
underlines: Vec::new(),
monochrome_sprites: Vec::new(), monochrome_sprites: Vec::new(),
polychrome_sprites: Vec::new(), polychrome_sprites: Vec::new(),
} }
@ -39,8 +41,9 @@ impl Scene {
Scene { Scene {
scale_factor: self.scale_factor, scale_factor: self.scale_factor,
layers: mem::take(&mut self.layers), layers: mem::take(&mut self.layers),
quads: mem::take(&mut self.quads),
shadows: mem::take(&mut self.shadows), shadows: mem::take(&mut self.shadows),
quads: mem::take(&mut self.quads),
underlines: mem::take(&mut self.underlines),
monochrome_sprites: mem::take(&mut self.monochrome_sprites), monochrome_sprites: mem::take(&mut self.monochrome_sprites),
polychrome_sprites: mem::take(&mut self.polychrome_sprites), polychrome_sprites: mem::take(&mut self.polychrome_sprites),
} }
@ -51,13 +54,17 @@ impl Scene {
let layer_id = *self.layers.entry(layer_id).or_insert(next_id); let layer_id = *self.layers.entry(layer_id).or_insert(next_id);
let primitive = primitive.into(); let primitive = primitive.into();
match primitive { match primitive {
Primitive::Shadow(mut shadow) => {
shadow.order = layer_id;
self.shadows.push(shadow);
}
Primitive::Quad(mut quad) => { Primitive::Quad(mut quad) => {
quad.order = layer_id; quad.order = layer_id;
self.quads.push(quad); self.quads.push(quad);
} }
Primitive::Shadow(mut shadow) => { Primitive::Underline(mut underline) => {
shadow.order = layer_id; underline.order = layer_id;
self.shadows.push(shadow); self.underlines.push(underline);
} }
Primitive::MonochromeSprite(mut sprite) => { Primitive::MonochromeSprite(mut sprite) => {
sprite.order = layer_id; sprite.order = layer_id;
@ -78,15 +85,26 @@ impl Scene {
} }
// Add all primitives to the BSP splitter to determine draw order // Add all primitives to the BSP splitter to determine draw order
// todo!("reuse the same splitter")
let mut splitter = BspSplitter::new(); let mut splitter = BspSplitter::new();
for (ix, shadow) in self.shadows.iter().enumerate() {
let z = layer_z_values[shadow.order as LayerId as usize];
splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix)));
}
for (ix, quad) in self.quads.iter().enumerate() { for (ix, quad) in self.quads.iter().enumerate() {
let z = layer_z_values[quad.order as LayerId as usize]; let z = layer_z_values[quad.order as LayerId as usize];
splitter.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix))); splitter.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
} }
for (ix, shadow) in self.shadows.iter().enumerate() { for (ix, underline) in self.underlines.iter().enumerate() {
let z = layer_z_values[shadow.order as LayerId as usize]; let z = layer_z_values[underline.order as LayerId as usize];
splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix))); splitter.add(
underline
.bounds
.to_bsp_polygon(z, (PrimitiveKind::Underline, ix)),
);
} }
for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() { for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() {
@ -111,8 +129,11 @@ impl Scene {
// We need primitives to be repr(C), hence the weird reuse of the order field for two different types. // We need primitives to be repr(C), hence the weird reuse of the order field for two different types.
for (draw_order, polygon) in splitter.sort(Vector3D::new(0., 0., 1.)).iter().enumerate() { for (draw_order, polygon) in splitter.sort(Vector3D::new(0., 0., 1.)).iter().enumerate() {
match polygon.anchor { match polygon.anchor {
(PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder,
(PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder, (PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder,
(PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder,
(PrimitiveKind::Underline, ix) => {
self.underlines[ix].order = draw_order as DrawOrder
}
(PrimitiveKind::MonochromeSprite, ix) => { (PrimitiveKind::MonochromeSprite, ix) => {
self.monochrome_sprites[ix].order = draw_order as DrawOrder self.monochrome_sprites[ix].order = draw_order as DrawOrder
} }
@ -123,18 +144,22 @@ impl Scene {
} }
// Sort the primitives // Sort the primitives
self.quads.sort_unstable();
self.shadows.sort_unstable(); self.shadows.sort_unstable();
self.quads.sort_unstable();
self.underlines.sort_unstable();
self.monochrome_sprites.sort_unstable(); self.monochrome_sprites.sort_unstable();
self.polychrome_sprites.sort_unstable(); self.polychrome_sprites.sort_unstable();
BatchIterator { BatchIterator {
quads: &self.quads,
quads_start: 0,
quads_iter: self.quads.iter().peekable(),
shadows: &self.shadows, shadows: &self.shadows,
shadows_start: 0, shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(), shadows_iter: self.shadows.iter().peekable(),
quads: &self.quads,
quads_start: 0,
quads_iter: self.quads.iter().peekable(),
underlines: &self.underlines,
underlines_start: 0,
underlines_iter: self.underlines.iter().peekable(),
monochrome_sprites: &self.monochrome_sprites, monochrome_sprites: &self.monochrome_sprites,
monochrome_sprites_start: 0, monochrome_sprites_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
@ -152,6 +177,9 @@ struct BatchIterator<'a> {
shadows: &'a [Shadow], shadows: &'a [Shadow],
shadows_start: usize, shadows_start: usize,
shadows_iter: Peekable<slice::Iter<'a, Shadow>>, shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
underlines: &'a [Underline],
underlines_start: usize,
underlines_iter: Peekable<slice::Iter<'a, Underline>>,
monochrome_sprites: &'a [MonochromeSprite], monochrome_sprites: &'a [MonochromeSprite],
monochrome_sprites_start: usize, monochrome_sprites_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>, monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
@ -165,11 +193,15 @@ impl<'a> Iterator for BatchIterator<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut orders_and_kinds = [ let mut orders_and_kinds = [
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
( (
self.shadows_iter.peek().map(|s| s.order), self.shadows_iter.peek().map(|s| s.order),
PrimitiveKind::Shadow, PrimitiveKind::Shadow,
), ),
(self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
(
self.underlines_iter.peek().map(|u| u.order),
PrimitiveKind::Underline,
),
( (
self.monochrome_sprites_iter.peek().map(|s| s.order), self.monochrome_sprites_iter.peek().map(|s| s.order),
PrimitiveKind::MonochromeSprite, PrimitiveKind::MonochromeSprite,
@ -190,19 +222,6 @@ impl<'a> Iterator for BatchIterator<'a> {
}; };
match batch_kind { match batch_kind {
PrimitiveKind::Quad => {
let quads_start = self.quads_start;
let mut quads_end = quads_start;
while self
.quads_iter
.next_if(|quad| quad.order <= max_order)
.is_some()
{
quads_end += 1;
}
self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
}
PrimitiveKind::Shadow => { PrimitiveKind::Shadow => {
let shadows_start = self.shadows_start; let shadows_start = self.shadows_start;
let mut shadows_end = shadows_start; let mut shadows_end = shadows_start;
@ -218,6 +237,34 @@ impl<'a> Iterator for BatchIterator<'a> {
&self.shadows[shadows_start..shadows_end], &self.shadows[shadows_start..shadows_end],
)) ))
} }
PrimitiveKind::Quad => {
let quads_start = self.quads_start;
let mut quads_end = quads_start;
while self
.quads_iter
.next_if(|quad| quad.order <= max_order)
.is_some()
{
quads_end += 1;
}
self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
}
PrimitiveKind::Underline => {
let underlines_start = self.underlines_start;
let mut underlines_end = underlines_start;
while self
.underlines_iter
.next_if(|underline| underline.order <= max_order)
.is_some()
{
underlines_end += 1;
}
self.underlines_start = underlines_end;
Some(PrimitiveBatch::Underlines(
&self.underlines[underlines_start..underlines_end],
))
}
PrimitiveKind::MonochromeSprite => { PrimitiveKind::MonochromeSprite => {
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id; let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.monochrome_sprites_start; let sprites_start = self.monochrome_sprites_start;
@ -265,22 +312,25 @@ pub enum PrimitiveKind {
Shadow, Shadow,
#[default] #[default]
Quad, Quad,
Underline,
MonochromeSprite, MonochromeSprite,
PolychromeSprite, PolychromeSprite,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Primitive { pub enum Primitive {
Quad(Quad),
Shadow(Shadow), Shadow(Shadow),
Quad(Quad),
Underline(Underline),
MonochromeSprite(MonochromeSprite), MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite), PolychromeSprite(PolychromeSprite),
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> { pub(crate) enum PrimitiveBatch<'a> {
Quads(&'a [Quad]),
Shadows(&'a [Shadow]), Shadows(&'a [Shadow]),
Quads(&'a [Quad]),
Underlines(&'a [Underline]),
MonochromeSprites { MonochromeSprites {
texture_id: AtlasTextureId, texture_id: AtlasTextureId,
sprites: &'a [MonochromeSprite], sprites: &'a [MonochromeSprite],
@ -321,6 +371,35 @@ impl From<Quad> for Primitive {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct Underline {
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ScaledContentMask,
pub thickness: ScaledPixels,
pub color: Hsla,
pub wavy: bool,
}
impl Ord for Underline {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Underline {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Underline> for Primitive {
fn from(underline: Underline) -> Self {
Primitive::Underline(underline)
}
}
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)] #[repr(C)]
pub struct Shadow { pub struct Shadow {

View file

@ -344,7 +344,7 @@ impl Default for Style {
pub struct UnderlineStyle { pub struct UnderlineStyle {
pub thickness: Pixels, pub thickness: Pixels,
pub color: Option<Hsla>, pub color: Option<Hsla>,
pub squiggly: bool, pub wavy: bool,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -416,6 +416,96 @@ pub trait StyleHelpers: Styled<Style = Style> {
self self
} }
fn text_decoration_none(mut self) -> Self
where
Self: Sized,
{
self.text_style()
.get_or_insert_with(Default::default)
.underline = None;
self
}
fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.color = Some(color.into());
self
}
fn text_decoration_solid(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.wavy = false;
self
}
fn text_decoration_wavy(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.wavy = true;
self
}
fn text_decoration_0(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(0.);
self
}
fn text_decoration_1(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(1.);
self
}
fn text_decoration_2(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(2.);
self
}
fn text_decoration_4(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(4.);
self
}
fn text_decoration_8(mut self) -> Self
where
Self: Sized,
{
let style = self.text_style().get_or_insert_with(Default::default);
let underline = style.underline.get_or_insert_with(Default::default);
underline.thickness = px(8.);
self
}
fn font(mut self, family_name: impl Into<SharedString>) -> Self fn font(mut self, family_name: impl Into<SharedString>) -> Self
where where
Self: Sized, Self: Sized,

View file

@ -134,9 +134,11 @@ impl Line {
origin.y + baseline_offset.y + (self.layout.descent * 0.618), origin.y + baseline_offset.y + (self.layout.descent * 0.618),
), ),
UnderlineStyle { UnderlineStyle {
color: style_run.underline.color, color: Some(
style_run.underline.color.unwrap_or(style_run.color),
),
thickness: style_run.underline.thickness, thickness: style_run.underline.thickness,
squiggly: style_run.underline.squiggly, wavy: style_run.underline.wavy,
}, },
)); ));
} }
@ -153,8 +155,12 @@ impl Line {
continue; continue;
} }
if let Some((_underline_origin, _underline_style)) = finished_underline { if let Some((underline_origin, underline_style)) = finished_underline {
todo!() cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
} }
if glyph.is_emoji { if glyph.is_emoji {
@ -171,15 +177,13 @@ impl Line {
} }
} }
if let Some((_underline_start, _underline_style)) = underline.take() { if let Some((underline_start, underline_style)) = underline.take() {
let _line_end_x = origin.x + self.layout.width; let line_end_x = origin.x + self.layout.width;
// cx.scene().push_underline(Underline { cx.paint_underline(
// origin: underline_start, underline_start,
// width: line_end_x - underline_start.x, line_end_x - underline_start.x,
// color: underline_style.color, &underline_style,
// thickness: underline_style.thickness.into(), )?;
// squiggly: underline_style.squiggly,
// });
} }
Ok(()) Ok(())
@ -188,7 +192,7 @@ impl Line {
pub fn paint_wrapped( pub fn paint_wrapped(
&self, &self,
origin: Point<Pixels>, origin: Point<Pixels>,
_visible_bounds: Bounds<Pixels>, _visible_bounds: Bounds<Pixels>, // todo!("use clipping")
line_height: Pixels, line_height: Pixels,
boundaries: &[ShapedBoundary], boundaries: &[ShapedBoundary],
cx: &mut WindowContext, cx: &mut WindowContext,
@ -213,14 +217,12 @@ impl Line {
.map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix) .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
{ {
boundaries.next(); boundaries.next();
if let Some((_underline_origin, _underline_style)) = underline.take() { if let Some((underline_origin, underline_style)) = underline.take() {
// cx.scene().push_underline(Underline { cx.paint_underline(
// origin: underline_origin, underline_origin,
// width: glyph_origin.x - underline_origin.x, glyph_origin.x - underline_origin.x,
// thickness: underline_style.thickness.into(), &underline_style,
// color: underline_style.color.unwrap(), )?;
// squiggly: underline_style.squiggly,
// });
} }
glyph_origin = point(origin.x, glyph_origin.y + line_height); glyph_origin = point(origin.x, glyph_origin.y + line_height);
@ -249,7 +251,7 @@ impl Line {
style_run.underline.color.unwrap_or(style_run.color), style_run.underline.color.unwrap_or(style_run.color),
), ),
thickness: style_run.underline.thickness, thickness: style_run.underline.thickness,
squiggly: style_run.underline.squiggly, wavy: style_run.underline.wavy,
}, },
)); ));
} }
@ -260,14 +262,12 @@ impl Line {
} }
} }
if let Some((_underline_origin, _underline_style)) = finished_underline { if let Some((underline_origin, underline_style)) = finished_underline {
// cx.scene().push_underline(Underline { cx.paint_underline(
// origin: underline_origin, underline_origin,
// width: glyph_origin.x - underline_origin.x, glyph_origin.x - underline_origin.x,
// thickness: underline_style.thickness.into(), &underline_style,
// color: underline_style.color.unwrap(), )?;
// squiggly: underline_style.squiggly,
// });
} }
let text_system = cx.text_system(); let text_system = cx.text_system();
@ -298,15 +298,13 @@ impl Line {
} }
} }
if let Some((_underline_origin, _underline_style)) = underline.take() { if let Some((underline_origin, underline_style)) = underline.take() {
// let line_end_x = glyph_origin.x + self.layout.width - prev_position; let line_end_x = glyph_origin.x + self.layout.width - prev_position;
// cx.scene().push_underline(Underline { cx.paint_underline(
// origin: underline_origin, underline_origin,
// width: line_end_x - underline_origin.x, line_end_x - underline_origin.x,
// thickness: underline_style.thickness.into(), &underline_style,
// color: underline_style.color, )?;
// squiggly: underline_style.squiggly,
// });
} }
Ok(()) Ok(())

View file

@ -1,10 +1,11 @@
use crate::{ use crate::{
image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace, image_cache::RenderImageParams, px, size, AnyView, AppContext, AsyncWindowContext,
BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect, Element, EntityId, AvailableSpace, BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect,
FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread, MainThreadOnly, Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread,
MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, StackingOrder, PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
Style, TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, SharedString, Size, StackingOrder, Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
}; };
use anyhow::Result; use anyhow::Result;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -259,6 +260,38 @@ impl<'a, 'w> WindowContext<'a, 'w> {
self.window.current_stacking_order.clone() self.window.current_stacking_order.clone()
} }
pub fn paint_underline(
&mut self,
origin: Point<Pixels>,
width: Pixels,
style: &UnderlineStyle,
) -> Result<()> {
let scale_factor = self.scale_factor();
let height = if style.wavy {
style.thickness * 3.
} else {
style.thickness
};
let bounds = Bounds {
origin,
size: size(width, height),
};
let content_mask = self.content_mask();
let layer_id = self.current_stacking_order();
self.window.scene.insert(
layer_id,
Underline {
order: 0,
bounds: bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
thickness: style.thickness.scale(scale_factor),
color: style.color.unwrap_or_default(),
wavy: style.wavy,
},
);
Ok(())
}
pub fn paint_glyph( pub fn paint_glyph(
&mut self, &mut self,
origin: Point<Pixels>, origin: Point<Pixels>,

View file

@ -160,7 +160,13 @@ impl Titlebar {
// .fill(theme.lowest.base.hovered.background) // .fill(theme.lowest.base.hovered.background)
// .active() // .active()
// .fill(theme.lowest.base.pressed.background) // .fill(theme.lowest.base.pressed.background)
.child(div().text_sm().child("branch")), .child(
div()
.text_sm()
.text_decoration_1()
.text_decoration_wavy()
.child("branch"),
),
), ),
) )
} }