Skip to main content

lumen_engine/node/compositing/
boolean.rs

1use 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/// Combines two raster alpha masks with boolean operations.
31#[derive(Debug, Clone, lumen_macros::Delegate)]
32pub struct BooleanParams {
33    /// Boolean operation used to combine the two input masks.
34    #[meta(kind = "enum", enum_type = BooleanOperation)]
35    pub operation: i64,
36    /// Alpha cutoff used before evaluating the boolean operation.
37    #[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/// Combines two raster alpha masks with boolean operations.
51#[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}