Start on rendering shadows
This still doesn't work properly because shadows are rendered beneath quads and we still don't have a layering mechanism. Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
45c1337c84
commit
bfc57cb4f6
6 changed files with 201 additions and 16 deletions
|
@ -84,9 +84,11 @@ fn compile_metal_shaders() {
|
||||||
fn generate_shader_bindings() {
|
fn generate_shader_bindings() {
|
||||||
let bindings = bindgen::Builder::default()
|
let bindings = bindgen::Builder::default()
|
||||||
.header(SHADER_HEADER_PATH)
|
.header(SHADER_HEADER_PATH)
|
||||||
|
.whitelist_type("GPUIUniforms")
|
||||||
.whitelist_type("GPUIQuadInputIndex")
|
.whitelist_type("GPUIQuadInputIndex")
|
||||||
.whitelist_type("GPUIQuad")
|
.whitelist_type("GPUIQuad")
|
||||||
.whitelist_type("GPUIQuadUniforms")
|
.whitelist_type("GPUIShadowInputIndex")
|
||||||
|
.whitelist_type("GPUIShadow")
|
||||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
||||||
.generate()
|
.generate()
|
||||||
.expect("unable to generate bindings");
|
.expect("unable to generate bindings");
|
||||||
|
|
|
@ -3,7 +3,7 @@ use pathfinder_geometry::rect::RectF;
|
||||||
use crate::{
|
use crate::{
|
||||||
color::ColorU,
|
color::ColorU,
|
||||||
geometry::vector::{vec2f, Vector2F},
|
geometry::vector::{vec2f, Vector2F},
|
||||||
scene::{Border, Quad},
|
scene::{self, Border, Quad},
|
||||||
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
||||||
SizeConstraint,
|
SizeConstraint,
|
||||||
};
|
};
|
||||||
|
@ -150,6 +150,14 @@ impl Element for Container {
|
||||||
_: &mut Self::LayoutState,
|
_: &mut Self::LayoutState,
|
||||||
ctx: &mut PaintContext,
|
ctx: &mut PaintContext,
|
||||||
) -> Self::PaintState {
|
) -> Self::PaintState {
|
||||||
|
if let Some(shadow) = self.shadow.as_ref() {
|
||||||
|
ctx.scene.push_shadow(scene::Shadow {
|
||||||
|
bounds: bounds + shadow.offset,
|
||||||
|
corner_radius: self.corner_radius,
|
||||||
|
sigma: shadow.blur,
|
||||||
|
color: shadow.color,
|
||||||
|
});
|
||||||
|
}
|
||||||
ctx.scene.push_quad(Quad {
|
ctx.scene.push_quad(Quad {
|
||||||
bounds,
|
bounds,
|
||||||
background: self.background_color,
|
background: self.background_color,
|
||||||
|
|
|
@ -14,7 +14,8 @@ const INSTANCE_BUFFER_SIZE: u64 = 1024 * 1024;
|
||||||
|
|
||||||
pub struct Renderer {
|
pub struct Renderer {
|
||||||
quad_pipeline_state: metal::RenderPipelineState,
|
quad_pipeline_state: metal::RenderPipelineState,
|
||||||
quad_vertices: metal::Buffer,
|
shadow_pipeline_state: metal::RenderPipelineState,
|
||||||
|
unit_vertices: metal::Buffer,
|
||||||
instances: metal::Buffer,
|
instances: metal::Buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ impl Renderer {
|
||||||
.new_library_with_data(SHADERS_METALLIB)
|
.new_library_with_data(SHADERS_METALLIB)
|
||||||
.map_err(|message| anyhow!("error building metal library: {}", message))?;
|
.map_err(|message| anyhow!("error building metal library: {}", message))?;
|
||||||
|
|
||||||
let quad_vertices = [
|
let unit_vertices = [
|
||||||
(0., 0.).to_float2(),
|
(0., 0.).to_float2(),
|
||||||
(1., 0.).to_float2(),
|
(1., 0.).to_float2(),
|
||||||
(0., 1.).to_float2(),
|
(0., 1.).to_float2(),
|
||||||
|
@ -32,9 +33,9 @@ impl Renderer {
|
||||||
(1., 0.).to_float2(),
|
(1., 0.).to_float2(),
|
||||||
(1., 1.).to_float2(),
|
(1., 1.).to_float2(),
|
||||||
];
|
];
|
||||||
let quad_vertices = device.new_buffer_with_data(
|
let unit_vertices = device.new_buffer_with_data(
|
||||||
quad_vertices.as_ptr() as *const c_void,
|
unit_vertices.as_ptr() as *const c_void,
|
||||||
(quad_vertices.len() * mem::size_of::<shaders::vector_float2>()) as u64,
|
(unit_vertices.len() * mem::size_of::<shaders::vector_float2>()) as u64,
|
||||||
MTLResourceOptions::StorageModeManaged,
|
MTLResourceOptions::StorageModeManaged,
|
||||||
);
|
);
|
||||||
let instances =
|
let instances =
|
||||||
|
@ -49,7 +50,15 @@ impl Renderer {
|
||||||
"quad_fragment",
|
"quad_fragment",
|
||||||
pixel_format,
|
pixel_format,
|
||||||
)?,
|
)?,
|
||||||
quad_vertices,
|
shadow_pipeline_state: build_pipeline_state(
|
||||||
|
device,
|
||||||
|
&library,
|
||||||
|
"shadow",
|
||||||
|
"shadow_vertex",
|
||||||
|
"shadow_fragment",
|
||||||
|
pixel_format,
|
||||||
|
)?,
|
||||||
|
unit_vertices,
|
||||||
instances,
|
instances,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -65,16 +74,70 @@ impl Renderer {
|
||||||
});
|
});
|
||||||
|
|
||||||
for layer in scene.layers() {
|
for layer in scene.layers() {
|
||||||
|
self.render_shadows(scene, layer, ctx);
|
||||||
self.render_quads(scene, layer, ctx);
|
self.render_quads(scene, layer, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_shadows(&mut self, scene: &Scene, layer: &Layer, ctx: &RenderContext) {
|
||||||
|
ctx.command_encoder
|
||||||
|
.set_render_pipeline_state(&self.shadow_pipeline_state);
|
||||||
|
ctx.command_encoder.set_vertex_buffer(
|
||||||
|
shaders::GPUIShadowInputIndex_GPUIShadowInputIndexVertices as u64,
|
||||||
|
Some(&self.unit_vertices),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
ctx.command_encoder.set_vertex_buffer(
|
||||||
|
shaders::GPUIShadowInputIndex_GPUIShadowInputIndexShadows as u64,
|
||||||
|
Some(&self.instances),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
ctx.command_encoder.set_vertex_bytes(
|
||||||
|
shaders::GPUIShadowInputIndex_GPUIShadowInputIndexUniforms as u64,
|
||||||
|
mem::size_of::<shaders::GPUIUniforms>() as u64,
|
||||||
|
[shaders::GPUIUniforms {
|
||||||
|
viewport_size: ctx.drawable_size.to_float2(),
|
||||||
|
}]
|
||||||
|
.as_ptr() as *const c_void,
|
||||||
|
);
|
||||||
|
|
||||||
|
let batch_size = self.instances.length() as usize / mem::size_of::<shaders::GPUIShadow>();
|
||||||
|
|
||||||
|
let buffer_contents = self.instances.contents() as *mut shaders::GPUIShadow;
|
||||||
|
for shadow_batch in layer.shadows().chunks(batch_size) {
|
||||||
|
for (ix, shadow) in shadow_batch.iter().enumerate() {
|
||||||
|
let shape_bounds = shadow.bounds * scene.scale_factor();
|
||||||
|
let shader_shadow = shaders::GPUIShadow {
|
||||||
|
origin: shape_bounds.origin().to_float2(),
|
||||||
|
size: shape_bounds.size().to_float2(),
|
||||||
|
corner_radius: shadow.corner_radius,
|
||||||
|
sigma: shadow.sigma,
|
||||||
|
color: shadow.color.to_uchar4(),
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
*(buffer_contents.offset(ix as isize)) = shader_shadow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.instances.did_modify_range(NSRange {
|
||||||
|
location: 0,
|
||||||
|
length: (shadow_batch.len() * mem::size_of::<shaders::GPUIShadow>()) as u64,
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.command_encoder.draw_primitives_instanced(
|
||||||
|
metal::MTLPrimitiveType::Triangle,
|
||||||
|
0,
|
||||||
|
6,
|
||||||
|
shadow_batch.len() as u64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_quads(&mut self, scene: &Scene, layer: &Layer, ctx: &RenderContext) {
|
fn render_quads(&mut self, scene: &Scene, layer: &Layer, ctx: &RenderContext) {
|
||||||
ctx.command_encoder
|
ctx.command_encoder
|
||||||
.set_render_pipeline_state(&self.quad_pipeline_state);
|
.set_render_pipeline_state(&self.quad_pipeline_state);
|
||||||
ctx.command_encoder.set_vertex_buffer(
|
ctx.command_encoder.set_vertex_buffer(
|
||||||
shaders::GPUIQuadInputIndex_GPUIQuadInputIndexVertices as u64,
|
shaders::GPUIQuadInputIndex_GPUIQuadInputIndexVertices as u64,
|
||||||
Some(&self.quad_vertices),
|
Some(&self.unit_vertices),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
ctx.command_encoder.set_vertex_buffer(
|
ctx.command_encoder.set_vertex_buffer(
|
||||||
|
@ -84,8 +147,8 @@ impl Renderer {
|
||||||
);
|
);
|
||||||
ctx.command_encoder.set_vertex_bytes(
|
ctx.command_encoder.set_vertex_bytes(
|
||||||
shaders::GPUIQuadInputIndex_GPUIQuadInputIndexUniforms as u64,
|
shaders::GPUIQuadInputIndex_GPUIQuadInputIndexUniforms as u64,
|
||||||
mem::size_of::<shaders::GPUIQuadUniforms>() as u64,
|
mem::size_of::<shaders::GPUIUniforms>() as u64,
|
||||||
[shaders::GPUIQuadUniforms {
|
[shaders::GPUIUniforms {
|
||||||
viewport_size: ctx.drawable_size.to_float2(),
|
viewport_size: ctx.drawable_size.to_float2(),
|
||||||
}]
|
}]
|
||||||
.as_ptr() as *const c_void,
|
.as_ptr() as *const c_void,
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
#include <simd/simd.h>
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
vector_float2 viewport_size;
|
||||||
|
} GPUIUniforms;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GPUIQuadInputIndexVertices = 0,
|
GPUIQuadInputIndexVertices = 0,
|
||||||
GPUIQuadInputIndexQuads = 1,
|
GPUIQuadInputIndexQuads = 1,
|
||||||
|
@ -18,6 +22,16 @@ typedef struct {
|
||||||
float corner_radius;
|
float corner_radius;
|
||||||
} GPUIQuad;
|
} GPUIQuad;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GPUIShadowInputIndexVertices = 0,
|
||||||
|
GPUIShadowInputIndexShadows = 1,
|
||||||
|
GPUIShadowInputIndexUniforms = 2,
|
||||||
|
} GPUIShadowInputIndex;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
vector_float2 viewport_size;
|
vector_float2 origin;
|
||||||
} GPUIQuadUniforms;
|
vector_float2 size;
|
||||||
|
float corner_radius;
|
||||||
|
float sigma;
|
||||||
|
vector_uchar4 color;
|
||||||
|
} GPUIShadow;
|
||||||
|
|
|
@ -7,6 +7,31 @@ float4 coloru_to_colorf(uchar4 coloru) {
|
||||||
return float4(coloru) / float4(0xff, 0xff, 0xff, 0xff);
|
return float4(coloru) / float4(0xff, 0xff, 0xff, 0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float4 to_device_position(float2 pixel_position, float2 viewport_size) {
|
||||||
|
return float4(pixel_position / viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A standard gaussian function, used for weighting samples
|
||||||
|
float gaussian(float x, float sigma) {
|
||||||
|
return exp(-(x * x) / (2.0 * sigma * sigma)) / (sqrt(2.0 * 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 + (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 halfSize) {
|
||||||
|
float delta = min(halfSize.y - corner - abs(y), 0.0);
|
||||||
|
float curved = halfSize.x - corner + sqrt(max(0.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;
|
||||||
|
}
|
||||||
|
|
||||||
struct QuadFragmentInput {
|
struct QuadFragmentInput {
|
||||||
float4 position [[position]];
|
float4 position [[position]];
|
||||||
GPUIQuad quad;
|
GPUIQuad quad;
|
||||||
|
@ -17,12 +42,12 @@ vertex QuadFragmentInput quad_vertex(
|
||||||
uint quad_id [[instance_id]],
|
uint quad_id [[instance_id]],
|
||||||
constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
|
constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
|
||||||
constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
|
constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
|
||||||
constant GPUIQuadUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
||||||
) {
|
) {
|
||||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
GPUIQuad quad = quads[quad_id];
|
GPUIQuad quad = quads[quad_id];
|
||||||
float2 position = unit_vertex * quad.size + quad.origin;
|
float2 position = unit_vertex * quad.size + quad.origin;
|
||||||
float4 device_position = float4(position / uniforms->viewport_size * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);
|
float4 device_position = to_device_position(position, uniforms->viewport_size);
|
||||||
|
|
||||||
return QuadFragmentInput {
|
return QuadFragmentInput {
|
||||||
device_position,
|
device_position,
|
||||||
|
@ -32,7 +57,7 @@ vertex QuadFragmentInput quad_vertex(
|
||||||
|
|
||||||
fragment float4 quad_fragment(
|
fragment float4 quad_fragment(
|
||||||
QuadFragmentInput input [[stage_in]],
|
QuadFragmentInput input [[stage_in]],
|
||||||
constant GPUIQuadUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
||||||
) {
|
) {
|
||||||
float2 half_size = input.quad.size / 2.;
|
float2 half_size = input.quad.size / 2.;
|
||||||
float2 center = input.quad.origin + half_size;
|
float2 center = input.quad.origin + half_size;
|
||||||
|
@ -57,3 +82,56 @@ fragment float4 quad_fragment(
|
||||||
float4 coverage = float4(1.0, 1.0, 1.0, saturate(0.5 - distance));
|
float4 coverage = float4(1.0, 1.0, 1.0, saturate(0.5 - distance));
|
||||||
return coverage * color;
|
return coverage * color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ShadowFragmentInput {
|
||||||
|
float4 position [[position]];
|
||||||
|
GPUIShadow shadow;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex ShadowFragmentInput shadow_vertex(
|
||||||
|
uint unit_vertex_id [[vertex_id]],
|
||||||
|
uint shadow_id [[instance_id]],
|
||||||
|
constant float2 *unit_vertices [[buffer(GPUIShadowInputIndexVertices)]],
|
||||||
|
constant GPUIShadow *shadows [[buffer(GPUIShadowInputIndexShadows)]],
|
||||||
|
constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
|
||||||
|
) {
|
||||||
|
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||||
|
GPUIShadow shadow = shadows[shadow_id];
|
||||||
|
|
||||||
|
float margin = 3. * shadow.sigma;
|
||||||
|
float2 position = unit_vertex * (shadow.size + 2.0 * margin) + shadow.origin - margin;
|
||||||
|
float4 device_position = to_device_position(position, uniforms->viewport_size);
|
||||||
|
|
||||||
|
return ShadowFragmentInput {
|
||||||
|
device_position,
|
||||||
|
shadow,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment float4 shadow_fragment(
|
||||||
|
ShadowFragmentInput input [[stage_in]],
|
||||||
|
constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
|
||||||
|
) {
|
||||||
|
float sigma = input.shadow.sigma;
|
||||||
|
float corner_radius = input.shadow.corner_radius;
|
||||||
|
float2 half_size = input.shadow.size / 2.;
|
||||||
|
float2 center = input.shadow.origin + half_size;
|
||||||
|
float2 point = input.position.xy - center;
|
||||||
|
|
||||||
|
// 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. * sigma, low, high);
|
||||||
|
float end = clamp(3. * sigma, 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.0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
alpha += blur_along_x(point.x, point.y - y, sigma, corner_radius, half_size) * gaussian(y, sigma) * step;
|
||||||
|
y += step;
|
||||||
|
}
|
||||||
|
|
||||||
|
return float4(1., 1., 1., alpha) * coloru_to_colorf(input.shadow.color);
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ pub struct Scene {
|
||||||
pub struct Layer {
|
pub struct Layer {
|
||||||
clip_bounds: Option<RectF>,
|
clip_bounds: Option<RectF>,
|
||||||
quads: Vec<Quad>,
|
quads: Vec<Quad>,
|
||||||
|
shadows: Vec<Shadow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
@ -21,6 +22,13 @@ pub struct Quad {
|
||||||
pub corner_radius: f32,
|
pub corner_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Shadow {
|
||||||
|
pub bounds: RectF,
|
||||||
|
pub corner_radius: f32,
|
||||||
|
pub sigma: f32,
|
||||||
|
pub color: ColorU,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Debug)]
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
pub struct Border {
|
pub struct Border {
|
||||||
pub width: f32,
|
pub width: f32,
|
||||||
|
@ -61,6 +69,10 @@ impl Scene {
|
||||||
self.active_layer().push_quad(quad)
|
self.active_layer().push_quad(quad)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn push_shadow(&mut self, shadow: Shadow) {
|
||||||
|
self.active_layer().push_shadow(shadow)
|
||||||
|
}
|
||||||
|
|
||||||
fn active_layer(&mut self) -> &mut Layer {
|
fn active_layer(&mut self) -> &mut Layer {
|
||||||
&mut self.layers[*self.active_layer_stack.last().unwrap()]
|
&mut self.layers[*self.active_layer_stack.last().unwrap()]
|
||||||
}
|
}
|
||||||
|
@ -74,6 +86,14 @@ impl Layer {
|
||||||
pub fn quads(&self) -> &[Quad] {
|
pub fn quads(&self) -> &[Quad] {
|
||||||
self.quads.as_slice()
|
self.quads.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_shadow(&mut self, shadow: Shadow) {
|
||||||
|
self.shadows.push(shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shadows(&self) -> &[Shadow] {
|
||||||
|
self.shadows.as_slice()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Border {
|
impl Border {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue