This commit is contained in:
Antonio Scandurra 2023-10-05 15:30:47 +02:00
parent 92bda1231e
commit 2e056e9b0b
8 changed files with 366 additions and 11 deletions

View file

@ -52,6 +52,8 @@ fn generate_shader_bindings() -> PathBuf {
"AtlasTile".into(),
"QuadInputIndex".into(),
"Quad".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
"SpriteInputIndex".into(),
"MonochromeSprite".into(),
"PolychromeSprite".into(),

View file

@ -469,6 +469,17 @@ impl Edges<AbsoluteLength> {
}
}
impl Edges<Pixels> {
pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
Edges {
top: self.top.scale(factor),
right: self.right.scale(factor),
bottom: self.bottom.scale(factor),
left: self.left.scale(factor),
}
}
}
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)]
#[repr(C)]
@ -480,8 +491,8 @@ pub struct Corners<T: Clone + Debug> {
}
impl Corners<AbsoluteLength> {
pub fn to_pixels(&self, bounds: Bounds<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
let max = bounds.size.width.max(bounds.size.height) / 2.;
pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
let max = size.width.max(size.height) / 2.;
Corners {
top_left: self.top_left.to_pixels(rem_size).min(max),
top_right: self.top_right.to_pixels(rem_size).min(max),

View file

@ -1,6 +1,6 @@
use crate::{
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
Quad, Scene, Size,
Quad, Scene, Shadow, Size,
};
use cocoa::{
base::{NO, YES},
@ -18,6 +18,7 @@ pub struct MetalRenderer {
layer: metal::MetalLayer,
command_queue: CommandQueue,
quads_pipeline_state: metal::RenderPipelineState,
shadows_pipeline_state: metal::RenderPipelineState,
monochrome_sprites_pipeline_state: metal::RenderPipelineState,
polychrome_sprites_pipeline_state: metal::RenderPipelineState,
unit_vertices: metal::Buffer,
@ -90,6 +91,14 @@ impl MetalRenderer {
"quad_fragment",
PIXEL_FORMAT,
);
let shadows_pipeline_state = build_pipeline_state(
&device,
&library,
"shadows",
"shadow_vertex",
"shadow_fragment",
PIXEL_FORMAT,
);
let monochrome_sprites_pipeline_state = build_pipeline_state(
&device,
&library,
@ -114,6 +123,7 @@ impl MetalRenderer {
layer,
command_queue,
quads_pipeline_state,
shadows_pipeline_state,
monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state,
unit_vertices,
@ -183,6 +193,14 @@ impl MetalRenderer {
command_encoder,
);
}
crate::PrimitiveBatch::Shadows(shadows) => {
self.draw_shadows(
shadows,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
crate::PrimitiveBatch::MonochromeSprites {
texture_id,
sprites,
@ -279,6 +297,66 @@ impl MetalRenderer {
*offset = next_offset;
}
fn draw_shadows(
&mut self,
shadows: &[Shadow],
offset: &mut usize,
viewport_size: Size<DevicePixels>,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if shadows.is_empty() {
return;
}
align_offset(offset);
command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state);
command_encoder.set_vertex_buffer(
ShadowInputIndex::Vertices as u64,
Some(&self.unit_vertices),
0,
);
command_encoder.set_vertex_buffer(
ShadowInputIndex::Shadows as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_fragment_buffer(
ShadowInputIndex::Shadows as u64,
Some(&self.instances),
*offset as u64,
);
command_encoder.set_vertex_bytes(
ShadowInputIndex::ViewportSize as u64,
mem::size_of_val(&viewport_size) as u64,
&viewport_size as *const Size<DevicePixels> as *const _,
);
let shadow_bytes_len = mem::size_of::<Shadow>() * shadows.len();
let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
unsafe {
ptr::copy_nonoverlapping(
shadows.as_ptr() as *const u8,
buffer_contents,
shadow_bytes_len,
);
}
let next_offset = *offset + shadow_bytes_len;
assert!(
next_offset <= INSTANCE_BUFFER_SIZE,
"instance buffer exhausted"
);
command_encoder.draw_primitives_instanced(
metal::MTLPrimitiveType::Triangle,
0,
6,
shadows.len() as u64,
);
*offset = next_offset;
}
fn draw_monochrome_sprites(
&mut self,
texture_id: AtlasTextureId,
@ -469,6 +547,13 @@ enum QuadInputIndex {
ViewportSize = 2,
}
#[repr(C)]
enum ShadowInputIndex {
Vertices = 0,
Shadows = 1,
ViewportSize = 2,
}
#[repr(C)]
enum SpriteInputIndex {
Vertices = 0,

View file

@ -11,6 +11,9 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii);
float gaussian(float x, float sigma);
float2 erf(float2 x);
float blur_along_x(float x, float y, float sigma, float corner, float2 half_size);
struct QuadVertexOutput {
float4 position [[position]];
@ -110,6 +113,91 @@ fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
return color * float4(1., 1., 1., saturate(0.5 - distance));
}
struct ShadowVertexOutput {
float4 position [[position]];
float4 color [[flat]];
uint shadow_id [[flat]];
};
vertex ShadowVertexOutput shadow_vertex(
uint unit_vertex_id [[vertex_id]],
uint shadow_id [[instance_id]],
constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
constant Size_DevicePixels *viewport_size [[buffer(ShadowInputIndex_ViewportSize)]]
) {
float2 unit_vertex = unit_vertices[unit_vertex_id];
Shadow shadow = shadows[shadow_id];
float margin = (3. * shadow.blur_radius) + shadow.spread_radius;
// Set the bounds of the shadow and adjust its size based on the shadow's spread radius
// to achieve the spreading effect
Bounds_ScaledPixels bounds = shadow.bounds;
bounds.origin.x -= margin;
bounds.origin.y -= margin;
bounds.size.width += 2. * margin;
bounds.size.height += 2. * margin;
float4 device_position = to_device_position(unit_vertex, bounds, bounds, viewport_size);
float4 color = hsla_to_rgba(shadow.color);
return ShadowVertexOutput {
device_position,
color,
shadow_id,
};
}
fragment float4 shadow_fragment(
ShadowVertexOutput input [[stage_in]],
constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]]
) {
Shadow shadow = shadows[input.shadow_id];
float2 origin = float2(
shadow.bounds.origin.x - shadow.spread_radius,
shadow.bounds.origin.y - shadow.spread_radius
);
float2 size = float2(
shadow.bounds.size.width + shadow.spread_radius * 2.,
shadow.bounds.size.height + shadow.spread_radius * 2.
);
float2 half_size = size / 2.;
float2 center = origin + half_size;
float2 point = input.position.xy - center;
float corner_radius;
if (point.x < 0.) {
if (point.y < 0.) {
corner_radius = shadow.corner_radii.top_left;
} else {
corner_radius = shadow.corner_radii.bottom_left;
}
} else {
if (point.y < 0.) {
corner_radius = shadow.corner_radii.top_right;
} else {
corner_radius = shadow.corner_radii.bottom_right;
}
}
// The signal is only non-zero in a limited range, so don't waste samples
float low = point.y - half_size.y;
float high = point.y + half_size.y;
float start = clamp(-3. * shadow.blur_radius, low, high);
float end = clamp(3. * shadow.blur_radius, low, high);
// Accumulate samples (we can get away with surprisingly few samples)
float step = (end - start) / 4.;
float y = start + step * 0.5;
float alpha = 0.;
for (int i = 0; i < 4; i++) {
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius, corner_radius, half_size) * gaussian(y, shadow.blur_radius) * step;
y += step;
}
return input.color * float4(1., 1., 1., alpha);
}
struct MonochromeSpriteVertexOutput {
float4 position [[position]];
float2 tile_position;
@ -308,3 +396,24 @@ float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
return distance;
}
// A standard gaussian function, used for weighting samples
float gaussian(float x, float sigma) {
return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
}
// This approximates the error function, needed for the gaussian integral
float2 erf(float2 x) {
float2 s = sign(x);
float2 a = abs(x);
x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
x *= x;
return s - s / (x * x);
}
float blur_along_x(float x, float y, float sigma, float corner, float2 half_size) {
float delta = min(half_size.y - corner - abs(y), 0.);
float curved = half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
return integral.y - integral.x;
}

View file

@ -38,6 +38,9 @@ impl Scene {
Primitive::Quad(quad) => {
layer.quads.push(quad);
}
Primitive::Shadow(shadow) => {
layer.shadows.push(shadow);
}
Primitive::MonochromeSprite(sprite) => {
layer.monochrome_sprites.push(sprite);
}
@ -55,6 +58,7 @@ impl Scene {
#[derive(Debug, Default)]
pub(crate) struct SceneLayer {
pub quads: Vec<Quad>,
pub shadows: Vec<Shadow>,
pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>,
}
@ -68,6 +72,9 @@ impl SceneLayer {
quads: &self.quads,
quads_start: 0,
quads_iter: self.quads.iter().peekable(),
shadows: &self.shadows,
shadows_start: 0,
shadows_iter: self.shadows.iter().peekable(),
monochrome_sprites: &self.monochrome_sprites,
monochrome_sprites_start: 0,
monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
@ -82,6 +89,9 @@ struct BatchIterator<'a> {
quads: &'a [Quad],
quads_start: usize,
quads_iter: Peekable<slice::Iter<'a, Quad>>,
shadows: &'a [Shadow],
shadows_start: usize,
shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
monochrome_sprites: &'a [MonochromeSprite],
monochrome_sprites_start: usize,
monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
@ -96,6 +106,10 @@ impl<'a> Iterator for BatchIterator<'a> {
fn next(&mut self) -> Option<Self::Item> {
let mut kinds_and_orders = [
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
(
PrimitiveKind::Shadow,
self.shadows_iter.peek().map(|s| s.order),
),
(
PrimitiveKind::MonochromeSprite,
self.monochrome_sprites_iter.peek().map(|s| s.order),
@ -127,6 +141,19 @@ impl<'a> Iterator for BatchIterator<'a> {
self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
}
PrimitiveKind::Shadow => {
let shadows_start = self.shadows_start;
let shadows_end = shadows_start
+ self
.shadows_iter
.by_ref()
.take_while(|shadow| shadow.order <= max_order)
.count();
self.shadows_start = shadows_end;
Some(PrimitiveBatch::Shadows(
&self.shadows[shadows_start..shadows_end],
))
}
PrimitiveKind::MonochromeSprite => {
let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
let sprites_start = self.monochrome_sprites_start;
@ -168,6 +195,7 @@ impl<'a> Iterator for BatchIterator<'a> {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PrimitiveKind {
Quad,
Shadow,
MonochromeSprite,
PolychromeSprite,
}
@ -175,12 +203,15 @@ pub enum PrimitiveKind {
#[derive(Clone, Debug)]
pub enum Primitive {
Quad(Quad),
Shadow(Shadow),
MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite),
}
#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> {
Quads(&'a [Quad]),
Shadows(&'a [Shadow]),
MonochromeSprites {
texture_id: AtlasTextureId,
sprites: &'a [MonochromeSprite],
@ -221,6 +252,36 @@ impl From<Quad> for Primitive {
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub struct Shadow {
pub order: u32,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
pub content_mask: ScaledContentMask,
pub color: Hsla,
pub blur_radius: ScaledPixels,
pub spread_radius: ScaledPixels,
}
impl Ord for Shadow {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.order.cmp(&other.order)
}
}
impl PartialOrd for Shadow {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl From<Shadow> for Primitive {
fn from(shadow: Shadow) -> Self {
Primitive::Shadow(shadow)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct MonochromeSprite {

View file

@ -1,7 +1,7 @@
use crate::{
phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle,
FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle, Shadow,
SharedString, Size, SizeRefinement, ViewContext, WindowContext,
};
use refineable::Refineable;
@ -89,10 +89,21 @@ pub struct Style {
#[refineable]
pub corner_radii: Corners<AbsoluteLength>,
/// Box Shadow of the element
pub box_shadow: Option<BoxShadow>,
/// TEXT
pub text: TextStyleRefinement,
}
#[derive(Clone, Debug)]
pub struct BoxShadow {
pub color: Hsla,
pub offset: Point<Pixels>,
pub blur_radius: Pixels,
pub spread_radius: Pixels,
}
#[derive(Refineable, Clone, Debug)]
#[refineable(debug)]
pub struct TextStyle {
@ -233,6 +244,28 @@ impl Style {
let rem_size = cx.rem_size();
let scale = cx.scale_factor();
if let Some(shadow) = self.box_shadow.as_ref() {
let layer_id = cx.current_layer_id();
let content_mask = cx.content_mask();
let mut shadow_bounds = bounds;
shadow_bounds.origin += shadow.offset;
cx.scene().insert(
layer_id,
Shadow {
order,
bounds: shadow_bounds.scale(scale),
content_mask: content_mask.scale(scale),
corner_radii: self
.corner_radii
.to_pixels(bounds.size, rem_size)
.scale(scale),
color: shadow.color,
blur_radius: shadow.blur_radius.scale(scale),
spread_radius: shadow.spread_radius.scale(scale),
},
);
}
let background_color = self.fill.as_ref().and_then(Fill::color);
if background_color.is_some() || self.is_border_visible() {
let layer_id = cx.current_layer_id();
@ -247,10 +280,9 @@ impl Style {
border_color: self.border_color.unwrap_or_default(),
corner_radii: self
.corner_radii
.map(|length| length.to_pixels(rem_size).scale(scale)),
border_widths: self
.border_widths
.map(|length| length.to_pixels(rem_size).scale(scale)),
.to_pixels(bounds.size, rem_size)
.scale(scale),
border_widths: self.border_widths.to_pixels(rem_size).scale(scale),
},
);
}
@ -296,6 +328,7 @@ impl Default for Style {
fill: None,
border_color: None,
corner_radii: Corners::default(),
box_shadow: None,
text: TextStyleRefinement::default(),
}
}

View file

@ -1,6 +1,7 @@
use crate::{
self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent,
Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement,
self as gpui3, hsla, point, px, relative, rems, AlignItems, BoxShadow, Display, Fill,
FlexDirection, Hsla, JustifyContent, Length, Position, SharedString, Style, StyleRefinement,
Styled, TextStyleRefinement,
};
pub trait StyleHelpers: Styled<Style = Style> {
@ -213,6 +214,58 @@ pub trait StyleHelpers: Styled<Style = Style> {
self
}
fn shadow(mut self) -> Self
where
Self: Sized,
{
self
}
fn shadow_sm(mut self) -> Self
where
Self: Sized,
{
self.declared_style().box_shadow = Some(BoxShadow {
color: hsla(0., 0., 0., 1.),
offset: point(px(0.), px(0.)),
blur_radius: px(1.),
spread_radius: px(0.),
});
self
}
fn shadow_md(mut self) -> Self
where
Self: Sized,
{
todo!();
self
}
fn shadow_lg(mut self) -> Self
where
Self: Sized,
{
todo!();
self
}
fn shadow_xl(mut self) -> Self
where
Self: Sized,
{
todo!();
self
}
fn shadow_2xl(mut self) -> Self
where
Self: Sized,
{
todo!();
self
}
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
let style: &mut StyleRefinement = self.declared_style();
&mut style.text

View file

@ -168,7 +168,8 @@ impl CollabPanel {
.uri(avatar_uri)
.size_3p5()
.rounded_full()
.fill(theme.middle.positive.default.foreground),
.fill(theme.middle.positive.default.foreground)
.shadow_sm(),
)
.child(label),
)