1use super::*;
2
3impl ViewportRenderer {
4 pub fn paint(&self, render_pass: &mut wgpu::RenderPass<'static>, frame: &FrameData) {
10 let camera_bg = self.viewport_camera_bind_group(frame.camera.viewport_index);
11 emit_draw_calls!(
12 &self.resources,
13 &mut *render_pass,
14 frame,
15 self.use_instancing,
16 &self.instanced_batches,
17 camera_bg,
18 &self.compute_filter_results
19 );
20 emit_scivis_draw_calls!(
21 &self.resources,
22 render_pass,
23 &self.point_cloud_gpu_data,
24 &self.glyph_gpu_data,
25 &self.polyline_gpu_data,
26 &self.volume_gpu_data,
27 &self.streamtube_gpu_data,
28 camera_bg
29 );
30 }
31
32 pub fn paint_to<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>, frame: &FrameData) {
38 let camera_bg = self.viewport_camera_bind_group(frame.camera.viewport_index);
39 emit_draw_calls!(
40 &self.resources,
41 &mut *render_pass,
42 frame,
43 self.use_instancing,
44 &self.instanced_batches,
45 camera_bg,
46 &self.compute_filter_results
47 );
48 emit_scivis_draw_calls!(
49 &self.resources,
50 render_pass,
51 &self.point_cloud_gpu_data,
52 &self.glyph_gpu_data,
53 &self.polyline_gpu_data,
54 &self.volume_gpu_data,
55 &self.streamtube_gpu_data,
56 camera_bg
57 );
58 }
59
60 pub fn render(
68 &mut self,
69 device: &wgpu::Device,
70 queue: &wgpu::Queue,
71 output_view: &wgpu::TextureView,
72 frame: &FrameData,
73 ) -> wgpu::CommandBuffer {
74 self.prepare(device, queue, frame);
76
77 let scene_items: &[SceneRenderItem] = match &frame.scene.surfaces {
79 SurfaceSubmission::Flat(items) => items,
80 };
81
82 let bg_color =
83 frame
84 .viewport
85 .background_color
86 .unwrap_or([65.0 / 255.0, 65.0 / 255.0, 65.0 / 255.0, 1.0]);
87
88 if !frame.effects.post_process.enabled {
89 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
91 label: Some("ldr_encoder"),
92 });
93 {
94 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
95 label: Some("ldr_render_pass"),
96 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
97 view: output_view,
98 resolve_target: None,
99 ops: wgpu::Operations {
100 load: wgpu::LoadOp::Clear(wgpu::Color {
101 r: bg_color[0] as f64,
102 g: bg_color[1] as f64,
103 b: bg_color[2] as f64,
104 a: bg_color[3] as f64,
105 }),
106 store: wgpu::StoreOp::Store,
107 },
108 depth_slice: None,
109 })],
110 depth_stencil_attachment: self.resources.outline_depth_view.as_ref().map(|v| {
111 wgpu::RenderPassDepthStencilAttachment {
112 view: v,
113 depth_ops: Some(wgpu::Operations {
114 load: wgpu::LoadOp::Clear(1.0),
115 store: wgpu::StoreOp::Discard,
116 }),
117 stencil_ops: None,
118 }
119 }),
120 timestamp_writes: None,
121 occlusion_query_set: None,
122 });
123 let camera_bg = self.viewport_camera_bind_group(frame.camera.viewport_index);
124 emit_draw_calls!(
125 &self.resources,
126 &mut render_pass,
127 frame,
128 self.use_instancing,
129 &self.instanced_batches,
130 camera_bg,
131 &self.compute_filter_results
132 );
133 emit_scivis_draw_calls!(
134 &self.resources,
135 &mut render_pass,
136 &self.point_cloud_gpu_data,
137 &self.glyph_gpu_data,
138 &self.polyline_gpu_data,
139 &self.volume_gpu_data,
140 &self.streamtube_gpu_data,
141 camera_bg
142 );
143 }
144 return encoder.finish();
145 }
146
147 let w = frame.camera.viewport_size[0] as u32;
149 let h = frame.camera.viewport_size[1] as u32;
150
151 self.resources.ensure_hdr_target(
152 device,
153 queue,
154 self.resources.target_format,
155 w.max(1),
156 h.max(1),
157 );
158
159 let pp = &frame.effects.post_process;
160
161 let mode = match pp.tone_mapping {
163 crate::renderer::ToneMapping::Reinhard => 0u32,
164 crate::renderer::ToneMapping::Aces => 1u32,
165 crate::renderer::ToneMapping::KhronosNeutral => 2u32,
166 };
167 let tm_uniform = crate::resources::ToneMapUniform {
168 exposure: pp.exposure,
169 mode,
170 bloom_enabled: if pp.bloom { 1 } else { 0 },
171 ssao_enabled: if pp.ssao { 1 } else { 0 },
172 contact_shadows_enabled: if pp.contact_shadows { 1 } else { 0 },
173 _pad_tm: [0; 3],
174 };
175 if let Some(buf) = &self.resources.tone_map_uniform_buf {
176 queue.write_buffer(buf, 0, bytemuck::cast_slice(&[tm_uniform]));
177 }
178
179 if pp.ssao {
181 if let Some(buf) = &self.resources.ssao_uniform_buf {
182 let proj = frame.camera.render_camera.projection;
183 let inv_proj = proj.inverse();
184 let ssao_uniform = crate::resources::SsaoUniform {
185 inv_proj: inv_proj.to_cols_array_2d(),
186 proj: proj.to_cols_array_2d(),
187 radius: 0.5,
188 bias: 0.025,
189 _pad: [0.0; 2],
190 };
191 queue.write_buffer(buf, 0, bytemuck::cast_slice(&[ssao_uniform]));
192 }
193 }
194
195 if pp.contact_shadows {
197 if let Some(buf) = &self.resources.contact_shadow_uniform_buf {
198 let proj = frame.camera.render_camera.projection;
199 let inv_proj = proj.inverse();
200 let light_dir_world: glam::Vec3 = if let Some(l) = frame.effects.lighting.lights.first() {
202 match l.kind {
203 LightKind::Directional { direction } => {
204 glam::Vec3::from(direction).normalize()
205 }
206 LightKind::Spot { direction, .. } => {
207 glam::Vec3::from(direction).normalize()
208 }
209 _ => glam::Vec3::new(0.0, -1.0, 0.0),
210 }
211 } else {
212 glam::Vec3::new(0.0, -1.0, 0.0)
213 };
214 let view = frame.camera.render_camera.view;
215 let light_dir_view = view.transform_vector3(light_dir_world).normalize();
216 let world_up_view = view.transform_vector3(glam::Vec3::Y).normalize();
217 let cs_uniform = crate::resources::ContactShadowUniform {
218 inv_proj: inv_proj.to_cols_array_2d(),
219 proj: proj.to_cols_array_2d(),
220 light_dir_view: [
221 light_dir_view.x,
222 light_dir_view.y,
223 light_dir_view.z,
224 0.0,
225 ],
226 world_up_view: [world_up_view.x, world_up_view.y, world_up_view.z, 0.0],
227 params: [
228 pp.contact_shadow_max_distance,
229 pp.contact_shadow_steps as f32,
230 pp.contact_shadow_thickness,
231 0.0,
232 ],
233 };
234 queue.write_buffer(buf, 0, bytemuck::cast_slice(&[cs_uniform]));
235 }
236 }
237
238 if pp.bloom {
240 if let Some(buf) = &self.resources.bloom_uniform_buf {
241 let bloom_u = crate::resources::BloomUniform {
242 threshold: pp.bloom_threshold,
243 intensity: pp.bloom_intensity,
244 horizontal: 0,
245 _pad: 0,
246 };
247 queue.write_buffer(buf, 0, bytemuck::cast_slice(&[bloom_u]));
248 }
249 }
250
251 self.resources.rebuild_tone_map_bind_group_with_device(
253 device,
254 pp.bloom,
255 pp.ssao,
256 pp.contact_shadows,
257 );
258
259 {
264 let needs_oit = if self.use_instancing && !self.instanced_batches.is_empty() {
265 self.instanced_batches.iter().any(|b| b.is_transparent)
266 } else {
267 scene_items
268 .iter()
269 .any(|i| i.visible && i.material.opacity < 1.0)
270 };
271 if needs_oit {
272 let w = (frame.camera.viewport_size[0] as u32).max(1);
273 let h = (frame.camera.viewport_size[1] as u32).max(1);
274 self.resources.ensure_oit_targets(device, w, h);
275 }
276 }
277
278 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
282 label: Some("hdr_encoder"),
283 });
284
285 let camera_bg = self.viewport_camera_bind_group(frame.camera.viewport_index);
287
288 {
292 let hdr_view = match &self.resources.hdr_view {
293 Some(v) => v,
294 None => {
295 return encoder.finish();
296 }
297 };
298 let hdr_depth_view = match &self.resources.hdr_depth_view {
299 Some(v) => v,
300 None => {
301 return encoder.finish();
302 }
303 };
304
305 let clear_wgpu = wgpu::Color {
306 r: bg_color[0] as f64,
307 g: bg_color[1] as f64,
308 b: bg_color[2] as f64,
309 a: bg_color[3] as f64,
310 };
311
312 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
313 label: Some("hdr_scene_pass"),
314 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
315 view: hdr_view,
316 resolve_target: None,
317 ops: wgpu::Operations {
318 load: wgpu::LoadOp::Clear(clear_wgpu),
319 store: wgpu::StoreOp::Store,
320 },
321 depth_slice: None,
322 })],
323 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
324 view: hdr_depth_view,
325 depth_ops: Some(wgpu::Operations {
326 load: wgpu::LoadOp::Clear(1.0),
327 store: wgpu::StoreOp::Store,
328 }),
329 stencil_ops: Some(wgpu::Operations {
330 load: wgpu::LoadOp::Clear(0),
331 store: wgpu::StoreOp::Store,
332 }),
333 }),
334 timestamp_writes: None,
335 occlusion_query_set: None,
336 });
337
338 let resources = &self.resources;
339 render_pass.set_bind_group(0, camera_bg, &[]);
340
341 let use_instancing = self.use_instancing;
342 let batches = &self.instanced_batches;
343
344 if !scene_items.is_empty() {
345 if use_instancing && !batches.is_empty() {
346 let excluded_items: Vec<&SceneRenderItem> = scene_items
347 .iter()
348 .filter(|item| {
349 item.visible
350 && (item.active_attribute.is_some() || item.two_sided)
351 && resources
352 .mesh_store
353 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
354 .is_some()
355 })
356 .collect();
357
358 let mut opaque_batches: Vec<&InstancedBatch> = Vec::new();
360 let mut transparent_batches: Vec<&InstancedBatch> = Vec::new();
361 for batch in batches {
362 if batch.is_transparent {
363 transparent_batches.push(batch);
364 } else {
365 opaque_batches.push(batch);
366 }
367 }
368
369 if !opaque_batches.is_empty() && !frame.viewport.wireframe_mode {
370 if let Some(ref pipeline) = resources.hdr_solid_instanced_pipeline {
371 render_pass.set_pipeline(pipeline);
372 for batch in &opaque_batches {
373 let Some(mesh) = resources
374 .mesh_store
375 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
376 else {
377 continue;
378 };
379 let mat_key = (
380 batch.texture_id.unwrap_or(u64::MAX),
381 batch.normal_map_id.unwrap_or(u64::MAX),
382 batch.ao_map_id.unwrap_or(u64::MAX),
383 );
384 let Some(inst_tex_bg) =
385 resources.instance_bind_groups.get(&mat_key)
386 else {
387 continue;
388 };
389 render_pass.set_bind_group(1, inst_tex_bg, &[]);
390 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
391 render_pass.set_index_buffer(
392 mesh.index_buffer.slice(..),
393 wgpu::IndexFormat::Uint32,
394 );
395 render_pass.draw_indexed(
396 0..mesh.index_count,
397 0,
398 batch.instance_offset
399 ..batch.instance_offset + batch.instance_count,
400 );
401 }
402 }
403 }
404
405 let _ = &transparent_batches; if frame.viewport.wireframe_mode {
410 if let Some(ref hdr_wf) = resources.hdr_wireframe_pipeline {
411 render_pass.set_pipeline(hdr_wf);
412 for item in scene_items {
413 if !item.visible {
414 continue;
415 }
416 let Some(mesh) = resources
417 .mesh_store
418 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
419 else {
420 continue;
421 };
422 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
423 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
424 render_pass.set_index_buffer(
425 mesh.edge_index_buffer.slice(..),
426 wgpu::IndexFormat::Uint32,
427 );
428 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
429 }
430 }
431 } else if let (Some(hdr_solid), Some(hdr_solid_two_sided)) = (
432 &resources.hdr_solid_pipeline,
433 &resources.hdr_solid_two_sided_pipeline,
434 ) {
435 for item in excluded_items
436 .into_iter()
437 .filter(|item| item.material.opacity >= 1.0)
438 {
439 let Some(mesh) = resources
440 .mesh_store
441 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
442 else {
443 continue;
444 };
445 let pipeline = if item.two_sided {
446 hdr_solid_two_sided
447 } else {
448 hdr_solid
449 };
450 render_pass.set_pipeline(pipeline);
451 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
452 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
453 render_pass.set_index_buffer(
454 mesh.index_buffer.slice(..),
455 wgpu::IndexFormat::Uint32,
456 );
457 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
458 }
459 }
460 } else {
461 let eye = glam::Vec3::from(frame.camera.render_camera.eye_position);
463 let dist_from_eye = |item: &&SceneRenderItem| -> f32 {
464 let pos =
465 glam::Vec3::new(item.model[3][0], item.model[3][1], item.model[3][2]);
466 (pos - eye).length()
467 };
468
469 let mut opaque: Vec<&SceneRenderItem> = Vec::new();
470 let mut transparent: Vec<&SceneRenderItem> = Vec::new();
471 for item in scene_items {
472 if !item.visible
473 || resources
474 .mesh_store
475 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
476 .is_none()
477 {
478 continue;
479 }
480 if item.material.opacity < 1.0 {
481 transparent.push(item);
482 } else {
483 opaque.push(item);
484 }
485 }
486 opaque.sort_by(|a, b| {
487 dist_from_eye(a)
488 .partial_cmp(&dist_from_eye(b))
489 .unwrap_or(std::cmp::Ordering::Equal)
490 });
491 transparent.sort_by(|a, b| {
492 dist_from_eye(b)
493 .partial_cmp(&dist_from_eye(a))
494 .unwrap_or(std::cmp::Ordering::Equal)
495 });
496
497 let draw_item_hdr =
498 |render_pass: &mut wgpu::RenderPass<'_>,
499 item: &SceneRenderItem,
500 solid_pl: &wgpu::RenderPipeline,
501 trans_pl: &wgpu::RenderPipeline,
502 wf_pl: &wgpu::RenderPipeline| {
503 let mesh = resources
504 .mesh_store
505 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
506 .unwrap();
507 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
510 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
511 if frame.viewport.wireframe_mode {
512 render_pass.set_pipeline(wf_pl);
513 render_pass.set_index_buffer(
514 mesh.edge_index_buffer.slice(..),
515 wgpu::IndexFormat::Uint32,
516 );
517 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
518 } else if item.material.opacity < 1.0 {
519 render_pass.set_pipeline(trans_pl);
520 render_pass.set_index_buffer(
521 mesh.index_buffer.slice(..),
522 wgpu::IndexFormat::Uint32,
523 );
524 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
525 } else {
526 render_pass.set_pipeline(solid_pl);
527 render_pass.set_index_buffer(
528 mesh.index_buffer.slice(..),
529 wgpu::IndexFormat::Uint32,
530 );
531 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
532 }
533 };
534
535 let _ = &transparent; if let (
539 Some(hdr_solid),
540 Some(hdr_solid_two_sided),
541 Some(hdr_trans),
542 Some(hdr_wf),
543 ) = (
544 &resources.hdr_solid_pipeline,
545 &resources.hdr_solid_two_sided_pipeline,
546 &resources.hdr_transparent_pipeline,
547 &resources.hdr_wireframe_pipeline,
548 ) {
549 for item in &opaque {
550 let solid_pl = if item.two_sided {
551 hdr_solid_two_sided
552 } else {
553 hdr_solid
554 };
555 draw_item_hdr(&mut render_pass, item, solid_pl, hdr_trans, hdr_wf);
556 }
557 }
558 }
559 }
560
561 if !resources.cap_buffers.is_empty() {
563 if let Some(ref hdr_overlay) = resources.hdr_overlay_pipeline {
564 render_pass.set_pipeline(hdr_overlay);
565 render_pass.set_bind_group(0, camera_bg, &[]);
566 for (vbuf, ibuf, idx_count, _ubuf, bg) in &resources.cap_buffers {
567 render_pass.set_bind_group(1, bg, &[]);
568 render_pass.set_vertex_buffer(0, vbuf.slice(..));
569 render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
570 render_pass.draw_indexed(0..*idx_count, 0, 0..1);
571 }
572 }
573 }
574
575 emit_scivis_draw_calls!(
577 &self.resources,
578 &mut render_pass,
579 &self.point_cloud_gpu_data,
580 &self.glyph_gpu_data,
581 &self.polyline_gpu_data,
582 &self.volume_gpu_data,
583 &self.streamtube_gpu_data,
584 camera_bg
585 );
586 }
587
588 let has_transparent = if self.use_instancing && !self.instanced_batches.is_empty() {
593 self.instanced_batches.iter().any(|b| b.is_transparent)
594 } else {
595 scene_items
596 .iter()
597 .any(|i| i.visible && i.material.opacity < 1.0)
598 };
599
600 if has_transparent {
601 if let (Some(accum_view), Some(reveal_view), Some(hdr_depth_view)) = (
603 self.resources.oit_accum_view.as_ref(),
604 self.resources.oit_reveal_view.as_ref(),
605 self.resources.hdr_depth_view.as_ref(),
606 ) {
607 let mut oit_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
609 label: Some("oit_pass"),
610 color_attachments: &[
611 Some(wgpu::RenderPassColorAttachment {
612 view: accum_view,
613 resolve_target: None,
614 ops: wgpu::Operations {
615 load: wgpu::LoadOp::Clear(wgpu::Color {
616 r: 0.0,
617 g: 0.0,
618 b: 0.0,
619 a: 0.0,
620 }),
621 store: wgpu::StoreOp::Store,
622 },
623 depth_slice: None,
624 }),
625 Some(wgpu::RenderPassColorAttachment {
626 view: reveal_view,
627 resolve_target: None,
628 ops: wgpu::Operations {
629 load: wgpu::LoadOp::Clear(wgpu::Color {
630 r: 1.0,
631 g: 1.0,
632 b: 1.0,
633 a: 1.0,
634 }),
635 store: wgpu::StoreOp::Store,
636 },
637 depth_slice: None,
638 }),
639 ],
640 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
641 view: hdr_depth_view,
642 depth_ops: Some(wgpu::Operations {
643 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
645 }),
646 stencil_ops: None,
647 }),
648 timestamp_writes: None,
649 occlusion_query_set: None,
650 });
651
652 oit_pass.set_bind_group(0, camera_bg, &[]);
653
654 if self.use_instancing && !self.instanced_batches.is_empty() {
655 if let Some(ref pipeline) = self.resources.oit_instanced_pipeline {
656 oit_pass.set_pipeline(pipeline);
657 for batch in &self.instanced_batches {
658 if !batch.is_transparent {
659 continue;
660 }
661 let Some(mesh) = self
662 .resources
663 .mesh_store
664 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
665 else {
666 continue;
667 };
668 let mat_key = (
669 batch.texture_id.unwrap_or(u64::MAX),
670 batch.normal_map_id.unwrap_or(u64::MAX),
671 batch.ao_map_id.unwrap_or(u64::MAX),
672 );
673 let Some(inst_tex_bg) =
674 self.resources.instance_bind_groups.get(&mat_key)
675 else {
676 continue;
677 };
678 oit_pass.set_bind_group(1, inst_tex_bg, &[]);
679 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
680 oit_pass.set_index_buffer(
681 mesh.index_buffer.slice(..),
682 wgpu::IndexFormat::Uint32,
683 );
684 oit_pass.draw_indexed(
685 0..mesh.index_count,
686 0,
687 batch.instance_offset..batch.instance_offset + batch.instance_count,
688 );
689 }
690 }
691 } else if let Some(ref pipeline) = self.resources.oit_pipeline {
692 oit_pass.set_pipeline(pipeline);
693 for item in scene_items {
694 if !item.visible || item.material.opacity >= 1.0 {
695 continue;
696 }
697 let Some(mesh) = self
698 .resources
699 .mesh_store
700 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
701 else {
702 continue;
703 };
704 oit_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
705 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
706 oit_pass.set_index_buffer(
707 mesh.index_buffer.slice(..),
708 wgpu::IndexFormat::Uint32,
709 );
710 oit_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
711 }
712 }
713 }
714 }
715
716 if has_transparent {
721 if let (Some(pipeline), Some(bg), Some(hdr_view)) = (
722 self.resources.oit_composite_pipeline.as_ref(),
723 self.resources.oit_composite_bind_group.as_ref(),
724 self.resources.hdr_view.as_ref(),
725 ) {
726 let mut composite_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
727 label: Some("oit_composite_pass"),
728 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
729 view: hdr_view,
730 resolve_target: None,
731 ops: wgpu::Operations {
732 load: wgpu::LoadOp::Load,
733 store: wgpu::StoreOp::Store,
734 },
735 depth_slice: None,
736 })],
737 depth_stencil_attachment: None,
738 timestamp_writes: None,
739 occlusion_query_set: None,
740 });
741 composite_pass.set_pipeline(pipeline);
742 composite_pass.set_bind_group(0, bg, &[]);
743 composite_pass.draw(0..3, 0..1);
744 }
745 }
746
747 if !self.resources.outline_object_buffers.is_empty() {
753 if let (Some(pipeline), Some(bg), Some(hdr_view), Some(hdr_depth_view)) = (
754 &self.resources.outline_composite_pipeline_single,
755 &self.resources.outline_composite_bind_group,
756 &self.resources.hdr_view,
757 &self.resources.hdr_depth_view,
758 ) {
759 let mut outline_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
760 label: Some("hdr_outline_composite_pass"),
761 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
762 view: hdr_view,
763 resolve_target: None,
764 ops: wgpu::Operations {
765 load: wgpu::LoadOp::Load,
766 store: wgpu::StoreOp::Store,
767 },
768 depth_slice: None,
769 })],
770 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
771 view: hdr_depth_view,
772 depth_ops: Some(wgpu::Operations {
773 load: wgpu::LoadOp::Load,
774 store: wgpu::StoreOp::Discard,
775 }),
776 stencil_ops: None,
777 }),
778 timestamp_writes: None,
779 occlusion_query_set: None,
780 });
781 outline_pass.set_pipeline(pipeline);
782 outline_pass.set_bind_group(0, bg, &[]);
783 outline_pass.draw(0..3, 0..1);
784 }
785 }
786
787 if pp.ssao {
791 if let (Some(ssao_bg), Some(ssao_pipeline), Some(ssao_view)) = (
792 &self.resources.ssao_bg,
793 &self.resources.ssao_pipeline,
794 &self.resources.ssao_view,
795 ) {
796 {
797 let mut ssao_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
798 label: Some("ssao_pass"),
799 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
800 view: ssao_view,
801 resolve_target: None,
802 ops: wgpu::Operations {
803 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
804 store: wgpu::StoreOp::Store,
805 },
806 depth_slice: None,
807 })],
808 depth_stencil_attachment: None,
809 timestamp_writes: None,
810 occlusion_query_set: None,
811 });
812 ssao_pass.set_pipeline(ssao_pipeline);
813 ssao_pass.set_bind_group(0, ssao_bg, &[]);
814 ssao_pass.draw(0..3, 0..1);
815 }
816
817 if let (Some(ssao_blur_bg), Some(ssao_blur_pipeline), Some(ssao_blur_view)) = (
819 &self.resources.ssao_blur_bg,
820 &self.resources.ssao_blur_pipeline,
821 &self.resources.ssao_blur_view,
822 ) {
823 let mut ssao_blur_pass =
824 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
825 label: Some("ssao_blur_pass"),
826 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
827 view: ssao_blur_view,
828 resolve_target: None,
829 ops: wgpu::Operations {
830 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
831 store: wgpu::StoreOp::Store,
832 },
833 depth_slice: None,
834 })],
835 depth_stencil_attachment: None,
836 timestamp_writes: None,
837 occlusion_query_set: None,
838 });
839 ssao_blur_pass.set_pipeline(ssao_blur_pipeline);
840 ssao_blur_pass.set_bind_group(0, ssao_blur_bg, &[]);
841 ssao_blur_pass.draw(0..3, 0..1);
842 }
843 }
844 }
845
846 if pp.contact_shadows {
850 if let (Some(cs_bg), Some(cs_pipeline), Some(cs_view)) = (
851 &self.resources.contact_shadow_bg,
852 &self.resources.contact_shadow_pipeline,
853 &self.resources.contact_shadow_view,
854 ) {
855 let mut cs_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
856 label: Some("contact_shadow_pass"),
857 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
858 view: cs_view,
859 resolve_target: None,
860 depth_slice: None,
861 ops: wgpu::Operations {
862 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
863 store: wgpu::StoreOp::Store,
864 },
865 })],
866 depth_stencil_attachment: None,
867 timestamp_writes: None,
868 occlusion_query_set: None,
869 });
870 cs_pass.set_pipeline(cs_pipeline);
871 cs_pass.set_bind_group(0, cs_bg, &[]);
872 cs_pass.draw(0..3, 0..1);
873 }
874 }
875
876 if pp.bloom {
880 if let (
882 Some(bloom_threshold_bg),
883 Some(bloom_threshold_pipeline),
884 Some(bloom_threshold_view),
885 ) = (
886 &self.resources.bloom_threshold_bg,
887 &self.resources.bloom_threshold_pipeline,
888 &self.resources.bloom_threshold_view,
889 ) {
890 {
891 let mut threshold_pass =
892 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
893 label: Some("bloom_threshold_pass"),
894 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
895 view: bloom_threshold_view,
896 resolve_target: None,
897 ops: wgpu::Operations {
898 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
899 store: wgpu::StoreOp::Store,
900 },
901 depth_slice: None,
902 })],
903 depth_stencil_attachment: None,
904 timestamp_writes: None,
905 occlusion_query_set: None,
906 });
907 threshold_pass.set_pipeline(bloom_threshold_pipeline);
908 threshold_pass.set_bind_group(0, bloom_threshold_bg, &[]);
909 threshold_pass.draw(0..3, 0..1);
910 }
911
912 if let (
915 Some(blur_h_bg),
916 Some(blur_h_pong_bg),
917 Some(blur_v_bg),
918 Some(blur_pipeline),
919 Some(bloom_ping_view),
920 Some(bloom_pong_view),
921 ) = (
922 &self.resources.bloom_blur_h_bg,
923 &self.resources.bloom_blur_h_pong_bg,
924 &self.resources.bloom_blur_v_bg,
925 &self.resources.bloom_blur_pipeline,
926 &self.resources.bloom_ping_view,
927 &self.resources.bloom_pong_view,
928 ) {
929 const BLUR_ITERATIONS: usize = 4;
930 for i in 0..BLUR_ITERATIONS {
931 let h_bg = if i == 0 { blur_h_bg } else { blur_h_pong_bg };
933 {
934 let mut h_pass =
935 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
936 label: Some("bloom_blur_h_pass"),
937 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
938 view: bloom_ping_view,
939 resolve_target: None,
940 ops: wgpu::Operations {
941 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
942 store: wgpu::StoreOp::Store,
943 },
944 depth_slice: None,
945 })],
946 depth_stencil_attachment: None,
947 timestamp_writes: None,
948 occlusion_query_set: None,
949 });
950 h_pass.set_pipeline(blur_pipeline);
951 h_pass.set_bind_group(0, h_bg, &[]);
952 h_pass.draw(0..3, 0..1);
953 }
954 {
956 let mut v_pass =
957 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
958 label: Some("bloom_blur_v_pass"),
959 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
960 view: bloom_pong_view,
961 resolve_target: None,
962 ops: wgpu::Operations {
963 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
964 store: wgpu::StoreOp::Store,
965 },
966 depth_slice: None,
967 })],
968 depth_stencil_attachment: None,
969 timestamp_writes: None,
970 occlusion_query_set: None,
971 });
972 v_pass.set_pipeline(blur_pipeline);
973 v_pass.set_bind_group(0, blur_v_bg, &[]);
974 v_pass.draw(0..3, 0..1);
975 }
976 }
977 }
978 }
979 }
980
981 let use_fxaa = pp.fxaa && self.resources.fxaa_view.is_some();
985 if let (Some(tone_map_pipeline), Some(tone_map_bg)) = (
986 &self.resources.tone_map_pipeline,
987 &self.resources.tone_map_bind_group,
988 ) {
989 let tone_target: &wgpu::TextureView = if use_fxaa {
990 self.resources.fxaa_view.as_ref().unwrap()
991 } else {
992 output_view
993 };
994 let mut tone_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
995 label: Some("tone_map_pass"),
996 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
997 view: tone_target,
998 resolve_target: None,
999 ops: wgpu::Operations {
1000 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1001 store: wgpu::StoreOp::Store,
1002 },
1003 depth_slice: None,
1004 })],
1005 depth_stencil_attachment: None,
1006 timestamp_writes: None,
1007 occlusion_query_set: None,
1008 });
1009 tone_pass.set_pipeline(tone_map_pipeline);
1010 tone_pass.set_bind_group(0, tone_map_bg, &[]);
1011 tone_pass.draw(0..3, 0..1);
1012 }
1013
1014 if use_fxaa {
1018 if let (Some(fxaa_pipeline), Some(fxaa_bg)) = (
1019 &self.resources.fxaa_pipeline,
1020 &self.resources.fxaa_bind_group,
1021 ) {
1022 let mut fxaa_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1023 label: Some("fxaa_pass"),
1024 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1025 view: output_view,
1026 resolve_target: None,
1027 ops: wgpu::Operations {
1028 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1029 store: wgpu::StoreOp::Store,
1030 },
1031 depth_slice: None,
1032 })],
1033 depth_stencil_attachment: None,
1034 timestamp_writes: None,
1035 occlusion_query_set: None,
1036 });
1037 fxaa_pass.set_pipeline(fxaa_pipeline);
1038 fxaa_pass.set_bind_group(0, fxaa_bg, &[]);
1039 fxaa_pass.draw(0..3, 0..1);
1040 }
1041 }
1042
1043 encoder.finish()
1044 }
1045
1046 pub fn render_offscreen(
1060 &mut self,
1061 device: &wgpu::Device,
1062 queue: &wgpu::Queue,
1063 frame: &FrameData,
1064 width: u32,
1065 height: u32,
1066 ) -> Vec<u8> {
1067 let target_format = self.resources.target_format;
1069 let offscreen_texture = device.create_texture(&wgpu::TextureDescriptor {
1070 label: Some("offscreen_target"),
1071 size: wgpu::Extent3d {
1072 width: width.max(1),
1073 height: height.max(1),
1074 depth_or_array_layers: 1,
1075 },
1076 mip_level_count: 1,
1077 sample_count: 1,
1078 dimension: wgpu::TextureDimension::D2,
1079 format: target_format,
1080 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
1081 view_formats: &[],
1082 });
1083
1084 let output_view = offscreen_texture.create_view(&wgpu::TextureViewDescriptor::default());
1086
1087 self.resources
1092 .ensure_outline_target(device, width.max(1), height.max(1));
1093
1094 let cmd_buf = self.render(device, queue, &output_view, frame);
1099 queue.submit(std::iter::once(cmd_buf));
1100
1101 let bytes_per_pixel = 4u32;
1103 let unpadded_row = width * bytes_per_pixel;
1104 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
1105 let padded_row = (unpadded_row + align - 1) & !(align - 1);
1106 let buffer_size = (padded_row * height.max(1)) as u64;
1107
1108 let staging_buf = device.create_buffer(&wgpu::BufferDescriptor {
1109 label: Some("offscreen_staging"),
1110 size: buffer_size,
1111 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1112 mapped_at_creation: false,
1113 });
1114
1115 let mut copy_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1116 label: Some("offscreen_copy_encoder"),
1117 });
1118 copy_encoder.copy_texture_to_buffer(
1119 wgpu::TexelCopyTextureInfo {
1120 texture: &offscreen_texture,
1121 mip_level: 0,
1122 origin: wgpu::Origin3d::ZERO,
1123 aspect: wgpu::TextureAspect::All,
1124 },
1125 wgpu::TexelCopyBufferInfo {
1126 buffer: &staging_buf,
1127 layout: wgpu::TexelCopyBufferLayout {
1128 offset: 0,
1129 bytes_per_row: Some(padded_row),
1130 rows_per_image: Some(height.max(1)),
1131 },
1132 },
1133 wgpu::Extent3d {
1134 width: width.max(1),
1135 height: height.max(1),
1136 depth_or_array_layers: 1,
1137 },
1138 );
1139 queue.submit(std::iter::once(copy_encoder.finish()));
1140
1141 let (tx, rx) = std::sync::mpsc::channel();
1143 staging_buf
1144 .slice(..)
1145 .map_async(wgpu::MapMode::Read, move |result| {
1146 let _ = tx.send(result);
1147 });
1148 device
1149 .poll(wgpu::PollType::Wait {
1150 submission_index: None,
1151 timeout: Some(std::time::Duration::from_secs(5)),
1152 })
1153 .unwrap();
1154 let _ = rx.recv().unwrap_or(Err(wgpu::BufferAsyncError));
1155
1156 let mut pixels: Vec<u8> = Vec::with_capacity((width * height * 4) as usize);
1157 {
1158 let mapped = staging_buf.slice(..).get_mapped_range();
1159 let data: &[u8] = &mapped;
1160 if padded_row == unpadded_row {
1161 pixels.extend_from_slice(data);
1163 } else {
1164 for row in 0..height as usize {
1166 let start = row * padded_row as usize;
1167 let end = start + unpadded_row as usize;
1168 pixels.extend_from_slice(&data[start..end]);
1169 }
1170 }
1171 }
1172 staging_buf.unmap();
1173
1174 let is_bgra = matches!(
1176 target_format,
1177 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
1178 );
1179 if is_bgra {
1180 for pixel in pixels.chunks_exact_mut(4) {
1181 pixel.swap(0, 2); }
1183 }
1184
1185 pixels
1186 }
1187}