Skip to main content

lumen_engine/node/processing/
crop.rs

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