@@ -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 {
@@ -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);
+}
@@ -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.,