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