1use crate::config::RenderConfig;
25use crate::error::Result;
26use bytemuck::{Pod, Zeroable};
27use std::sync::Arc;
28use wgpu::util::DeviceExt;
29
30#[repr(C)]
34#[derive(Debug, Clone, Copy, Pod, Zeroable)]
35pub struct BlendUniforms {
36 pub progress: f32,
37 pub has_secondary: u32,
38 _pad: [f32; 2],
39}
40
41impl Default for BlendUniforms {
42 fn default() -> Self {
43 Self {
44 progress: 0.0,
45 has_secondary: 0,
46 _pad: [0.0; 2],
47 }
48 }
49}
50
51pub struct FinalBlendPipeline {
56 pipeline: wgpu::RenderPipeline,
57 bind_group_layout: wgpu::BindGroupLayout,
58 sampler: wgpu::Sampler,
59 uniform_buffer: wgpu::Buffer,
60 bind_group: wgpu::BindGroup,
61 fallback_view: wgpu::TextureView,
65 display_texture: wgpu::Texture,
68 display_texture_view: wgpu::TextureView,
69}
70
71impl FinalBlendPipeline {
72 pub fn new(
76 device: &Arc<wgpu::Device>,
77 config: &RenderConfig,
78 primary_view: &wgpu::TextureView,
79 ) -> Result<Self> {
80 let display_texture = device.create_texture(&wgpu::TextureDescriptor {
81 label: Some("Display Texture"),
82 size: wgpu::Extent3d {
83 width: config.width,
84 height: config.height,
85 depth_or_array_layers: 1,
86 },
87 mip_level_count: 1,
88 sample_count: 1,
89 dimension: wgpu::TextureDimension::D2,
90 format: config.texture_format.to_wgpu(),
91 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
92 | wgpu::TextureUsages::TEXTURE_BINDING
93 | wgpu::TextureUsages::COPY_SRC,
94 view_formats: &[],
95 });
96 let display_texture_view =
97 display_texture.create_view(&wgpu::TextureViewDescriptor::default());
98
99 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
100 label: Some("Final Blend Sampler"),
101 address_mode_u: wgpu::AddressMode::ClampToEdge,
102 address_mode_v: wgpu::AddressMode::ClampToEdge,
103 address_mode_w: wgpu::AddressMode::ClampToEdge,
104 mag_filter: wgpu::FilterMode::Linear,
105 min_filter: wgpu::FilterMode::Linear,
106 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
107 ..Default::default()
108 });
109
110 let fallback_texture = device.create_texture(&wgpu::TextureDescriptor {
115 label: Some("Final Blend Fallback (1x1)"),
116 size: wgpu::Extent3d {
117 width: 1,
118 height: 1,
119 depth_or_array_layers: 1,
120 },
121 mip_level_count: 1,
122 sample_count: 1,
123 dimension: wgpu::TextureDimension::D2,
124 format: config.texture_format.to_wgpu(),
125 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
126 view_formats: &[],
127 });
128 let fallback_view = fallback_texture.create_view(&wgpu::TextureViewDescriptor::default());
129
130 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
131 label: Some("Final Blend Uniforms"),
132 contents: bytemuck::bytes_of(&BlendUniforms::default()),
133 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
134 });
135
136 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
137 label: Some("Final Blend BGL"),
138 entries: &[
139 wgpu::BindGroupLayoutEntry {
140 binding: 0,
141 visibility: wgpu::ShaderStages::FRAGMENT,
142 ty: wgpu::BindingType::Buffer {
143 ty: wgpu::BufferBindingType::Uniform,
144 has_dynamic_offset: false,
145 min_binding_size: None,
146 },
147 count: None,
148 },
149 wgpu::BindGroupLayoutEntry {
150 binding: 1,
151 visibility: wgpu::ShaderStages::FRAGMENT,
152 ty: wgpu::BindingType::Texture {
153 sample_type: wgpu::TextureSampleType::Float { filterable: true },
154 view_dimension: wgpu::TextureViewDimension::D2,
155 multisampled: false,
156 },
157 count: None,
158 },
159 wgpu::BindGroupLayoutEntry {
160 binding: 2,
161 visibility: wgpu::ShaderStages::FRAGMENT,
162 ty: wgpu::BindingType::Texture {
163 sample_type: wgpu::TextureSampleType::Float { filterable: true },
164 view_dimension: wgpu::TextureViewDimension::D2,
165 multisampled: false,
166 },
167 count: None,
168 },
169 wgpu::BindGroupLayoutEntry {
170 binding: 3,
171 visibility: wgpu::ShaderStages::FRAGMENT,
172 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
173 count: None,
174 },
175 ],
176 });
177
178 let bind_group = make_bind_group(
179 device,
180 &bind_group_layout,
181 &uniform_buffer,
182 primary_view,
183 &fallback_view,
184 &sampler,
185 );
186
187 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
188 label: Some("Final Blend Shader"),
189 source: wgpu::ShaderSource::Wgsl(SHADER.into()),
190 });
191
192 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
193 label: Some("Final Blend Pipeline Layout"),
194 bind_group_layouts: &[Some(&bind_group_layout)],
195 immediate_size: 0,
196 });
197
198 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
199 label: Some("Final Blend Pipeline"),
200 layout: Some(&pipeline_layout),
201 vertex: wgpu::VertexState {
202 module: &shader,
203 entry_point: Some("vs_main"),
204 buffers: &[],
205 compilation_options: Default::default(),
206 },
207 fragment: Some(wgpu::FragmentState {
208 module: &shader,
209 entry_point: Some("fs_main"),
210 targets: &[Some(wgpu::ColorTargetState {
211 format: config.texture_format.to_wgpu(),
212 blend: None,
213 write_mask: wgpu::ColorWrites::ALL,
214 })],
215 compilation_options: Default::default(),
216 }),
217 primitive: wgpu::PrimitiveState {
218 topology: wgpu::PrimitiveTopology::TriangleList,
219 ..Default::default()
220 },
221 depth_stencil: None,
222 multisample: wgpu::MultisampleState::default(),
223 multiview_mask: None,
224 cache: None,
225 });
226
227 Ok(Self {
228 pipeline,
229 bind_group_layout,
230 sampler,
231 uniform_buffer,
232 bind_group,
233 fallback_view,
234 display_texture,
235 display_texture_view,
236 })
237 }
238
239 pub fn display_texture_view(&self) -> &wgpu::TextureView {
244 &self.display_texture_view
245 }
246
247 pub fn display_texture(&self) -> &wgpu::Texture {
248 &self.display_texture
249 }
250
251 pub fn set_inputs(
256 &mut self,
257 device: &Arc<wgpu::Device>,
258 primary_view: &wgpu::TextureView,
259 secondary_view: Option<&wgpu::TextureView>,
260 ) {
261 let secondary = secondary_view.unwrap_or(&self.fallback_view);
262 self.bind_group = make_bind_group(
263 device,
264 &self.bind_group_layout,
265 &self.uniform_buffer,
266 primary_view,
267 secondary,
268 &self.sampler,
269 );
270 }
271
272 pub fn update_progress(&self, queue: &wgpu::Queue, progress: f32, has_secondary: bool) {
275 let u = BlendUniforms {
276 progress: progress.clamp(0.0, 1.0),
277 has_secondary: if has_secondary { 1 } else { 0 },
278 _pad: [0.0; 2],
279 };
280 queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&u));
281 }
282
283 pub fn resize(&mut self, device: &Arc<wgpu::Device>, config: &RenderConfig) {
286 self.display_texture = device.create_texture(&wgpu::TextureDescriptor {
287 label: Some("Display Texture"),
288 size: wgpu::Extent3d {
289 width: config.width,
290 height: config.height,
291 depth_or_array_layers: 1,
292 },
293 mip_level_count: 1,
294 sample_count: 1,
295 dimension: wgpu::TextureDimension::D2,
296 format: config.texture_format.to_wgpu(),
297 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
298 | wgpu::TextureUsages::TEXTURE_BINDING
299 | wgpu::TextureUsages::COPY_SRC,
300 view_formats: &[],
301 });
302 self.display_texture_view = self
303 .display_texture
304 .create_view(&wgpu::TextureViewDescriptor::default());
305 }
306
307 pub fn record_pass(&self, encoder: &mut wgpu::CommandEncoder) {
310 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
311 label: Some("Final Blend Pass"),
312 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
313 view: &self.display_texture_view,
314 depth_slice: None,
315 resolve_target: None,
316 ops: wgpu::Operations {
317 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
318 store: wgpu::StoreOp::Store,
319 },
320 })],
321 depth_stencil_attachment: None,
322 timestamp_writes: None,
323 occlusion_query_set: None,
324 multiview_mask: None,
325 });
326 pass.set_pipeline(&self.pipeline);
327 pass.set_bind_group(0, &self.bind_group, &[]);
328 pass.draw(0..3, 0..1);
329 }
330}
331
332fn make_bind_group(
333 device: &wgpu::Device,
334 layout: &wgpu::BindGroupLayout,
335 uniforms: &wgpu::Buffer,
336 primary_view: &wgpu::TextureView,
337 secondary_view: &wgpu::TextureView,
338 sampler: &wgpu::Sampler,
339) -> wgpu::BindGroup {
340 device.create_bind_group(&wgpu::BindGroupDescriptor {
341 label: Some("Final Blend BG"),
342 layout,
343 entries: &[
344 wgpu::BindGroupEntry {
345 binding: 0,
346 resource: uniforms.as_entire_binding(),
347 },
348 wgpu::BindGroupEntry {
349 binding: 1,
350 resource: wgpu::BindingResource::TextureView(primary_view),
351 },
352 wgpu::BindGroupEntry {
353 binding: 2,
354 resource: wgpu::BindingResource::TextureView(secondary_view),
355 },
356 wgpu::BindGroupEntry {
357 binding: 3,
358 resource: wgpu::BindingResource::Sampler(sampler),
359 },
360 ],
361 })
362}
363
364const SHADER: &str = r#"
365struct Uniforms {
366 progress: f32,
367 has_secondary: u32,
368 pad0: f32,
369 pad1: f32,
370};
371
372@group(0) @binding(0) var<uniform> u: Uniforms;
373@group(0) @binding(1) var primary_tex: texture_2d<f32>;
374@group(0) @binding(2) var secondary_tex: texture_2d<f32>;
375@group(0) @binding(3) var samp: sampler;
376
377struct VsOut {
378 @builtin(position) position: vec4<f32>,
379 @location(0) uv: vec2<f32>,
380};
381
382@vertex
383fn vs_main(@builtin(vertex_index) idx: u32) -> VsOut {
384 let x = f32(idx & 1u) * 4.0 - 1.0;
385 let y = f32((idx >> 1u) & 1u) * 4.0 - 1.0;
386 var out: VsOut;
387 out.position = vec4<f32>(x, y, 0.0, 1.0);
388 out.uv = vec2<f32>((x + 1.0) * 0.5, 1.0 - (y + 1.0) * 0.5);
389 return out;
390}
391
392@fragment
393fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
394 let primary = textureSample(primary_tex, samp, in.uv);
395 if (u.has_secondary == 0u) {
396 return primary;
397 }
398 let secondary = textureSample(secondary_tex, samp, in.uv);
399 return mix(secondary, primary, u.progress);
400}
401"#;