1use crate::node::{NodeId, NodeProperty, PortRef};
2
3use crate::gpu::{
4 BoundFrame, CompiledOutput, FrameBindContext, FrameBinding, GpuCompileNode, GpuFrameBindNode,
5 RasterHandle, compiler,
6};
7
8pub(crate) const SHADER: &str = include_str!("shadow.wgsl");
9
10#[derive(Debug, Clone, lumen_macros::Node)]
12#[node(kind = "shadow", name = "Shadow", category = "processing")]
13pub struct Shadow {
14 pub id: NodeId,
15 #[property(kind = "float", name = "Offset X", step = 1)]
17 pub offset_x: NodeProperty,
18 #[property(kind = "float", name = "Offset Y", step = 1)]
20 pub offset_y: NodeProperty,
21 #[property(kind = "float", name = "Blur radius", min = 0, step = 0.5)]
23 pub radius: NodeProperty,
24 #[property(kind = "color")]
26 pub color: NodeProperty,
27 #[property(kind = "float", min = 0, max = 1, step = 0.05)]
29 pub opacity: NodeProperty,
30 #[input()]
31 pub source: PortRef,
32}
33
34impl Default for Shadow {
35 fn default() -> Self {
36 Self {
37 id: NodeId::new(0),
38 offset_x: NodeProperty::Float(8.0),
39 offset_y: NodeProperty::Float(8.0),
40 radius: NodeProperty::Float(8.0),
41 color: NodeProperty::Color([0, 0, 0, 255]),
42 opacity: NodeProperty::Float(0.5),
43 source: PortRef::empty(),
44 }
45 }
46}
47
48impl GpuCompileNode for Shadow {
49 fn compile_gpu(
50 &self,
51 ctx: &mut crate::gpu::CompileContext<'_>,
52 port: &PortRef,
53 ) -> crate::Result<CompiledOutput> {
54 if port.port != "output" {
55 return Err(ctx.missing_output(self.id, &port.port));
56 }
57
58 let source = ctx
59 .compile_port(&self.source)?
60 .into_raster(self.source.id, &self.source.port)?;
61 let size = source.domain.storage_size;
62 let temp = ctx.builder_mut().texture_for(
63 lumen_gpu::NodeKey(self.id.0),
64 Some(format!("shadow:{}:horizontal", self.id.0)),
65 lumen_gpu::TextureDesc::storage(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
66 );
67 let texture = ctx.builder_mut().texture_for(
68 lumen_gpu::NodeKey(self.id.0),
69 Some(format!("shadow:{}:output", self.id.0)),
70 lumen_gpu::TextureDesc::storage(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
71 );
72 let params = ctx.builder_mut().buffer_for(
73 lumen_gpu::NodeKey(self.id.0),
74 Some(format!("shadow:{}:params", self.id.0)),
75 lumen_gpu::BufferDesc::uniform(std::mem::size_of::<compiler::ShadowParams>() as u64),
76 );
77 let bind_groups = lumen_gpu::BindGroupLayoutSpec::single(vec![
78 lumen_gpu::BindingLayoutEntry::texture(0, lumen_gpu::wgpu::ShaderStages::COMPUTE),
79 lumen_gpu::BindingLayoutEntry::texture(1, lumen_gpu::wgpu::ShaderStages::COMPUTE),
80 lumen_gpu::BindingLayoutEntry::uniform(2, lumen_gpu::wgpu::ShaderStages::COMPUTE),
81 lumen_gpu::BindingLayoutEntry::storage_texture(
82 3,
83 lumen_gpu::wgpu::ShaderStages::COMPUTE,
84 lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
85 lumen_gpu::wgpu::StorageTextureAccess::WriteOnly,
86 ),
87 ]);
88 let horizontal = ctx.builder_mut().program_for(
89 lumen_gpu::NodeKey(self.id.0),
90 lumen_gpu::ProgramDesc::Compute(lumen_gpu::ComputeProgramDesc {
91 label: Some("shadow-horizontal".to_string()),
92 shader: SHADER.to_string(),
93 entry: "horizontal_main".to_string(),
94 bind_groups: bind_groups.clone(),
95 }),
96 );
97 let vertical = ctx.builder_mut().program_for(
98 lumen_gpu::NodeKey(self.id.0),
99 lumen_gpu::ProgramDesc::Compute(lumen_gpu::ComputeProgramDesc {
100 label: Some("shadow-vertical".to_string()),
101 shader: SHADER.to_string(),
102 entry: "vertical_main".to_string(),
103 bind_groups,
104 }),
105 );
106 ctx.builder_mut().compute_pass(lumen_gpu::ComputePassDesc {
107 label: Some(format!("shadow:{}:horizontal", self.id.0)),
108 owner: Some(lumen_gpu::NodeKey(self.id.0)),
109 program: horizontal,
110 bindings: vec![
111 lumen_gpu::Binding::sampled_texture(0, 0, source.texture),
112 lumen_gpu::Binding::sampled_texture(0, 1, source.texture),
113 lumen_gpu::Binding::uniform(0, 2, params),
114 lumen_gpu::Binding::storage_texture(0, 3, temp),
115 ],
116 dispatch: compiler::dispatch_for(size).into(),
117 });
118 ctx.builder_mut().compute_pass(lumen_gpu::ComputePassDesc {
119 label: Some(format!("shadow:{}:vertical", self.id.0)),
120 owner: Some(lumen_gpu::NodeKey(self.id.0)),
121 program: vertical,
122 bindings: vec![
123 lumen_gpu::Binding::sampled_texture(0, 0, source.texture),
124 lumen_gpu::Binding::sampled_texture(0, 1, temp),
125 lumen_gpu::Binding::uniform(0, 2, params),
126 lumen_gpu::Binding::storage_texture(0, 3, texture),
127 ],
128 dispatch: compiler::dispatch_for(size).into(),
129 });
130 ctx.builder_mut().param(
131 lumen_gpu::ParamKey {
132 owner: lumen_gpu::NodeKey(self.id.0),
133 slot: 0,
134 },
135 lumen_gpu::ParamTarget::Buffer(params),
136 );
137 ctx.push_frame_binding(FrameBinding::Shadow {
138 node_id: self.id,
139 offset_x: self.offset_x.clone(),
140 offset_y: self.offset_y.clone(),
141 radius: self.radius.clone(),
142 color: self.color.clone(),
143 opacity: self.opacity.clone(),
144 buffer: params,
145 });
146 Ok(CompiledOutput::Raster(RasterHandle {
147 texture,
148 domain: source.domain,
149 metadata: source.metadata,
150 }))
151 }
152}
153
154impl GpuFrameBindNode for Shadow {
155 fn bind_gpu_frame(
156 &self,
157 ctx: &FrameBindContext<'_>,
158 binding: &FrameBinding,
159 bound: &mut BoundFrame,
160 ) -> crate::Result<()> {
161 let FrameBinding::Shadow {
162 node_id,
163 offset_x,
164 offset_y,
165 radius,
166 color,
167 opacity,
168 buffer,
169 } = binding
170 else {
171 return Ok(());
172 };
173 let color = color.resolve_color(*node_id, "color", &ctx.expr_context(*node_id, "color"))?;
174 let color = compiler::ColorParams::from_rgba8(color).color;
175 let params = compiler::ShadowParams {
176 color,
177 values: [
178 offset_x.resolve_float(
179 *node_id,
180 "offset_x",
181 &ctx.expr_context(*node_id, "offset_x"),
182 )? as f32,
183 offset_y.resolve_float(
184 *node_id,
185 "offset_y",
186 &ctx.expr_context(*node_id, "offset_y"),
187 )? as f32,
188 radius
189 .resolve_float(*node_id, "radius", &ctx.expr_context(*node_id, "radius"))?
190 .round()
191 .clamp(0.0, 32.0) as f32,
192 opacity.resolve_float(
193 *node_id,
194 "opacity",
195 &ctx.expr_context(*node_id, "opacity"),
196 )? as f32,
197 ],
198 };
199 bound.write_buffer(*buffer, 0, bytemuck::bytes_of(¶ms));
200 Ok(())
201 }
202}