1use crate::node::{NodeId, PortRef};
2
3use crate::gpu::{
4 BoundFrame, CompiledOutput, FrameBindContext, FrameBinding, GpuCompileNode, GpuFrameBindNode,
5 RasterHandle, compiler,
6};
7
8pub(crate) const SHADER: &str = include_str!("media_output.wgsl");
9pub(crate) const RENDER_SHADER: &str = include_str!("media_output_render.wgsl");
10
11#[derive(Debug, Clone, lumen_macros::Node)]
13#[node(kind = "media_output", name = "Media Output", category = "output")]
14pub struct MediaOutput {
15 pub id: NodeId,
16 #[input()]
17 pub source: PortRef,
18}
19
20impl Default for MediaOutput {
21 fn default() -> Self {
22 Self {
23 id: NodeId::new(0),
24 source: PortRef::empty(),
25 }
26 }
27}
28
29impl GpuCompileNode for MediaOutput {
30 fn compile_gpu(
31 &self,
32 ctx: &mut crate::gpu::CompileContext<'_>,
33 port: &PortRef,
34 ) -> crate::Result<CompiledOutput> {
35 if port.port != "output" {
36 return Err(ctx.missing_output(self.id, &port.port));
37 }
38
39 let source = ctx
40 .compile_port(&self.source)?
41 .into_raster(self.source.id, &self.source.port)?;
42 let size = lumen_gpu::Size::new(
43 ctx.composition().render_settings.width.max(1),
44 ctx.composition().render_settings.height.max(1),
45 );
46 let output_format = ctx.output_format();
47 let output = ctx.builder_mut().texture_for(
48 lumen_gpu::NodeKey(self.id.0),
49 Some("media-output:final".to_string()),
50 media_output_texture_desc(size, output_format),
51 );
52 if output_format == lumen_gpu::wgpu::TextureFormat::Rgba8Unorm {
53 let program = ctx.builder_mut().program_for(
54 lumen_gpu::NodeKey(self.id.0),
55 lumen_gpu::ProgramDesc::Compute(lumen_gpu::ComputeProgramDesc {
56 label: Some("media-output".to_string()),
57 shader: SHADER.to_string(),
58 entry: "cs_main".to_string(),
59 bind_groups: lumen_gpu::BindGroupLayoutSpec::single(vec![
60 lumen_gpu::BindingLayoutEntry::texture(
61 0,
62 lumen_gpu::wgpu::ShaderStages::COMPUTE,
63 ),
64 lumen_gpu::BindingLayoutEntry::storage_texture(
65 1,
66 lumen_gpu::wgpu::ShaderStages::COMPUTE,
67 lumen_gpu::wgpu::TextureFormat::Rgba8Unorm,
68 lumen_gpu::wgpu::StorageTextureAccess::WriteOnly,
69 ),
70 ]),
71 }),
72 );
73 ctx.builder_mut().compute_pass(lumen_gpu::ComputePassDesc {
74 label: Some("media-output:copy".to_string()),
75 owner: Some(lumen_gpu::NodeKey(self.id.0)),
76 program,
77 bindings: vec![
78 lumen_gpu::Binding::sampled_texture(0, 0, source.texture),
79 lumen_gpu::Binding::storage_texture(0, 1, output),
80 ],
81 dispatch: compiler::dispatch_for(size).into(),
82 });
83 } else {
84 let sampler = ctx.builder_mut().sampler(
85 Some("media-output:sampler".to_string()),
86 lumen_gpu::wgpu::SamplerDescriptor {
87 address_mode_u: lumen_gpu::wgpu::AddressMode::ClampToEdge,
88 address_mode_v: lumen_gpu::wgpu::AddressMode::ClampToEdge,
89 address_mode_w: lumen_gpu::wgpu::AddressMode::ClampToEdge,
90 mag_filter: lumen_gpu::wgpu::FilterMode::Nearest,
91 min_filter: lumen_gpu::wgpu::FilterMode::Nearest,
92 mipmap_filter: lumen_gpu::wgpu::MipmapFilterMode::Nearest,
93 ..Default::default()
94 },
95 );
96 let program = ctx.builder_mut().program_for(
97 lumen_gpu::NodeKey(self.id.0),
98 lumen_gpu::ProgramDesc::Render(lumen_gpu::RenderProgramDesc {
99 label: Some("media-output".to_string()),
100 shader: RENDER_SHADER.to_string(),
101 vertex_entry: "vs_main".to_string(),
102 fragment_entry: "fs_main".to_string(),
103 bind_groups: lumen_gpu::BindGroupLayoutSpec::single(vec![
104 lumen_gpu::BindingLayoutEntry::texture(
105 0,
106 lumen_gpu::wgpu::ShaderStages::FRAGMENT,
107 ),
108 lumen_gpu::BindingLayoutEntry::sampler(
109 1,
110 lumen_gpu::wgpu::ShaderStages::FRAGMENT,
111 ),
112 ]),
113 targets: vec![Some(lumen_gpu::wgpu::ColorTargetState {
114 format: output_format,
115 blend: Some(lumen_gpu::wgpu::BlendState::REPLACE),
116 write_mask: lumen_gpu::wgpu::ColorWrites::ALL,
117 })],
118 vertex_buffers: Vec::new(),
119 primitive: lumen_gpu::wgpu::PrimitiveState::default(),
120 }),
121 );
122 ctx.builder_mut().render_pass(lumen_gpu::RenderPassDesc {
123 label: Some("media-output:render".to_string()),
124 owner: Some(lumen_gpu::NodeKey(self.id.0)),
125 program,
126 targets: vec![lumen_gpu::RenderTargetRef {
127 texture: output,
128 load: lumen_gpu::LoadOp::Clear(lumen_gpu::wgpu::Color::TRANSPARENT),
129 store: lumen_gpu::wgpu::StoreOp::Store,
130 }],
131 bindings: vec![
132 lumen_gpu::Binding::sampled_texture(0, 0, source.texture),
133 lumen_gpu::Binding::sampler(0, 1, sampler),
134 ],
135 vertex_buffers: Vec::new(),
136 index_buffer: None,
137 draw: lumen_gpu::DrawCommand::Draw(lumen_gpu::Draw {
138 vertices: 0..3,
139 instances: 0..1,
140 }),
141 scissor: None,
142 });
143 }
144
145 Ok(CompiledOutput::Raster(RasterHandle {
146 texture: output,
147 domain: lumen_gpu::TextureDomain::full_frame(size),
148 metadata: source.metadata,
149 }))
150 }
151}
152
153fn media_output_texture_desc(
154 size: lumen_gpu::Size,
155 format: lumen_gpu::wgpu::TextureFormat,
156) -> lumen_gpu::TextureDesc {
157 if format == lumen_gpu::wgpu::TextureFormat::Rgba8Unorm {
158 compiler::copyable_texture_desc(size)
159 } else {
160 lumen_gpu::TextureDesc {
161 domain: lumen_gpu::TextureDomain::full_frame(size),
162 format,
163 usage: lumen_gpu::wgpu::TextureUsages::COPY_SRC
164 | lumen_gpu::wgpu::TextureUsages::TEXTURE_BINDING
165 | lumen_gpu::wgpu::TextureUsages::RENDER_ATTACHMENT,
166 }
167 }
168}
169
170impl GpuFrameBindNode for MediaOutput {
171 fn bind_gpu_frame(
172 &self,
173 _ctx: &FrameBindContext<'_>,
174 _binding: &FrameBinding,
175 _bound: &mut BoundFrame,
176 ) -> crate::Result<()> {
177 Ok(())
178 }
179}