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() {
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header(SHADER_HEADER_PATH)
|
||||
.whitelist_type("GPUIUniforms")
|
||||
.whitelist_type("GPUIQuadInputIndex")
|
||||
.whitelist_type("GPUIQuad")
|
||||
.whitelist_type("GPUIQuadUniforms")
|
||||
.whitelist_type("GPUIShadowInputIndex")
|
||||
.whitelist_type("GPUIShadow")
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
|
||||
.generate()
|
||||
.expect("unable to generate bindings");
|
||||
|
|
|
@ -3,7 +3,7 @@ use pathfinder_geometry::rect::RectF;
|
|||
use crate::{
|
||||
color::ColorU,
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
scene::{Border, Quad},
|
||||
scene::{self, Border, Quad},
|
||||
AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
||||
SizeConstraint,
|
||||
};
|
||||
|
@ -150,6 +150,14 @@ impl Element for Container {
|
|||
_: &mut Self::LayoutState,
|
||||
ctx: &mut PaintContext,
|
||||
) -> 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 {
|
||||
bounds,
|
||||
background: self.background_color,
|
||||
|
|
|
@ -14,7 +14,8 @@ const INSTANCE_BUFFER_SIZE: u64 = 1024 * 1024;
|
|||
|
||||
pub struct Renderer {
|
||||
quad_pipeline_state: metal::RenderPipelineState,
|
||||
quad_vertices: metal::Buffer,
|
||||
shadow_pipeline_state: metal::RenderPipelineState,
|
||||
unit_vertices: metal::Buffer,
|
||||
instances: metal::Buffer,
|
||||
}
|
||||
|
||||
|
@ -24,7 +25,7 @@ impl Renderer {
|
|||
.new_library_with_data(SHADERS_METALLIB)
|
||||
.map_err(|message| anyhow!("error building metal library: {}", message))?;
|
||||
|
||||
let quad_vertices = [
|
||||
let unit_vertices = [
|
||||
(0., 0.).to_float2(),
|
||||
(1., 0.).to_float2(),
|
||||
(0., 1.).to_float2(),
|
||||
|
@ -32,9 +33,9 @@ impl Renderer {
|
|||
(1., 0.).to_float2(),
|
||||
(1., 1.).to_float2(),
|
||||
];
|
||||
let quad_vertices = device.new_buffer_with_data(
|
||||
quad_vertices.as_ptr() as *const c_void,
|
||||
(quad_vertices.len() * mem::size_of::<shaders::vector_float2>()) as u64,
|
||||
let unit_vertices = device.new_buffer_with_data(
|
||||
unit_vertices.as_ptr() as *const c_void,
|
||||
(unit_vertices.len() * mem::size_of::<shaders::vector_float2>()) as u64,
|
||||
MTLResourceOptions::StorageModeManaged,
|
||||
);
|
||||
let instances =
|
||||
|
@ -49,7 +50,15 @@ impl Renderer {
|
|||
"quad_fragment",
|
||||
pixel_format,
|
||||
)?,
|
||||
quad_vertices,
|
||||
shadow_pipeline_state: build_pipeline_state(
|
||||
device,
|
||||
&library,
|
||||
"shadow",
|
||||
"shadow_vertex",
|
||||
"shadow_fragment",
|
||||
pixel_format,
|
||||
)?,
|
||||
unit_vertices,
|
||||
instances,
|
||||
})
|
||||
}
|
||||
|
@ -65,16 +74,70 @@ impl Renderer {
|
|||
});
|
||||
|
||||
for layer in scene.layers() {
|
||||
self.render_shadows(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) {
|
||||
ctx.command_encoder
|
||||
.set_render_pipeline_state(&self.quad_pipeline_state);
|
||||
ctx.command_encoder.set_vertex_buffer(
|
||||
shaders::GPUIQuadInputIndex_GPUIQuadInputIndexVertices as u64,
|
||||
Some(&self.quad_vertices),
|
||||
Some(&self.unit_vertices),
|
||||
0,
|
||||
);
|
||||
ctx.command_encoder.set_vertex_buffer(
|
||||
|
@ -84,8 +147,8 @@ impl Renderer {
|
|||
);
|
||||
ctx.command_encoder.set_vertex_bytes(
|
||||
shaders::GPUIQuadInputIndex_GPUIQuadInputIndexUniforms as u64,
|
||||
mem::size_of::<shaders::GPUIQuadUniforms>() as u64,
|
||||
[shaders::GPUIQuadUniforms {
|
||||
mem::size_of::<shaders::GPUIUniforms>() as u64,
|
||||
[shaders::GPUIUniforms {
|
||||
viewport_size: ctx.drawable_size.to_float2(),
|
||||
}]
|
||||
.as_ptr() as *const c_void,
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
#include <simd/simd.h>
|
||||
|
||||
typedef struct {
|
||||
vector_float2 viewport_size;
|
||||
} GPUIUniforms;
|
||||
|
||||
typedef enum {
|
||||
GPUIQuadInputIndexVertices = 0,
|
||||
GPUIQuadInputIndexQuads = 1,
|
||||
|
@ -18,6 +22,16 @@ typedef struct {
|
|||
float corner_radius;
|
||||
} GPUIQuad;
|
||||
|
||||
typedef enum {
|
||||
GPUIShadowInputIndexVertices = 0,
|
||||
GPUIShadowInputIndexShadows = 1,
|
||||
GPUIShadowInputIndexUniforms = 2,
|
||||
} GPUIShadowInputIndex;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 viewport_size;
|
||||
} GPUIQuadUniforms;
|
||||
vector_float2 origin;
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
float4 position [[position]];
|
||||
GPUIQuad quad;
|
||||
|
@ -17,12 +42,12 @@ vertex QuadFragmentInput quad_vertex(
|
|||
uint quad_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
|
||||
constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
|
||||
constant GPUIQuadUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
||||
constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
||||
) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
GPUIQuad quad = quads[quad_id];
|
||||
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 {
|
||||
device_position,
|
||||
|
@ -32,7 +57,7 @@ vertex QuadFragmentInput quad_vertex(
|
|||
|
||||
fragment float4 quad_fragment(
|
||||
QuadFragmentInput input [[stage_in]],
|
||||
constant GPUIQuadUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
||||
constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
||||
) {
|
||||
float2 half_size = input.quad.size / 2.;
|
||||
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));
|
||||
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 {
|
||||
clip_bounds: Option<RectF>,
|
||||
quads: Vec<Quad>,
|
||||
shadows: Vec<Shadow>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -21,6 +22,13 @@ pub struct Quad {
|
|||
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)]
|
||||
pub struct Border {
|
||||
pub width: f32,
|
||||
|
@ -61,6 +69,10 @@ impl Scene {
|
|||
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 {
|
||||
&mut self.layers[*self.active_layer_stack.last().unwrap()]
|
||||
}
|
||||
|
@ -74,6 +86,14 @@ impl Layer {
|
|||
pub fn quads(&self) -> &[Quad] {
|
||||
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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue