Skip to main content

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