lumen_engine/node/processing/
crop.rs1use 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#[derive(Debug, Clone, lumen_macros::Delegate)]
12pub struct CropParams {
13 #[meta(step = 1)]
15 pub x: i64,
16 #[meta(step = 1)]
18 pub y: i64,
19 #[meta(min = 0, step = 1)]
21 pub width: i64,
22 #[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#[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}