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(), "AtlasTile".into(),
"QuadInputIndex".into(), "QuadInputIndex".into(),
"Quad".into(), "Quad".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
"SpriteInputIndex".into(), "SpriteInputIndex".into(),
"MonochromeSprite".into(), "MonochromeSprite".into(),
"PolychromeSprite".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)] #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
#[refineable(debug)] #[refineable(debug)]
#[repr(C)] #[repr(C)]
@ -480,8 +491,8 @@ pub struct Corners<T: Clone + Debug> {
} }
impl Corners<AbsoluteLength> { impl Corners<AbsoluteLength> {
pub fn to_pixels(&self, bounds: Bounds<Pixels>, rem_size: Pixels) -> Corners<Pixels> { pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
let max = bounds.size.width.max(bounds.size.height) / 2.; let max = size.width.max(size.height) / 2.;
Corners { Corners {
top_left: self.top_left.to_pixels(rem_size).min(max), top_left: self.top_left.to_pixels(rem_size).min(max),
top_right: self.top_right.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::{ use crate::{
point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite, point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
Quad, Scene, Size, Quad, Scene, Shadow, Size,
}; };
use cocoa::{ use cocoa::{
base::{NO, YES}, base::{NO, YES},
@ -18,6 +18,7 @@ pub struct MetalRenderer {
layer: metal::MetalLayer, layer: metal::MetalLayer,
command_queue: CommandQueue, command_queue: CommandQueue,
quads_pipeline_state: metal::RenderPipelineState, quads_pipeline_state: metal::RenderPipelineState,
shadows_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,
@ -90,6 +91,14 @@ impl MetalRenderer {
"quad_fragment", "quad_fragment",
PIXEL_FORMAT, 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( let monochrome_sprites_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
@ -114,6 +123,7 @@ impl MetalRenderer {
layer, layer,
command_queue, command_queue,
quads_pipeline_state, quads_pipeline_state,
shadows_pipeline_state,
monochrome_sprites_pipeline_state, monochrome_sprites_pipeline_state,
polychrome_sprites_pipeline_state, polychrome_sprites_pipeline_state,
unit_vertices, unit_vertices,
@ -183,6 +193,14 @@ impl MetalRenderer {
command_encoder, command_encoder,
); );
} }
crate::PrimitiveBatch::Shadows(shadows) => {
self.draw_shadows(
shadows,
&mut instance_offset,
viewport_size,
command_encoder,
);
}
crate::PrimitiveBatch::MonochromeSprites { crate::PrimitiveBatch::MonochromeSprites {
texture_id, texture_id,
sprites, sprites,
@ -279,6 +297,66 @@ impl MetalRenderer {
*offset = next_offset; *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( fn draw_monochrome_sprites(
&mut self, &mut self,
texture_id: AtlasTextureId, texture_id: AtlasTextureId,
@ -469,6 +547,13 @@ enum QuadInputIndex {
ViewportSize = 2, ViewportSize = 2,
} }
#[repr(C)]
enum ShadowInputIndex {
Vertices = 0,
Shadows = 1,
ViewportSize = 2,
}
#[repr(C)] #[repr(C)]
enum SpriteInputIndex { enum SpriteInputIndex {
Vertices = 0, Vertices = 0,

View file

@ -11,6 +11,9 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
constant Size_DevicePixels *atlas_size); constant Size_DevicePixels *atlas_size);
float quad_sdf(float2 point, Bounds_ScaledPixels bounds, float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
Corners_ScaledPixels corner_radii); 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 { struct QuadVertexOutput {
float4 position [[position]]; 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)); 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 { struct MonochromeSpriteVertexOutput {
float4 position [[position]]; float4 position [[position]];
float2 tile_position; float2 tile_position;
@ -308,3 +396,24 @@ float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
return distance; 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) => { Primitive::Quad(quad) => {
layer.quads.push(quad); layer.quads.push(quad);
} }
Primitive::Shadow(shadow) => {
layer.shadows.push(shadow);
}
Primitive::MonochromeSprite(sprite) => { Primitive::MonochromeSprite(sprite) => {
layer.monochrome_sprites.push(sprite); layer.monochrome_sprites.push(sprite);
} }
@ -55,6 +58,7 @@ impl Scene {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct SceneLayer { pub(crate) struct SceneLayer {
pub quads: Vec<Quad>, pub quads: Vec<Quad>,
pub shadows: Vec<Shadow>,
pub monochrome_sprites: Vec<MonochromeSprite>, pub monochrome_sprites: Vec<MonochromeSprite>,
pub polychrome_sprites: Vec<PolychromeSprite>, pub polychrome_sprites: Vec<PolychromeSprite>,
} }
@ -68,6 +72,9 @@ impl SceneLayer {
quads: &self.quads, quads: &self.quads,
quads_start: 0, quads_start: 0,
quads_iter: self.quads.iter().peekable(), 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: &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(),
@ -82,6 +89,9 @@ struct BatchIterator<'a> {
quads: &'a [Quad], quads: &'a [Quad],
quads_start: usize, quads_start: usize,
quads_iter: Peekable<slice::Iter<'a, Quad>>, 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: &'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>>,
@ -96,6 +106,10 @@ impl<'a> Iterator for BatchIterator<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut kinds_and_orders = [ let mut kinds_and_orders = [
(PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)), (PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
(
PrimitiveKind::Shadow,
self.shadows_iter.peek().map(|s| s.order),
),
( (
PrimitiveKind::MonochromeSprite, PrimitiveKind::MonochromeSprite,
self.monochrome_sprites_iter.peek().map(|s| s.order), self.monochrome_sprites_iter.peek().map(|s| s.order),
@ -127,6 +141,19 @@ impl<'a> Iterator for BatchIterator<'a> {
self.quads_start = quads_end; self.quads_start = quads_end;
Some(PrimitiveBatch::Quads(&self.quads[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 => { 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;
@ -168,6 +195,7 @@ impl<'a> Iterator for BatchIterator<'a> {
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PrimitiveKind { pub enum PrimitiveKind {
Quad, Quad,
Shadow,
MonochromeSprite, MonochromeSprite,
PolychromeSprite, PolychromeSprite,
} }
@ -175,12 +203,15 @@ pub enum PrimitiveKind {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Primitive { pub enum Primitive {
Quad(Quad), Quad(Quad),
Shadow(Shadow),
MonochromeSprite(MonochromeSprite), MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite), PolychromeSprite(PolychromeSprite),
} }
#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> { pub(crate) enum PrimitiveBatch<'a> {
Quads(&'a [Quad]), Quads(&'a [Quad]),
Shadows(&'a [Shadow]),
MonochromeSprites { MonochromeSprites {
texture_id: AtlasTextureId, texture_id: AtlasTextureId,
sprites: &'a [MonochromeSprite], 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)] #[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)] #[repr(C)]
pub struct MonochromeSprite { pub struct MonochromeSprite {

View file

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

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent, self as gpui3, hsla, point, px, relative, rems, AlignItems, BoxShadow, Display, Fill,
Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement, FlexDirection, Hsla, JustifyContent, Length, Position, SharedString, Style, StyleRefinement,
Styled, TextStyleRefinement,
}; };
pub trait StyleHelpers: Styled<Style = Style> { pub trait StyleHelpers: Styled<Style = Style> {
@ -213,6 +214,58 @@ pub trait StyleHelpers: Styled<Style = Style> {
self 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> { fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
let style: &mut StyleRefinement = self.declared_style(); let style: &mut StyleRefinement = self.declared_style();
&mut style.text &mut style.text

View file

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