Skip to main content

lumen_engine/node/processing/
levels.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!("levels.wgsl");
9
10/// Remaps raster black, white, gamma, and output range.
11#[derive(Debug, Clone, lumen_macros::Delegate)]
12pub struct LevelsParams {
13    /// Input black point.
14    #[meta(min = 0, max = 1, step = 0.01)]
15    pub black_point: f64,
16    /// Input white point.
17    #[meta(min = 0, max = 1, step = 0.01)]
18    pub white_point: f64,
19    /// Midtone gamma adjustment.
20    #[meta(min = 0.01, step = 0.01)]
21    pub gamma: f64,
22    /// Output black level.
23    #[meta(min = 0, max = 1, step = 0.01)]
24    pub output_black: f64,
25    /// Output white level.
26    #[meta(min = 0, max = 1, step = 0.01)]
27    pub output_white: f64,
28}
29
30impl Default for LevelsParams {
31    fn default() -> Self {
32        Self {
33            black_point: 0.0,
34            white_point: 1.0,
35            gamma: 1.0,
36            output_black: 0.0,
37            output_white: 1.0,
38        }
39    }
40}
41
42/// Remaps raster black, white, gamma, and output range.
43#[derive(Debug, Clone, lumen_macros::Node)]
44#[node(kind = "levels", name = "Levels", category = "processing")]
45pub struct Levels {
46    pub id: NodeId,
47    #[params]
48    pub params: LevelsParamsDelegate,
49
50    #[input()]
51    pub source: PortRef,
52}
53
54impl Default for Levels {
55    fn default() -> Self {
56        Self {
57            id: NodeId::new(0),
58            params: LevelsParamsDelegate::default(),
59            source: PortRef::empty(),
60        }
61    }
62}
63
64#[derive(Debug, Clone)]
65struct CompiledLevels {
66    node_id: NodeId,
67    params: LevelsParamsDelegate,
68    buffer: lumen_gpu::BufferId,
69}
70
71impl GpuCompiledNode for CompiledLevels {
72    fn node_id(&self) -> NodeId {
73        self.node_id
74    }
75
76    fn bind(&self, ctx: &FrameBindContext<'_>, bound: &mut BoundFrame) -> crate::Result<()> {
77        let params = self.params.eval(&NodeParamEvalContext {
78            node_id: self.node_id,
79            expr: &ctx.expr_context(self.node_id, "params"),
80        })?;
81        let gpu_params = compiler::LevelsParams {
82            black_point: params.black_point as f32,
83            white_point: params.white_point as f32,
84            gamma: params.gamma as f32,
85            output_black: params.output_black as f32,
86            output_white: params.output_white as f32,
87            _pad: [0.0; 3],
88        };
89        bound.write_buffer(self.buffer, 0, bytemuck::bytes_of(&gpu_params));
90        Ok(())
91    }
92}
93
94impl GpuCompileNode for Levels {
95    fn compile_gpu(
96        &self,
97        ctx: &mut crate::gpu::CompileContext<'_>,
98        port: &PortRef,
99    ) -> crate::Result<CompiledOutput> {
100        let (source, texture, params) = ctx.compile_unary_filter(
101            self.id,
102            &self.source,
103            port,
104            "levels",
105            SHADER,
106            std::mem::size_of::<compiler::LevelsParams>() as u64,
107        )?;
108        ctx.register_compiled_node(CompiledLevels {
109            node_id: self.id,
110            params: self.params.clone(),
111            buffer: params,
112        });
113        Ok(CompiledOutput::Raster(RasterHandle {
114            texture,
115            domain: source.domain,
116            metadata: source.metadata,
117        }))
118    }
119}