WIP: Start on squiggly underlines

This commit is contained in:
Antonio Scandurra 2022-01-26 13:07:39 +01:00
parent a890787923
commit 52594fe250
4 changed files with 141 additions and 3 deletions

View File

@ -26,6 +26,7 @@ pub struct Renderer {
sprite_pipeline_state: metal::RenderPipelineState,
image_pipeline_state: metal::RenderPipelineState,
path_atlas_pipeline_state: metal::RenderPipelineState,
underline_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
instances: metal::Buffer,
}
@ -109,6 +110,14 @@ impl Renderer {
"path_atlas_fragment",
MTLPixelFormat::R16Float,
);
let underline_pipeline_state = build_pipeline_state(
&device,
&library,
"underline",
"underline_vertex",
"underline_fragment",
pixel_format,
);
Self {
sprite_cache,
image_cache,
@ -118,6 +127,7 @@ impl Renderer {
sprite_pipeline_state,
image_pipeline_state,
path_atlas_pipeline_state,
underline_pipeline_state,
unit_vertices,
instances,
}
@ -339,7 +349,7 @@ impl Renderer {
drawable_size,
command_encoder,
);
self.render_quads(
self.render_underlines(
layer.underlines(),
scale_factor,
offset,
@ -821,6 +831,73 @@ impl Renderer {
);
*offset = next_offset;
}
fn render_underlines(
&mut self,
underlines: &[Quad],
scale_factor: f32,
offset: &mut usize,
drawable_size: Vector2F,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if underlines.is_empty() {
return;
}
align_offset(offset);
let next_offset = *offset + underlines.len() * mem::size_of::<shaders::GPUIUnderline>();
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.set_render_pipeline_state(&self.underline_pipeline_state);
command_encoder.set_vertex_buffer(
shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexVertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexUnderlines as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
shaders::GPUIUnderlineInputIndex_GPUIUnderlineInputIndexUniforms as u64,
mem::size_of::<shaders::GPUIUniforms>() as u64,
[shaders::GPUIUniforms {
viewport_size: drawable_size.to_float2(),
}]
.as_ptr() as *const c_void,
);
let buffer_contents = unsafe {
(self.instances.contents() as *mut u8).offset(*offset as isize)
as *mut shaders::GPUIUnderline
};
for (ix, quad) in underlines.iter().enumerate() {
let bounds = quad.bounds * scale_factor;
let shader_quad = shaders::GPUIUnderline {
origin: bounds.origin().round().to_float2(),
size: bounds.size().round().to_float2(),
thickness: 1. * scale_factor,
color: quad
.background
.unwrap_or(Color::transparent_black())
.to_uchar4(),
};
unsafe {
*(buffer_contents.offset(ix as isize)) = shader_quad;
}
}
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
underlines.len() as u64,
);
*offset = next_offset;
}
}
fn build_path_atlas_texture_descriptor() -> metal::TextureDescriptor {

View File

@ -104,3 +104,18 @@ typedef struct
vector_uchar4 border_color;
float corner_radius;
} GPUIImage;
typedef enum
{
GPUIUnderlineInputIndexVertices = 0,
GPUIUnderlineInputIndexUnderlines = 1,
GPUIUnderlineInputIndexUniforms = 2,
} GPUIUnderlineInputIndex;
typedef struct
{
vector_float2 origin;
vector_float2 size;
float thickness;
vector_uchar4 color;
} GPUIUnderline;

View File

@ -304,3 +304,49 @@ fragment float4 path_atlas_fragment(
float alpha = saturate(0.5 - distance);
return float4(alpha, 0., 0., 1.);
}
struct UnderlineFragmentInput {
float4 position [[position]];
float2 origin;
float2 size;
float thickness;
float4 color;
};
vertex UnderlineFragmentInput underline_vertex(
uint unit_vertex_id [[vertex_id]],
uint underline_id [[instance_id]],
constant float2 *unit_vertices [[buffer(GPUIUnderlineInputIndexVertices)]],
constant GPUIUnderline *underlines [[buffer(GPUIUnderlineInputIndexUnderlines)]],
constant GPUIUniforms *uniforms [[buffer(GPUIUnderlineInputIndexUniforms)]]
) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
GPUIUnderline underline = underlines[underline_id];
float2 position = unit_vertex * underline.size + underline.origin;
float4 device_position = to_device_position(position, uniforms->viewport_size);
return UnderlineFragmentInput {
device_position,
underline.origin,
underline.size,
underline.thickness,
coloru_to_colorf(underline.color),
};
}
fragment float4 underline_fragment(
UnderlineFragmentInput input [[stage_in]]
) {
float half_thickness = input.thickness * 0.5;
float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5);
float frequency = M_PI_F * 0.75;
float amplitude = 0.3;
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 * input.size.y;
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);
}

View File

@ -290,7 +290,7 @@ impl Line {
if let Some((underline_origin, underline_color)) = finished_underline {
cx.scene.push_underline(scene::Quad {
bounds: RectF::from_points(underline_origin, glyph_origin + vec2f(0., 1.)),
bounds: RectF::from_points(underline_origin, glyph_origin + vec2f(0., 3.)),
background: Some(underline_color),
border: Default::default(),
corner_radius: 0.,
@ -311,7 +311,7 @@ impl Line {
let line_end = origin + baseline_offset + vec2f(self.layout.width, 0.);
cx.scene.push_underline(scene::Quad {
bounds: RectF::from_points(underline_start, line_end + vec2f(0., 1.)),
bounds: RectF::from_points(underline_start, line_end + vec2f(0., 3.)),
background: Some(underline_color),
border: Default::default(),
corner_radius: 0.,