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