@@ -4,7 +4,7 @@
use super::{BladeBelt, BladeBeltDescriptor};
use crate::{
AtlasTextureKind, AtlasTile, BladeAtlas, Bounds, ContentMask, Hsla, Path, PathId, PathVertex,
- PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, PATH_TEXTURE_FORMAT,
+ PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Underline, PATH_TEXTURE_FORMAT,
};
use bytemuck::{Pod, Zeroable};
use collections::HashMap;
@@ -48,6 +48,12 @@ struct ShaderPathsData {
b_path_sprites: gpu::BufferPiece,
}
+#[derive(blade_macros::ShaderData)]
+struct ShaderUnderlinesData {
+ globals: GlobalParams,
+ b_underlines: gpu::BufferPiece,
+}
+
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
struct PathSprite {
@@ -61,6 +67,7 @@ struct BladePipelines {
shadows: gpu::RenderPipeline,
path_rasterization: gpu::RenderPipeline,
paths: gpu::RenderPipeline,
+ underlines: gpu::RenderPipeline,
}
impl BladePipelines {
@@ -75,11 +82,13 @@ impl BladePipelines {
shader.get_struct_size("PathVertex") as usize,
);
shader.check_struct_size::<PathSprite>();
+ shader.check_struct_size::<Underline>();
let quads_layout = <ShaderQuadsData as gpu::ShaderData>::layout();
let shadows_layout = <ShaderShadowsData as gpu::ShaderData>::layout();
let path_rasterization_layout = <ShaderPathRasterizationData as gpu::ShaderData>::layout();
let paths_layout = <ShaderPathsData as gpu::ShaderData>::layout();
+ let underlines_layout = <ShaderUnderlinesData as gpu::ShaderData>::layout();
Self {
quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
@@ -146,6 +155,22 @@ impl BladePipelines {
write_mask: gpu::ColorWrites::default(),
}],
}),
+ underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
+ name: "underlines",
+ data_layouts: &[&underlines_layout],
+ vertex: shader.at("vs_underline"),
+ primitive: gpu::PrimitiveState {
+ topology: gpu::PrimitiveTopology::TriangleStrip,
+ ..Default::default()
+ },
+ depth_stencil: None,
+ fragment: shader.at("fs_underline"),
+ color_targets: &[gpu::ColorTargetState {
+ format: surface_format,
+ blend: Some(gpu::BlendState::ALPHA_BLENDING),
+ write_mask: gpu::ColorWrites::default(),
+ }],
+ }),
}
}
}
@@ -359,6 +384,18 @@ impl BladeRenderer {
encoder.draw(0, 4, 0, sprites.len() as u32);
}
}
+ PrimitiveBatch::Underlines(underlines) => {
+ let instance_buf = self.instance_belt.alloc_data(underlines, &self.gpu);
+ let mut encoder = pass.with(&self.pipelines.underlines);
+ encoder.bind(
+ 0,
+ &ShaderUnderlinesData {
+ globals,
+ b_underlines: instance_buf,
+ },
+ );
+ encoder.draw(0, 4, 0, underlines.len() as u32);
+ }
_ => continue,
}
}
@@ -373,7 +373,7 @@ struct PathVarying {
@vertex
fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PathVarying {
- let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
+ let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
let sprite = b_path_sprites[instance_id];
// Don't apply content mask because it was already accounted for when rasterizing the path.
@@ -386,7 +386,69 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
@fragment
fn fs_path(input: PathVarying) -> @location(0) vec4<f32> {
- let sample = textureSample(t_tile, s_tile, input.tile_position).r;
- let mask = 1.0 - abs(1.0 - sample % 2.0);
- return input.color * mask;
+ let sample = textureSample(t_tile, s_tile, input.tile_position).r;
+ let mask = 1.0 - abs(1.0 - sample % 2.0);
+ return input.color * mask;
+}
+
+// --- underlines --- //
+
+struct Underline {
+ view_id: ViewId,
+ layer_id: u32,
+ order: u32,
+ bounds: Bounds,
+ content_mask: Bounds,
+ color: Hsla,
+ thickness: f32,
+ wavy: u32,
+}
+var<storage, read> b_underlines: array<Underline>;
+
+struct UnderlineVarying {
+ @builtin(position) position: vec4<f32>,
+ @location(0) @interpolate(flat) color: vec4<f32>,
+ @location(1) @interpolate(flat) underline_id: u32,
+ //TODO: use `clip_distance` once Naga supports it
+ @location(3) clip_distances: vec4<f32>,
+}
+
+@vertex
+fn vs_underline(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> UnderlineVarying {
+ let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
+ let underline = b_underlines[instance_id];
+
+ var out = UnderlineVarying();
+ out.position = to_device_position(unit_vertex, underline.bounds);
+ out.color = hsla_to_rgba(underline.color);
+ out.underline_id = instance_id;
+ out.clip_distances = distance_from_clip_rect(unit_vertex, underline.bounds, underline.content_mask);
+ return out;
+}
+
+@fragment
+fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
+ // Alpha clip first, since we don't have `clip_distance`.
+ if (any(input.clip_distances < vec4<f32>(0.0))) {
+ return vec4<f32>(0.0);
+ }
+
+ let underline = b_underlines[input.underline_id];
+ if (underline.wavy == 0u)
+ {
+ return vec4<f32>(0.0);
+ }
+
+ let half_thickness = underline.thickness * 0.5;
+ let st = (input.position.xy - underline.bounds.origin) / underline.bounds.size.y - vec2<f32>(0.0, 0.5);
+ let frequency = M_PI_F * 3.0 * underline.thickness / 8.0;
+ let amplitude = 1.0 / (2.0 * underline.thickness);
+ let sine = sin(st.x * frequency) * amplitude;
+ let dSine = cos(st.x * frequency) * amplitude * frequency;
+ let distance = (st.y - sine) / sqrt(1.0 + dSine * dSine);
+ let distance_in_pixels = distance * underline.bounds.size.y;
+ let distance_from_top_border = distance_in_pixels - half_thickness;
+ let distance_from_bottom_border = distance_in_pixels + half_thickness;
+ let alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
+ return input.color * vec4<f32>(1.0, 1.0, 1.0, alpha);
}
@@ -543,8 +543,8 @@ pub(crate) struct Underline {
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
- pub thickness: ScaledPixels,
pub color: Hsla,
+ pub thickness: ScaledPixels,
pub wavy: bool,
}
@@ -753,8 +753,8 @@ impl<'a> ElementContext<'a> {
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(),
+ thickness: style.thickness.scale(scale_factor),
wavy: style.wavy,
},
);