lumen_engine/node/compositing/
boolean.rs1use 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!("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::Node)]
32#[node(kind = "boolean", name = "Boolean", category = "compositing")]
33pub struct Boolean {
34 pub id: NodeId,
35 #[property(kind = "enum", enum_type = BooleanOperation)]
37 pub operation: NodeProperty,
38 #[property(kind = "float", min = 0, max = 1, step = 0.01)]
40 pub threshold: NodeProperty,
41 #[input()]
42 pub a: PortRef,
43 #[input()]
44 pub b: PortRef,
45}
46
47impl Default for Boolean {
48 fn default() -> Self {
49 Self {
50 id: NodeId::new(0),
51 operation: NodeProperty::Int(BooleanOperation::Union as i64),
52 threshold: NodeProperty::Float(0.0),
53 a: PortRef::empty(),
54 b: PortRef::empty(),
55 }
56 }
57}
58
59impl GpuCompileNode for Boolean {
60 fn compile_gpu(
61 &self,
62 ctx: &mut crate::gpu::CompileContext<'_>,
63 port: &PortRef,
64 ) -> crate::Result<CompiledOutput> {
65 if port.port != "output" {
66 return Err(ctx.missing_output(self.id, &port.port));
67 }
68
69 let a = ctx
70 .compile_port(&self.a)?
71 .into_raster(self.a.id, &self.a.port)?;
72 let b = ctx
73 .compile_port(&self.b)?
74 .into_raster(self.b.id, &self.b.port)?;
75 let size = a.domain.storage_size;
76 let texture = ctx.builder_mut().texture_for(
77 lumen_gpu::NodeKey(self.id.0),
78 Some(format!("boolean:{}:output", self.id.0)),
79 lumen_gpu::TextureDesc::storage(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
80 );
81 let params = ctx.builder_mut().buffer_for(
82 lumen_gpu::NodeKey(self.id.0),
83 Some(format!("boolean:{}:params", self.id.0)),
84 lumen_gpu::BufferDesc::uniform(std::mem::size_of::<compiler::BooleanParams>() as u64),
85 );
86 let program = ctx.builder_mut().program_for(
87 lumen_gpu::NodeKey(self.id.0),
88 lumen_gpu::ProgramDesc::Compute(lumen_gpu::ComputeProgramDesc {
89 label: Some("boolean".to_string()),
90 shader: SHADER.to_string(),
91 entry: "cs_main".to_string(),
92 bind_groups: lumen_gpu::BindGroupLayoutSpec::single(vec![
93 lumen_gpu::BindingLayoutEntry::texture(
94 0,
95 lumen_gpu::wgpu::ShaderStages::COMPUTE,
96 ),
97 lumen_gpu::BindingLayoutEntry::texture(
98 1,
99 lumen_gpu::wgpu::ShaderStages::COMPUTE,
100 ),
101 lumen_gpu::BindingLayoutEntry::uniform(
102 2,
103 lumen_gpu::wgpu::ShaderStages::COMPUTE,
104 ),
105 lumen_gpu::BindingLayoutEntry::storage_texture(
106 3,
107 lumen_gpu::wgpu::ShaderStages::COMPUTE,
108 lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
109 lumen_gpu::wgpu::StorageTextureAccess::WriteOnly,
110 ),
111 ]),
112 }),
113 );
114 ctx.builder_mut().compute_pass(lumen_gpu::ComputePassDesc {
115 label: Some(format!("boolean:{}:apply", self.id.0)),
116 owner: Some(lumen_gpu::NodeKey(self.id.0)),
117 program,
118 bindings: vec![
119 lumen_gpu::Binding::sampled_texture(0, 0, a.texture),
120 lumen_gpu::Binding::sampled_texture(0, 1, b.texture),
121 lumen_gpu::Binding::uniform(0, 2, params),
122 lumen_gpu::Binding::storage_texture(0, 3, texture),
123 ],
124 dispatch: compiler::dispatch_for(size).into(),
125 });
126 ctx.builder_mut().param(
127 lumen_gpu::ParamKey {
128 owner: lumen_gpu::NodeKey(self.id.0),
129 slot: 0,
130 },
131 lumen_gpu::ParamTarget::Buffer(params),
132 );
133 ctx.push_frame_binding(FrameBinding::Boolean {
134 node_id: self.id,
135 operation: self.operation.clone(),
136 threshold: self.threshold.clone(),
137 buffer: params,
138 });
139
140 Ok(CompiledOutput::Raster(RasterHandle {
141 texture,
142 domain: lumen_gpu::TextureDomain::full_frame(size),
143 metadata: a.metadata,
144 }))
145 }
146}
147
148impl GpuFrameBindNode for Boolean {
149 fn bind_gpu_frame(
150 &self,
151 ctx: &FrameBindContext<'_>,
152 binding: &FrameBinding,
153 bound: &mut BoundFrame,
154 ) -> crate::Result<()> {
155 let FrameBinding::Boolean {
156 node_id,
157 operation,
158 threshold,
159 buffer,
160 } = binding
161 else {
162 return Ok(());
163 };
164 let params = compiler::BooleanParams {
165 values: [
166 BooleanOperation::from_int(operation.resolve_int(
167 *node_id,
168 "operation",
169 &ctx.expr_context(*node_id, "operation"),
170 )?) as u32 as f32,
171 threshold.resolve_float(
172 *node_id,
173 "threshold",
174 &ctx.expr_context(*node_id, "threshold"),
175 )? as f32,
176 0.0,
177 0.0,
178 ],
179 };
180 bound.write_buffer(*buffer, 0, bytemuck::bytes_of(¶ms));
181 Ok(())
182 }
183}