Skip to main content

lumen_engine/node/processing/
crop.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!("crop.wgsl");
9
10/// Extracts a fixed raster region into static output bounds.
11#[derive(Debug, Clone, lumen_macros::Node)]
12#[node(kind = "crop", name = "Crop", category = "processing")]
13pub struct Crop {
14    pub id: NodeId,
15    /// Left edge of the crop region in pixels.
16    #[property(kind = "int", step = 1)]
17    pub x: NodeProperty,
18    /// Top edge of the crop region in pixels.
19    #[property(kind = "int", step = 1)]
20    pub y: NodeProperty,
21    /// Width of the crop region in pixels.
22    #[property(kind = "int", min = 0, step = 1)]
23    pub width: NodeProperty,
24    /// Height of the crop region in pixels.
25    #[property(kind = "int", min = 0, step = 1)]
26    pub height: NodeProperty,
27    #[input()]
28    pub source: PortRef,
29}
30
31impl Default for Crop {
32    fn default() -> Self {
33        Self {
34            id: NodeId::new(0),
35            x: NodeProperty::Int(0),
36            y: NodeProperty::Int(0),
37            width: NodeProperty::Int(1),
38            height: NodeProperty::Int(1),
39            source: PortRef::empty(),
40        }
41    }
42}
43
44impl GpuCompileNode for Crop {
45    fn compile_gpu(
46        &self,
47        ctx: &mut crate::gpu::CompileContext<'_>,
48        port: &PortRef,
49    ) -> crate::Result<CompiledOutput> {
50        if port.port != "output" {
51            return Err(ctx.missing_output(self.id, &port.port));
52        }
53
54        let source = ctx
55            .compile_port(&self.source)?
56            .into_raster(self.source.id, &self.source.port)?;
57        let size = lumen_gpu::Size::new(
58            ctx.static_dimension(&self.width, self.id, "width")?,
59            ctx.static_dimension(&self.height, self.id, "height")?,
60        );
61        let texture = ctx.builder_mut().texture_for(
62            lumen_gpu::NodeKey(self.id.0),
63            Some(format!("crop:{}:output", self.id.0)),
64            lumen_gpu::TextureDesc::storage(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
65        );
66        let params = ctx.builder_mut().buffer_for(
67            lumen_gpu::NodeKey(self.id.0),
68            Some(format!("crop:{}:params", self.id.0)),
69            lumen_gpu::BufferDesc::uniform(std::mem::size_of::<compiler::CropParams>() as u64),
70        );
71        let program = ctx.spatial_program(self.id, "crop", SHADER);
72        ctx.builder_mut().compute_pass(lumen_gpu::ComputePassDesc {
73            label: Some(format!("crop:{}:apply", self.id.0)),
74            owner: Some(lumen_gpu::NodeKey(self.id.0)),
75            program,
76            bindings: compiler::spatial_bindings(source.texture, params, texture),
77            dispatch: compiler::dispatch_for(size).into(),
78        });
79        ctx.builder_mut().param(
80            lumen_gpu::ParamKey {
81                owner: lumen_gpu::NodeKey(self.id.0),
82                slot: 0,
83            },
84            lumen_gpu::ParamTarget::Buffer(params),
85        );
86        ctx.push_frame_binding(FrameBinding::Crop {
87            node_id: self.id,
88            x: self.x.clone(),
89            y: self.y.clone(),
90            width: self.width.clone(),
91            height: self.height.clone(),
92            buffer: params,
93        });
94
95        Ok(CompiledOutput::Raster(RasterHandle {
96            texture,
97            domain: lumen_gpu::TextureDomain::full_frame(size),
98            metadata: source.metadata,
99        }))
100    }
101}
102
103impl GpuFrameBindNode for Crop {
104    fn bind_gpu_frame(
105        &self,
106        ctx: &FrameBindContext<'_>,
107        binding: &FrameBinding,
108        bound: &mut BoundFrame,
109    ) -> crate::Result<()> {
110        let FrameBinding::Crop {
111            node_id,
112            x,
113            y,
114            width,
115            height,
116            buffer,
117        } = binding
118        else {
119            return Ok(());
120        };
121        let params = compiler::CropParams {
122            origin: [
123                x.resolve_int(*node_id, "x", &ctx.expr_context(*node_id, "x"))? as i32,
124                y.resolve_int(*node_id, "y", &ctx.expr_context(*node_id, "y"))? as i32,
125            ],
126            size: [
127                width
128                    .resolve_int(*node_id, "width", &ctx.expr_context(*node_id, "width"))?
129                    .max(0) as u32,
130                height
131                    .resolve_int(*node_id, "height", &ctx.expr_context(*node_id, "height"))?
132                    .max(0) as u32,
133            ],
134        };
135        bound.write_buffer(*buffer, 0, bytemuck::bytes_of(&params));
136        Ok(())
137    }
138}