lumen_engine/node/processing/
crop.rs1use 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!("crop.wgsl");
9
10#[derive(Debug, Clone, lumen_macros::Node)]
12#[node(kind = "crop", name = "Crop", category = "processing")]
13pub struct Crop {
14 pub id: NodeId,
15 #[property(kind = "int", step = 1)]
17 pub x: NodeProperty,
18 #[property(kind = "int", step = 1)]
20 pub y: NodeProperty,
21 #[property(kind = "int", min = 0, step = 1)]
23 pub width: NodeProperty,
24 #[property(kind = "int", min = 0, step = 1)]
26 pub height: NodeProperty,
27 #[input()]
28 pub source: PortRef,
29}
30
31impl Default for Crop {
32 fn default() -> Self {
33 Self {
34 id: NodeId::new(0),
35 x: NodeProperty::Int(0),
36 y: NodeProperty::Int(0),
37 width: NodeProperty::Int(1),
38 height: NodeProperty::Int(1),
39 source: PortRef::empty(),
40 }
41 }
42}
43
44impl GpuCompileNode for Crop {
45 fn compile_gpu(
46 &self,
47 ctx: &mut crate::gpu::CompileContext<'_>,
48 port: &PortRef,
49 ) -> crate::Result<CompiledOutput> {
50 if port.port != "output" {
51 return Err(ctx.missing_output(self.id, &port.port));
52 }
53
54 let source = ctx
55 .compile_port(&self.source)?
56 .into_raster(self.source.id, &self.source.port)?;
57 let size = lumen_gpu::Size::new(
58 ctx.static_dimension(&self.width, self.id, "width")?,
59 ctx.static_dimension(&self.height, self.id, "height")?,
60 );
61 let texture = ctx.builder_mut().texture_for(
62 lumen_gpu::NodeKey(self.id.0),
63 Some(format!("crop:{}:output", self.id.0)),
64 lumen_gpu::TextureDesc::storage(size, lumen_gpu::wgpu::TextureFormat::Rgba8Unorm),
65 );
66 let params = ctx.builder_mut().buffer_for(
67 lumen_gpu::NodeKey(self.id.0),
68 Some(format!("crop:{}:params", self.id.0)),
69 lumen_gpu::BufferDesc::uniform(std::mem::size_of::<compiler::CropParams>() as u64),
70 );
71 let program = ctx.spatial_program(self.id, "crop", SHADER);
72 ctx.builder_mut().compute_pass(lumen_gpu::ComputePassDesc {
73 label: Some(format!("crop:{}:apply", self.id.0)),
74 owner: Some(lumen_gpu::NodeKey(self.id.0)),
75 program,
76 bindings: compiler::spatial_bindings(source.texture, params, texture),
77 dispatch: compiler::dispatch_for(size).into(),
78 });
79 ctx.builder_mut().param(
80 lumen_gpu::ParamKey {
81 owner: lumen_gpu::NodeKey(self.id.0),
82 slot: 0,
83 },
84 lumen_gpu::ParamTarget::Buffer(params),
85 );
86 ctx.push_frame_binding(FrameBinding::Crop {
87 node_id: self.id,
88 x: self.x.clone(),
89 y: self.y.clone(),
90 width: self.width.clone(),
91 height: self.height.clone(),
92 buffer: params,
93 });
94
95 Ok(CompiledOutput::Raster(RasterHandle {
96 texture,
97 domain: lumen_gpu::TextureDomain::full_frame(size),
98 metadata: source.metadata,
99 }))
100 }
101}
102
103impl GpuFrameBindNode for Crop {
104 fn bind_gpu_frame(
105 &self,
106 ctx: &FrameBindContext<'_>,
107 binding: &FrameBinding,
108 bound: &mut BoundFrame,
109 ) -> crate::Result<()> {
110 let FrameBinding::Crop {
111 node_id,
112 x,
113 y,
114 width,
115 height,
116 buffer,
117 } = binding
118 else {
119 return Ok(());
120 };
121 let params = compiler::CropParams {
122 origin: [
123 x.resolve_int(*node_id, "x", &ctx.expr_context(*node_id, "x"))? as i32,
124 y.resolve_int(*node_id, "y", &ctx.expr_context(*node_id, "y"))? as i32,
125 ],
126 size: [
127 width
128 .resolve_int(*node_id, "width", &ctx.expr_context(*node_id, "width"))?
129 .max(0) as u32,
130 height
131 .resolve_int(*node_id, "height", &ctx.expr_context(*node_id, "height"))?
132 .max(0) as u32,
133 ],
134 };
135 bound.write_buffer(*buffer, 0, bytemuck::bytes_of(¶ms));
136 Ok(())
137 }
138}