1use wgpu::{
6 BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
7 BindGroupLayoutEntry, BindingResource, BindingType, BlendState, ColorTargetState, ColorWrites,
8 CommandEncoder, Device, FragmentState, LoadOp, MultisampleState, Operations,
9 PipelineLayoutDescriptor, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor,
10 RenderPipeline, RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor,
11 ShaderModuleDescriptor, ShaderSource, ShaderStages, StoreOp, TextureFormat, TextureSampleType,
12 TextureView, TextureViewDimension, VertexState,
13};
14
15pub struct CompositeNode {
16 pipeline: RenderPipeline,
17 sampler: Sampler,
18 bind_group_layout: BindGroupLayout,
19 bind_group: Option<BindGroup>,
20}
21
22impl CompositeNode {
23 pub fn new(device: &Device, target_format: TextureFormat) -> Self {
24 let shader = device.create_shader_module(ShaderModuleDescriptor {
25 label: Some("loam-render::composite::shader"),
26 source: ShaderSource::Wgsl(include_str!("composite.wgsl").into()),
27 });
28
29 let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
30 label: Some("loam-render::composite::bgl"),
31 entries: &[
32 BindGroupLayoutEntry {
33 binding: 0,
34 visibility: ShaderStages::FRAGMENT,
35 ty: BindingType::Texture {
36 sample_type: TextureSampleType::Float { filterable: true },
37 view_dimension: TextureViewDimension::D2,
38 multisampled: false,
39 },
40 count: None,
41 },
42 BindGroupLayoutEntry {
43 binding: 1,
44 visibility: ShaderStages::FRAGMENT,
45 ty: BindingType::Sampler(SamplerBindingType::Filtering),
46 count: None,
47 },
48 ],
49 });
50
51 let sampler = device.create_sampler(&SamplerDescriptor {
52 label: Some("loam-render::composite::sampler"),
53 mag_filter: wgpu::FilterMode::Linear,
54 min_filter: wgpu::FilterMode::Linear,
55 mipmap_filter: wgpu::FilterMode::Nearest,
56 address_mode_u: wgpu::AddressMode::ClampToEdge,
57 address_mode_v: wgpu::AddressMode::ClampToEdge,
58 address_mode_w: wgpu::AddressMode::ClampToEdge,
59 ..Default::default()
60 });
61
62 let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
63 label: Some("loam-render::composite::layout"),
64 bind_group_layouts: &[&bind_group_layout],
65 push_constant_ranges: &[],
66 });
67
68 let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
69 label: Some("loam-render::composite::pipeline"),
70 layout: Some(&layout),
71 vertex: VertexState {
72 module: &shader,
73 entry_point: Some("vs_fullscreen"),
74 buffers: &[],
75 compilation_options: Default::default(),
76 },
77 fragment: Some(FragmentState {
78 module: &shader,
79 entry_point: Some("fs_composite"),
80 targets: &[Some(ColorTargetState {
81 format: target_format,
82 blend: Some(BlendState::REPLACE),
83 write_mask: ColorWrites::ALL,
84 })],
85 compilation_options: Default::default(),
86 }),
87 primitive: PrimitiveState {
88 topology: wgpu::PrimitiveTopology::TriangleList,
89 ..Default::default()
90 },
91 depth_stencil: None,
92 multisample: MultisampleState::default(),
93 multiview: None,
94 cache: None,
95 });
96
97 Self {
98 pipeline,
99 sampler,
100 bind_group_layout,
101 bind_group: None,
102 }
103 }
104
105 pub fn set_scene_view(&mut self, device: &Device, scene_view: &TextureView) {
107 let bg = device.create_bind_group(&BindGroupDescriptor {
108 label: Some("loam-render::composite::bg"),
109 layout: &self.bind_group_layout,
110 entries: &[
111 BindGroupEntry {
112 binding: 0,
113 resource: BindingResource::TextureView(scene_view),
114 },
115 BindGroupEntry {
116 binding: 1,
117 resource: BindingResource::Sampler(&self.sampler),
118 },
119 ],
120 });
121 self.bind_group = Some(bg);
122 }
123
124 pub fn run(&self, encoder: &mut CommandEncoder, target_view: &TextureView) {
126 let Some(bind_group) = self.bind_group.as_ref() else {
127 return;
128 };
129 let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
130 label: Some("loam-render::composite::pass"),
131 color_attachments: &[Some(RenderPassColorAttachment {
132 view: target_view,
133 depth_slice: None,
134 resolve_target: None,
135 ops: Operations {
136 load: LoadOp::Clear(wgpu::Color::BLACK),
137 store: StoreOp::Store,
138 },
139 })],
140 depth_stencil_attachment: None,
141 timestamp_writes: None,
142 occlusion_query_set: None,
143 });
144 pass.set_pipeline(&self.pipeline);
145 pass.set_bind_group(0, bind_group, &[]);
146 pass.draw(0..3, 0..1);
147 }
148}