Skip to main content

lumen_engine/node/compositing/
boolean.rs

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!("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/// Combines two raster alpha masks with boolean operations.
31#[derive(Debug, Clone, lumen_macros::Node)]
32#[node(kind = "boolean", name = "Boolean", category = "compositing")]
33pub struct Boolean {
34    pub id: NodeId,
35    /// Boolean operation used to combine the two input masks.
36    #[property(kind = "enum", enum_type = BooleanOperation)]
37    pub operation: NodeProperty,
38    /// Alpha cutoff used before evaluating the boolean operation.
39    #[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(&params));
181        Ok(())
182    }
183}