Skip to main content

lumen_engine/node/processing/
resize.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!("resize.wgsl");
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, lumen_macros::NodeEnum)]
11#[repr(i64)]
12pub enum ResizeMode {
13    Stretch = 0,
14    Fit = 1,
15    Fill = 2,
16}
17
18impl ResizeMode {
19    pub fn from_int(value: i64) -> Self {
20        match value {
21            1 => Self::Fit,
22            2 => Self::Fill,
23            _ => Self::Stretch,
24        }
25    }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, lumen_macros::NodeEnum)]
29#[repr(i64)]
30pub enum ResizeSampling {
31    Nearest = 0,
32    Linear = 1,
33}
34
35impl ResizeSampling {
36    pub fn from_int(value: i64) -> Self {
37        if value == Self::Nearest as i64 {
38            Self::Nearest
39        } else {
40            Self::Linear
41        }
42    }
43}
44
45/// Resamples a raster into static output bounds.
46#[derive(Debug, Clone, lumen_macros::Node)]
47#[node(kind = "resize", name = "Resize", category = "processing")]
48pub struct Resize {
49    pub id: NodeId,
50    /// Output width in pixels.
51    #[property(kind = "int", min = 1, step = 1)]
52    pub width: NodeProperty,
53    /// Output height in pixels.
54    #[property(kind = "int", min = 1, step = 1)]
55    pub height: NodeProperty,
56    /// How the source raster should fit the output bounds.
57    #[property(kind = "enum", enum_type = ResizeMode)]
58    pub mode: NodeProperty,
59    /// Sampling filter used when resizing.
60    #[property(kind = "enum", enum_type = ResizeSampling)]
61    pub sampling: NodeProperty,
62    #[input()]
63    pub source: PortRef,
64}
65
66impl Default for Resize {
67    fn default() -> Self {
68        Self {
69            id: NodeId::new(0),
70            width: NodeProperty::Int(1),
71            height: NodeProperty::Int(1),
72            mode: NodeProperty::Int(ResizeMode::Stretch as i64),
73            sampling: NodeProperty::Int(ResizeSampling::Linear as i64),
74            source: PortRef::empty(),
75        }
76    }
77}
78
79impl GpuCompileNode for Resize {
80    fn compile_gpu(
81        &self,
82        ctx: &mut crate::gpu::CompileContext<'_>,
83        port: &PortRef,
84    ) -> crate::Result<CompiledOutput> {
85        if port.port != "output" {
86            return Err(ctx.missing_output(self.id, &port.port));
87        }
88
89        let source = ctx
90            .compile_port(&self.source)?
91            .into_raster(self.source.id, &self.source.port)?;
92        let size = lumen_gpu::Size::new(
93            ctx.static_dimension(&self.width, self.id, "width")?,
94            ctx.static_dimension(&self.height, self.id, "height")?,
95        );
96        let texture = ctx.builder_mut().texture_for(
97            lumen_gpu::NodeKey(self.id.0),
98            Some(format!("resize:{}:output", self.id.0)),
99            lumen_gpu::TextureDesc::storage(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
100        );
101        let params = ctx.builder_mut().buffer_for(
102            lumen_gpu::NodeKey(self.id.0),
103            Some(format!("resize:{}:params", self.id.0)),
104            lumen_gpu::BufferDesc::uniform(std::mem::size_of::<compiler::ResizeParams>() as u64),
105        );
106        let program = ctx.spatial_program(self.id, "resize", SHADER);
107        ctx.builder_mut().compute_pass(lumen_gpu::ComputePassDesc {
108            label: Some(format!("resize:{}:apply", self.id.0)),
109            owner: Some(lumen_gpu::NodeKey(self.id.0)),
110            program,
111            bindings: compiler::spatial_bindings(source.texture, params, texture),
112            dispatch: compiler::dispatch_for(size).into(),
113        });
114        ctx.builder_mut().param(
115            lumen_gpu::ParamKey {
116                owner: lumen_gpu::NodeKey(self.id.0),
117                slot: 0,
118            },
119            lumen_gpu::ParamTarget::Buffer(params),
120        );
121        ctx.push_frame_binding(FrameBinding::Resize {
122            node_id: self.id,
123            width: self.width.clone(),
124            height: self.height.clone(),
125            mode: self.mode.clone(),
126            sampling: self.sampling.clone(),
127            buffer: params,
128        });
129
130        Ok(CompiledOutput::Raster(RasterHandle {
131            texture,
132            domain: lumen_gpu::TextureDomain::full_frame(size),
133            metadata: source.metadata,
134        }))
135    }
136}
137
138impl GpuFrameBindNode for Resize {
139    fn bind_gpu_frame(
140        &self,
141        ctx: &FrameBindContext<'_>,
142        binding: &FrameBinding,
143        bound: &mut BoundFrame,
144    ) -> crate::Result<()> {
145        let FrameBinding::Resize {
146            node_id,
147            width,
148            height,
149            mode,
150            sampling,
151            buffer,
152        } = binding
153        else {
154            return Ok(());
155        };
156        let params = compiler::ResizeParams {
157            size: [
158                width
159                    .resolve_int(*node_id, "width", &ctx.expr_context(*node_id, "width"))?
160                    .max(1) as u32,
161                height
162                    .resolve_int(*node_id, "height", &ctx.expr_context(*node_id, "height"))?
163                    .max(1) as u32,
164            ],
165            mode: ResizeMode::from_int(mode.resolve_int(
166                *node_id,
167                "mode",
168                &ctx.expr_context(*node_id, "mode"),
169            )?) as u32,
170            sampling: ResizeSampling::from_int(sampling.resolve_int(
171                *node_id,
172                "sampling",
173                &ctx.expr_context(*node_id, "sampling"),
174            )?) as u32,
175        };
176        bound.write_buffer(*buffer, 0, bytemuck::bytes_of(&params));
177        Ok(())
178    }
179}