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