Skip to main content

lumen_engine/node/processing/
exposure.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!("exposure.wgsl");
9
10/// Adjusts raster exposure, contrast, and offset.
11#[derive(Debug, Clone, lumen_macros::Node)]
12#[node(kind = "exposure", name = "Exposure", category = "processing")]
13pub struct Exposure {
14    pub id: NodeId,
15    /// Exposure offset in stops.
16    #[property(kind = "float", step = 0.01)]
17    pub exposure: NodeProperty,
18    /// Contrast multiplier.
19    #[property(kind = "float", min = 0, step = 0.01)]
20    pub contrast: NodeProperty,
21    /// Linear color offset.
22    #[property(kind = "float", step = 0.01)]
23    pub offset: NodeProperty,
24    #[input()]
25    pub source: PortRef,
26}
27
28impl Default for Exposure {
29    fn default() -> Self {
30        Self {
31            id: NodeId::new(0),
32            exposure: NodeProperty::Float(0.0),
33            contrast: NodeProperty::Float(1.0),
34            offset: NodeProperty::Float(0.0),
35            source: PortRef::empty(),
36        }
37    }
38}
39
40impl GpuCompileNode for Exposure {
41    fn compile_gpu(
42        &self,
43        ctx: &mut crate::gpu::CompileContext<'_>,
44        port: &PortRef,
45    ) -> crate::Result<CompiledOutput> {
46        let (source, texture, params) = ctx.compile_unary_filter(
47            self.id,
48            &self.source,
49            port,
50            "exposure",
51            SHADER,
52            std::mem::size_of::<compiler::ExposureParams>() as u64,
53        )?;
54        ctx.push_frame_binding(FrameBinding::Exposure {
55            node_id: self.id,
56            exposure: self.exposure.clone(),
57            contrast: self.contrast.clone(),
58            offset: self.offset.clone(),
59            buffer: params,
60        });
61        Ok(CompiledOutput::Raster(RasterHandle {
62            texture,
63            domain: source.domain,
64            metadata: source.metadata,
65        }))
66    }
67}
68
69impl GpuFrameBindNode for Exposure {
70    fn bind_gpu_frame(
71        &self,
72        ctx: &FrameBindContext<'_>,
73        binding: &FrameBinding,
74        bound: &mut BoundFrame,
75    ) -> crate::Result<()> {
76        let FrameBinding::Exposure {
77            node_id,
78            exposure,
79            contrast,
80            offset,
81            buffer,
82        } = binding
83        else {
84            return Ok(());
85        };
86        let params = compiler::ExposureParams {
87            exposure: exposure.resolve_float(
88                *node_id,
89                "exposure",
90                &ctx.expr_context(*node_id, "exposure"),
91            )? as f32,
92            contrast: contrast.resolve_float(
93                *node_id,
94                "contrast",
95                &ctx.expr_context(*node_id, "contrast"),
96            )? as f32,
97            offset: offset.resolve_float(
98                *node_id,
99                "offset",
100                &ctx.expr_context(*node_id, "offset"),
101            )? as f32,
102            _pad: 0.0,
103        };
104        bound.write_buffer(*buffer, 0, bytemuck::bytes_of(&params));
105        Ok(())
106    }
107}