1use super::*;
2
3impl ViewportRenderer {
4 pub fn paint(&self, render_pass: &mut wgpu::RenderPass<'static>, frame: &FrameData) {
10 let vp_idx = frame.camera.viewport_index;
11 let camera_bg = self.viewport_camera_bind_group(vp_idx);
12 let grid_bg = self.viewport_grid_bind_group(vp_idx);
13 let vp_slot = self.viewport_slots.get(vp_idx);
14 emit_draw_calls!(
15 &self.resources,
16 &mut *render_pass,
17 frame,
18 self.use_instancing,
19 &self.instanced_batches,
20 camera_bg,
21 grid_bg,
22 &self.compute_filter_results,
23 vp_slot
24 );
25 emit_scivis_draw_calls!(
26 &self.resources,
27 &mut *render_pass,
28 &self.point_cloud_gpu_data,
29 &self.glyph_gpu_data,
30 &self.polyline_gpu_data,
31 &self.volume_gpu_data,
32 &self.streamtube_gpu_data,
33 camera_bg
34 );
35 if !self.screen_image_gpu_data.is_empty() {
37 if let Some(pipeline) = &self.resources.screen_image_pipeline {
38 render_pass.set_pipeline(pipeline);
39 for gpu in &self.screen_image_gpu_data {
40 render_pass.set_bind_group(0, &gpu.bind_group, &[]);
41 render_pass.draw(0..6, 0..1);
42 }
43 }
44 }
45 }
46
47 pub fn paint_to<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>, frame: &FrameData) {
53 let vp_idx = frame.camera.viewport_index;
54 let camera_bg = self.viewport_camera_bind_group(vp_idx);
55 let grid_bg = self.viewport_grid_bind_group(vp_idx);
56 let vp_slot = self.viewport_slots.get(vp_idx);
57 emit_draw_calls!(
58 &self.resources,
59 &mut *render_pass,
60 frame,
61 self.use_instancing,
62 &self.instanced_batches,
63 camera_bg,
64 grid_bg,
65 &self.compute_filter_results,
66 vp_slot
67 );
68 emit_scivis_draw_calls!(
69 &self.resources,
70 &mut *render_pass,
71 &self.point_cloud_gpu_data,
72 &self.glyph_gpu_data,
73 &self.polyline_gpu_data,
74 &self.volume_gpu_data,
75 &self.streamtube_gpu_data,
76 camera_bg
77 );
78 if !self.screen_image_gpu_data.is_empty() {
80 if let Some(pipeline) = &self.resources.screen_image_pipeline {
81 render_pass.set_pipeline(pipeline);
82 for gpu in &self.screen_image_gpu_data {
83 render_pass.set_bind_group(0, &gpu.bind_group, &[]);
84 render_pass.draw(0..6, 0..1);
85 }
86 }
87 }
88 }
89
90 pub fn render_viewport(
104 &mut self,
105 device: &wgpu::Device,
106 queue: &wgpu::Queue,
107 output_view: &wgpu::TextureView,
108 id: ViewportId,
109 frame: &FrameData,
110 ) -> wgpu::CommandBuffer {
111 self.render_frame_internal(device, queue, output_view, id.0, frame)
112 }
113
114 pub fn render(
122 &mut self,
123 device: &wgpu::Device,
124 queue: &wgpu::Queue,
125 output_view: &wgpu::TextureView,
126 frame: &FrameData,
127 ) -> wgpu::CommandBuffer {
128 self.prepare(device, queue, frame);
130 self.render_frame_internal(
131 device,
132 queue,
133 output_view,
134 frame.camera.viewport_index,
135 frame,
136 )
137 }
138
139 fn render_frame_internal(
144 &mut self,
145 device: &wgpu::Device,
146 queue: &wgpu::Queue,
147 output_view: &wgpu::TextureView,
148 vp_idx: usize,
149 frame: &FrameData,
150 ) -> wgpu::CommandBuffer {
151 let scene_items: &[SceneRenderItem] = match &frame.scene.surfaces {
153 SurfaceSubmission::Flat(items) => items,
154 };
155
156 let bg_color = frame.viewport.background_color.unwrap_or([
157 65.0 / 255.0,
158 65.0 / 255.0,
159 65.0 / 255.0,
160 1.0,
161 ]);
162 let w = frame.camera.viewport_size[0] as u32;
163 let h = frame.camera.viewport_size[1] as u32;
164
165 let ssaa_factor = frame.effects.post_process.ssaa_factor.max(1);
167 self.ensure_viewport_hdr(device, queue, vp_idx, w.max(1), h.max(1), ssaa_factor);
168
169 if !frame.effects.post_process.enabled {
170 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
172 label: Some("ldr_encoder"),
173 });
174 {
175 let slot = &self.viewport_slots[vp_idx];
176 let slot_hdr = slot.hdr.as_ref().unwrap();
177 let camera_bg = &slot.camera_bind_group;
178 let grid_bg = &slot.grid_bind_group;
179 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
180 label: Some("ldr_render_pass"),
181 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
182 view: output_view,
183 resolve_target: None,
184 ops: wgpu::Operations {
185 load: wgpu::LoadOp::Clear(wgpu::Color {
186 r: bg_color[0] as f64,
187 g: bg_color[1] as f64,
188 b: bg_color[2] as f64,
189 a: bg_color[3] as f64,
190 }),
191 store: wgpu::StoreOp::Store,
192 },
193 depth_slice: None,
194 })],
195 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
196 view: &slot_hdr.outline_depth_view,
197 depth_ops: Some(wgpu::Operations {
198 load: wgpu::LoadOp::Clear(1.0),
199 store: wgpu::StoreOp::Discard,
200 }),
201 stencil_ops: None,
202 }),
203 timestamp_writes: None,
204 occlusion_query_set: None,
205 });
206 emit_draw_calls!(
207 &self.resources,
208 &mut render_pass,
209 frame,
210 self.use_instancing,
211 &self.instanced_batches,
212 camera_bg,
213 grid_bg,
214 &self.compute_filter_results,
215 Some(slot)
216 );
217 emit_scivis_draw_calls!(
218 &self.resources,
219 &mut render_pass,
220 &self.point_cloud_gpu_data,
221 &self.glyph_gpu_data,
222 &self.polyline_gpu_data,
223 &self.volume_gpu_data,
224 &self.streamtube_gpu_data,
225 camera_bg
226 );
227 if !self.screen_image_gpu_data.is_empty() {
232 if let Some(overlay_pipeline) = &self.resources.screen_image_pipeline {
233 let dc_pipeline = self.resources.screen_image_dc_pipeline.as_ref();
234 for gpu in &self.screen_image_gpu_data {
235 if let (Some(dc_bg), Some(dc_pipe)) =
236 (&gpu.depth_bind_group, dc_pipeline)
237 {
238 render_pass.set_pipeline(dc_pipe);
239 render_pass.set_bind_group(0, dc_bg, &[]);
240 } else {
241 render_pass.set_pipeline(overlay_pipeline);
242 render_pass.set_bind_group(0, &gpu.bind_group, &[]);
243 }
244 render_pass.draw(0..6, 0..1);
245 }
246 }
247 }
248 }
249 return encoder.finish();
250 }
251
252 let pp = &frame.effects.post_process;
254
255 let hdr_clear_rgb = [
256 bg_color[0].powf(2.2),
257 bg_color[1].powf(2.2),
258 bg_color[2].powf(2.2),
259 ];
260
261 let mode = match pp.tone_mapping {
263 crate::renderer::ToneMapping::Reinhard => 0u32,
264 crate::renderer::ToneMapping::Aces => 1u32,
265 crate::renderer::ToneMapping::KhronosNeutral => 2u32,
266 };
267 let tm_uniform = crate::resources::ToneMapUniform {
268 exposure: pp.exposure,
269 mode,
270 bloom_enabled: if pp.bloom { 1 } else { 0 },
271 ssao_enabled: if pp.ssao { 1 } else { 0 },
272 contact_shadows_enabled: if pp.contact_shadows { 1 } else { 0 },
273 _pad_tm: [0; 3],
274 background_color: bg_color,
275 };
276 {
277 let hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
278 queue.write_buffer(
279 &hdr.tone_map_uniform_buf,
280 0,
281 bytemuck::cast_slice(&[tm_uniform]),
282 );
283
284 if pp.ssao {
286 let proj = frame.camera.render_camera.projection;
287 let inv_proj = proj.inverse();
288 let ssao_uniform = crate::resources::SsaoUniform {
289 inv_proj: inv_proj.to_cols_array_2d(),
290 proj: proj.to_cols_array_2d(),
291 radius: 0.5,
292 bias: 0.025,
293 _pad: [0.0; 2],
294 };
295 queue.write_buffer(
296 &hdr.ssao_uniform_buf,
297 0,
298 bytemuck::cast_slice(&[ssao_uniform]),
299 );
300 }
301
302 if pp.contact_shadows {
304 let proj = frame.camera.render_camera.projection;
305 let inv_proj = proj.inverse();
306 let light_dir_world: glam::Vec3 =
307 if let Some(l) = frame.effects.lighting.lights.first() {
308 match l.kind {
309 LightKind::Directional { direction } => {
310 glam::Vec3::from(direction).normalize()
311 }
312 LightKind::Spot { direction, .. } => {
313 glam::Vec3::from(direction).normalize()
314 }
315 _ => glam::Vec3::new(0.0, -1.0, 0.0),
316 }
317 } else {
318 glam::Vec3::new(0.0, -1.0, 0.0)
319 };
320 let view = frame.camera.render_camera.view;
321 let light_dir_view = view.transform_vector3(light_dir_world).normalize();
322 let world_up_view = view.transform_vector3(glam::Vec3::Z).normalize();
323 let cs_uniform = crate::resources::ContactShadowUniform {
324 inv_proj: inv_proj.to_cols_array_2d(),
325 proj: proj.to_cols_array_2d(),
326 light_dir_view: [light_dir_view.x, light_dir_view.y, light_dir_view.z, 0.0],
327 world_up_view: [world_up_view.x, world_up_view.y, world_up_view.z, 0.0],
328 params: [
329 pp.contact_shadow_max_distance,
330 pp.contact_shadow_steps as f32,
331 pp.contact_shadow_thickness,
332 0.0,
333 ],
334 };
335 queue.write_buffer(
336 &hdr.contact_shadow_uniform_buf,
337 0,
338 bytemuck::cast_slice(&[cs_uniform]),
339 );
340 }
341
342 if pp.bloom {
344 let bloom_u = crate::resources::BloomUniform {
345 threshold: pp.bloom_threshold,
346 intensity: pp.bloom_intensity,
347 horizontal: 0,
348 _pad: 0,
349 };
350 queue.write_buffer(&hdr.bloom_uniform_buf, 0, bytemuck::cast_slice(&[bloom_u]));
351 }
352 }
353
354 {
356 let hdr = self.viewport_slots[vp_idx].hdr.as_mut().unwrap();
357 self.resources.rebuild_tone_map_bind_group(
358 device,
359 hdr,
360 pp.bloom,
361 pp.ssao,
362 pp.contact_shadows,
363 );
364 }
365
366 {
371 let needs_oit = if self.use_instancing && !self.instanced_batches.is_empty() {
372 self.instanced_batches.iter().any(|b| b.is_transparent)
373 } else {
374 scene_items
375 .iter()
376 .any(|i| i.visible && i.material.opacity < 1.0)
377 };
378 if needs_oit {
379 let hdr = self.viewport_slots[vp_idx].hdr.as_mut().unwrap();
380 self.resources
381 .ensure_viewport_oit(device, hdr, w.max(1), h.max(1));
382 }
383 }
384
385 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
389 label: Some("hdr_encoder"),
390 });
391
392 let slot = &self.viewport_slots[vp_idx];
394 let camera_bg = &slot.camera_bind_group;
395 let slot_hdr = slot.hdr.as_ref().unwrap();
396
397 {
401 let use_ssaa = ssaa_factor > 1
403 && slot_hdr.ssaa_color_view.is_some()
404 && slot_hdr.ssaa_depth_view.is_some();
405 let scene_color_view = if use_ssaa {
406 slot_hdr.ssaa_color_view.as_ref().unwrap()
407 } else {
408 &slot_hdr.hdr_view
409 };
410 let scene_depth_view = if use_ssaa {
411 slot_hdr.ssaa_depth_view.as_ref().unwrap()
412 } else {
413 &slot_hdr.hdr_depth_view
414 };
415
416 let clear_wgpu = wgpu::Color {
417 r: hdr_clear_rgb[0] as f64,
418 g: hdr_clear_rgb[1] as f64,
419 b: hdr_clear_rgb[2] as f64,
420 a: bg_color[3] as f64,
421 };
422
423 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
424 label: Some("hdr_scene_pass"),
425 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
426 view: scene_color_view,
427 resolve_target: None,
428 ops: wgpu::Operations {
429 load: wgpu::LoadOp::Clear(clear_wgpu),
430 store: wgpu::StoreOp::Store,
431 },
432 depth_slice: None,
433 })],
434 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
435 view: scene_depth_view,
436 depth_ops: Some(wgpu::Operations {
437 load: wgpu::LoadOp::Clear(1.0),
438 store: wgpu::StoreOp::Store,
439 }),
440 stencil_ops: Some(wgpu::Operations {
441 load: wgpu::LoadOp::Clear(0),
442 store: wgpu::StoreOp::Store,
443 }),
444 }),
445 timestamp_writes: None,
446 occlusion_query_set: None,
447 });
448
449 let resources = &self.resources;
450 render_pass.set_bind_group(0, camera_bg, &[]);
451
452 let show_skybox = frame
454 .effects
455 .environment
456 .as_ref()
457 .is_some_and(|e| e.show_skybox)
458 && resources.ibl_skybox_view.is_some();
459
460 let use_instancing = self.use_instancing;
461 let batches = &self.instanced_batches;
462
463 if !scene_items.is_empty() {
464 if use_instancing && !batches.is_empty() {
465 let excluded_items: Vec<&SceneRenderItem> = scene_items
466 .iter()
467 .filter(|item| {
468 item.visible
469 && (item.active_attribute.is_some()
470 || item.material.is_two_sided()
471 || item.material.matcap_id.is_some())
472 && resources
473 .mesh_store
474 .get(item.mesh_id)
475 .is_some()
476 })
477 .collect();
478
479 let mut opaque_batches: Vec<&InstancedBatch> = Vec::new();
481 let mut transparent_batches: Vec<&InstancedBatch> = Vec::new();
482 for batch in batches {
483 if batch.is_transparent {
484 transparent_batches.push(batch);
485 } else {
486 opaque_batches.push(batch);
487 }
488 }
489
490 if !opaque_batches.is_empty() && !frame.viewport.wireframe_mode {
491 if let Some(ref pipeline) = resources.hdr_solid_instanced_pipeline {
492 render_pass.set_pipeline(pipeline);
493 for batch in &opaque_batches {
494 let Some(mesh) = resources
495 .mesh_store
496 .get(batch.mesh_id)
497 else {
498 continue;
499 };
500 let mat_key = (
501 batch.texture_id.unwrap_or(u64::MAX),
502 batch.normal_map_id.unwrap_or(u64::MAX),
503 batch.ao_map_id.unwrap_or(u64::MAX),
504 );
505 let Some(inst_tex_bg) =
506 resources.instance_bind_groups.get(&mat_key)
507 else {
508 continue;
509 };
510 render_pass.set_bind_group(1, inst_tex_bg, &[]);
511 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
512 render_pass.set_index_buffer(
513 mesh.index_buffer.slice(..),
514 wgpu::IndexFormat::Uint32,
515 );
516 render_pass.draw_indexed(
517 0..mesh.index_count,
518 0,
519 batch.instance_offset
520 ..batch.instance_offset + batch.instance_count,
521 );
522 }
523 }
524 }
525
526 let _ = &transparent_batches; if frame.viewport.wireframe_mode {
531 if let Some(ref hdr_wf) = resources.hdr_wireframe_pipeline {
532 render_pass.set_pipeline(hdr_wf);
533 for item in scene_items {
534 if !item.visible {
535 continue;
536 }
537 let Some(mesh) = resources
538 .mesh_store
539 .get(item.mesh_id)
540 else {
541 continue;
542 };
543 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
544 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
545 render_pass.set_index_buffer(
546 mesh.edge_index_buffer.slice(..),
547 wgpu::IndexFormat::Uint32,
548 );
549 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
550 }
551 }
552 } else if let (Some(hdr_solid), Some(hdr_solid_two_sided)) = (
553 &resources.hdr_solid_pipeline,
554 &resources.hdr_solid_two_sided_pipeline,
555 ) {
556 for item in excluded_items
557 .into_iter()
558 .filter(|item| item.material.opacity >= 1.0)
559 {
560 let Some(mesh) = resources
561 .mesh_store
562 .get(item.mesh_id)
563 else {
564 continue;
565 };
566 let pipeline = if item.material.is_two_sided() {
567 hdr_solid_two_sided
568 } else {
569 hdr_solid
570 };
571 render_pass.set_pipeline(pipeline);
572 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
573 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
574 render_pass.set_index_buffer(
575 mesh.index_buffer.slice(..),
576 wgpu::IndexFormat::Uint32,
577 );
578 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
579 }
580 }
581 } else {
582 let eye = glam::Vec3::from(frame.camera.render_camera.eye_position);
584 let dist_from_eye = |item: &&SceneRenderItem| -> f32 {
585 let pos =
586 glam::Vec3::new(item.model[3][0], item.model[3][1], item.model[3][2]);
587 (pos - eye).length()
588 };
589
590 let mut opaque: Vec<&SceneRenderItem> = Vec::new();
591 let mut transparent: Vec<&SceneRenderItem> = Vec::new();
592 for item in scene_items {
593 if !item.visible
594 || resources
595 .mesh_store
596 .get(item.mesh_id)
597 .is_none()
598 {
599 continue;
600 }
601 if item.material.opacity < 1.0 {
602 transparent.push(item);
603 } else {
604 opaque.push(item);
605 }
606 }
607 opaque.sort_by(|a, b| {
608 dist_from_eye(a)
609 .partial_cmp(&dist_from_eye(b))
610 .unwrap_or(std::cmp::Ordering::Equal)
611 });
612 transparent.sort_by(|a, b| {
613 dist_from_eye(b)
614 .partial_cmp(&dist_from_eye(a))
615 .unwrap_or(std::cmp::Ordering::Equal)
616 });
617
618 let draw_item_hdr =
619 |render_pass: &mut wgpu::RenderPass<'_>,
620 item: &SceneRenderItem,
621 solid_pl: &wgpu::RenderPipeline,
622 trans_pl: &wgpu::RenderPipeline,
623 wf_pl: &wgpu::RenderPipeline| {
624 let mesh = resources
625 .mesh_store
626 .get(item.mesh_id)
627 .unwrap();
628 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
631 let is_face_attr = item.active_attribute.as_ref().map_or(false, |a| {
632 matches!(
633 a.kind,
634 crate::resources::AttributeKind::Face
635 | crate::resources::AttributeKind::FaceColor
636 | crate::resources::AttributeKind::Halfedge
637 | crate::resources::AttributeKind::Corner
638 )
639 });
640 if frame.viewport.wireframe_mode {
641 render_pass.set_pipeline(wf_pl);
642 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
643 render_pass.set_index_buffer(
644 mesh.edge_index_buffer.slice(..),
645 wgpu::IndexFormat::Uint32,
646 );
647 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
648 } else if is_face_attr {
649 if let Some(ref fvb) = mesh.face_vertex_buffer {
650 let pl = if item.material.opacity < 1.0 {
651 trans_pl
652 } else {
653 solid_pl
654 };
655 render_pass.set_pipeline(pl);
656 render_pass.set_vertex_buffer(0, fvb.slice(..));
657 render_pass.draw(0..mesh.index_count, 0..1);
658 }
659 } else if item.material.opacity < 1.0 {
660 render_pass.set_pipeline(trans_pl);
661 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
662 render_pass.set_index_buffer(
663 mesh.index_buffer.slice(..),
664 wgpu::IndexFormat::Uint32,
665 );
666 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
667 } else {
668 render_pass.set_pipeline(solid_pl);
669 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
670 render_pass.set_index_buffer(
671 mesh.index_buffer.slice(..),
672 wgpu::IndexFormat::Uint32,
673 );
674 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
675 }
676 };
677
678 let _ = &transparent; if let (
682 Some(hdr_solid),
683 Some(hdr_solid_two_sided),
684 Some(hdr_trans),
685 Some(hdr_wf),
686 ) = (
687 &resources.hdr_solid_pipeline,
688 &resources.hdr_solid_two_sided_pipeline,
689 &resources.hdr_transparent_pipeline,
690 &resources.hdr_wireframe_pipeline,
691 ) {
692 for item in &opaque {
693 let solid_pl = if item.material.is_two_sided() {
694 hdr_solid_two_sided
695 } else {
696 hdr_solid
697 };
698 draw_item_hdr(&mut render_pass, item, solid_pl, hdr_trans, hdr_wf);
699 }
700 }
701 }
702 }
703
704 if !slot.cap_buffers.is_empty() {
706 if let Some(ref hdr_overlay) = resources.hdr_overlay_pipeline {
707 render_pass.set_pipeline(hdr_overlay);
708 render_pass.set_bind_group(0, camera_bg, &[]);
709 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.cap_buffers {
710 render_pass.set_bind_group(1, bg, &[]);
711 render_pass.set_vertex_buffer(0, vbuf.slice(..));
712 render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
713 render_pass.draw_indexed(0..*idx_count, 0, 0..1);
714 }
715 }
716 }
717
718 emit_scivis_draw_calls!(
720 &self.resources,
721 &mut render_pass,
722 &self.point_cloud_gpu_data,
723 &self.glyph_gpu_data,
724 &self.polyline_gpu_data,
725 &self.volume_gpu_data,
726 &self.streamtube_gpu_data,
727 camera_bg
728 );
729
730 if show_skybox {
732 render_pass.set_bind_group(0, camera_bg, &[]);
733 render_pass.set_pipeline(&resources.skybox_pipeline);
734 render_pass.draw(0..3, 0..1);
735 }
736 }
737
738 if ssaa_factor > 1 {
743 let slot_hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
744 if let (Some(pipeline), Some(bg)) = (
745 &self.resources.ssaa_resolve_pipeline,
746 &slot_hdr.ssaa_resolve_bind_group,
747 ) {
748 let mut resolve_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
749 label: Some("ssaa_resolve_pass"),
750 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
751 view: &slot_hdr.hdr_view,
752 resolve_target: None,
753 ops: wgpu::Operations {
754 load: wgpu::LoadOp::Load,
755 store: wgpu::StoreOp::Store,
756 },
757 depth_slice: None,
758 })],
759 depth_stencil_attachment: None,
760 timestamp_writes: None,
761 occlusion_query_set: None,
762 });
763 resolve_pass.set_pipeline(pipeline);
764 resolve_pass.set_bind_group(0, bg, &[]);
765 resolve_pass.draw(0..3, 0..1);
766 }
767 }
768
769 let has_transparent = if self.use_instancing && !self.instanced_batches.is_empty() {
774 self.instanced_batches.iter().any(|b| b.is_transparent)
775 } else {
776 scene_items
777 .iter()
778 .any(|i| i.visible && i.material.opacity < 1.0)
779 };
780
781 if has_transparent {
782 if let (Some(accum_view), Some(reveal_view)) = (
784 slot_hdr.oit_accum_view.as_ref(),
785 slot_hdr.oit_reveal_view.as_ref(),
786 ) {
787 let hdr_depth_view = &slot_hdr.hdr_depth_view;
788 let mut oit_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
790 label: Some("oit_pass"),
791 color_attachments: &[
792 Some(wgpu::RenderPassColorAttachment {
793 view: accum_view,
794 resolve_target: None,
795 ops: wgpu::Operations {
796 load: wgpu::LoadOp::Clear(wgpu::Color {
797 r: 0.0,
798 g: 0.0,
799 b: 0.0,
800 a: 0.0,
801 }),
802 store: wgpu::StoreOp::Store,
803 },
804 depth_slice: None,
805 }),
806 Some(wgpu::RenderPassColorAttachment {
807 view: reveal_view,
808 resolve_target: None,
809 ops: wgpu::Operations {
810 load: wgpu::LoadOp::Clear(wgpu::Color {
811 r: 1.0,
812 g: 1.0,
813 b: 1.0,
814 a: 1.0,
815 }),
816 store: wgpu::StoreOp::Store,
817 },
818 depth_slice: None,
819 }),
820 ],
821 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
822 view: hdr_depth_view,
823 depth_ops: Some(wgpu::Operations {
824 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
826 }),
827 stencil_ops: None,
828 }),
829 timestamp_writes: None,
830 occlusion_query_set: None,
831 });
832
833 oit_pass.set_bind_group(0, camera_bg, &[]);
834
835 if self.use_instancing && !self.instanced_batches.is_empty() {
836 if let Some(ref pipeline) = self.resources.oit_instanced_pipeline {
837 oit_pass.set_pipeline(pipeline);
838 for batch in &self.instanced_batches {
839 if !batch.is_transparent {
840 continue;
841 }
842 let Some(mesh) = self
843 .resources
844 .mesh_store
845 .get(batch.mesh_id)
846 else {
847 continue;
848 };
849 let mat_key = (
850 batch.texture_id.unwrap_or(u64::MAX),
851 batch.normal_map_id.unwrap_or(u64::MAX),
852 batch.ao_map_id.unwrap_or(u64::MAX),
853 );
854 let Some(inst_tex_bg) =
855 self.resources.instance_bind_groups.get(&mat_key)
856 else {
857 continue;
858 };
859 oit_pass.set_bind_group(1, inst_tex_bg, &[]);
860 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
861 oit_pass.set_index_buffer(
862 mesh.index_buffer.slice(..),
863 wgpu::IndexFormat::Uint32,
864 );
865 oit_pass.draw_indexed(
866 0..mesh.index_count,
867 0,
868 batch.instance_offset..batch.instance_offset + batch.instance_count,
869 );
870 }
871 }
872 } else if let Some(ref pipeline) = self.resources.oit_pipeline {
873 oit_pass.set_pipeline(pipeline);
874 for item in scene_items {
875 if !item.visible || item.material.opacity >= 1.0 {
876 continue;
877 }
878 let Some(mesh) = self
879 .resources
880 .mesh_store
881 .get(item.mesh_id)
882 else {
883 continue;
884 };
885 oit_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
886 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
887 oit_pass.set_index_buffer(
888 mesh.index_buffer.slice(..),
889 wgpu::IndexFormat::Uint32,
890 );
891 oit_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
892 }
893 }
894 }
895 }
896
897 if has_transparent {
902 if let (Some(pipeline), Some(bg)) = (
903 self.resources.oit_composite_pipeline.as_ref(),
904 slot_hdr.oit_composite_bind_group.as_ref(),
905 ) {
906 let hdr_view = &slot_hdr.hdr_view;
907 let mut composite_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
908 label: Some("oit_composite_pass"),
909 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
910 view: hdr_view,
911 resolve_target: None,
912 ops: wgpu::Operations {
913 load: wgpu::LoadOp::Load,
914 store: wgpu::StoreOp::Store,
915 },
916 depth_slice: None,
917 })],
918 depth_stencil_attachment: None,
919 timestamp_writes: None,
920 occlusion_query_set: None,
921 });
922 composite_pass.set_pipeline(pipeline);
923 composite_pass.set_bind_group(0, bg, &[]);
924 composite_pass.draw(0..3, 0..1);
925 }
926 }
927
928 if !slot.outline_object_buffers.is_empty() {
934 let hdr_pipeline = self
936 .resources
937 .outline_composite_pipeline_hdr
938 .as_ref()
939 .or(self.resources.outline_composite_pipeline_single.as_ref());
940 if let Some(pipeline) = hdr_pipeline {
941 let bg = &slot_hdr.outline_composite_bind_group;
942 let hdr_view = &slot_hdr.hdr_view;
943 let hdr_depth_view = &slot_hdr.hdr_depth_view;
944 let mut outline_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
945 label: Some("hdr_outline_composite_pass"),
946 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
947 view: hdr_view,
948 resolve_target: None,
949 ops: wgpu::Operations {
950 load: wgpu::LoadOp::Load,
951 store: wgpu::StoreOp::Store,
952 },
953 depth_slice: None,
954 })],
955 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
956 view: hdr_depth_view,
957 depth_ops: Some(wgpu::Operations {
958 load: wgpu::LoadOp::Load,
959 store: wgpu::StoreOp::Store,
960 }),
961 stencil_ops: None,
962 }),
963 timestamp_writes: None,
964 occlusion_query_set: None,
965 });
966 outline_pass.set_pipeline(pipeline);
967 outline_pass.set_bind_group(0, bg, &[]);
968 outline_pass.draw(0..3, 0..1);
969 }
970 }
971
972 if pp.ssao {
976 if let Some(ssao_pipeline) = &self.resources.ssao_pipeline {
977 {
978 let mut ssao_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
979 label: Some("ssao_pass"),
980 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
981 view: &slot_hdr.ssao_view,
982 resolve_target: None,
983 ops: wgpu::Operations {
984 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
985 store: wgpu::StoreOp::Store,
986 },
987 depth_slice: None,
988 })],
989 depth_stencil_attachment: None,
990 timestamp_writes: None,
991 occlusion_query_set: None,
992 });
993 ssao_pass.set_pipeline(ssao_pipeline);
994 ssao_pass.set_bind_group(0, &slot_hdr.ssao_bg, &[]);
995 ssao_pass.draw(0..3, 0..1);
996 }
997
998 if let Some(ssao_blur_pipeline) = &self.resources.ssao_blur_pipeline {
1000 let mut ssao_blur_pass =
1001 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1002 label: Some("ssao_blur_pass"),
1003 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1004 view: &slot_hdr.ssao_blur_view,
1005 resolve_target: None,
1006 ops: wgpu::Operations {
1007 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
1008 store: wgpu::StoreOp::Store,
1009 },
1010 depth_slice: None,
1011 })],
1012 depth_stencil_attachment: None,
1013 timestamp_writes: None,
1014 occlusion_query_set: None,
1015 });
1016 ssao_blur_pass.set_pipeline(ssao_blur_pipeline);
1017 ssao_blur_pass.set_bind_group(0, &slot_hdr.ssao_blur_bg, &[]);
1018 ssao_blur_pass.draw(0..3, 0..1);
1019 }
1020 }
1021 }
1022
1023 if pp.contact_shadows {
1027 if let Some(cs_pipeline) = &self.resources.contact_shadow_pipeline {
1028 let mut cs_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1029 label: Some("contact_shadow_pass"),
1030 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1031 view: &slot_hdr.contact_shadow_view,
1032 resolve_target: None,
1033 depth_slice: None,
1034 ops: wgpu::Operations {
1035 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
1036 store: wgpu::StoreOp::Store,
1037 },
1038 })],
1039 depth_stencil_attachment: None,
1040 timestamp_writes: None,
1041 occlusion_query_set: None,
1042 });
1043 cs_pass.set_pipeline(cs_pipeline);
1044 cs_pass.set_bind_group(0, &slot_hdr.contact_shadow_bg, &[]);
1045 cs_pass.draw(0..3, 0..1);
1046 }
1047 }
1048
1049 if pp.bloom {
1053 if let Some(bloom_threshold_pipeline) = &self.resources.bloom_threshold_pipeline {
1055 {
1056 let mut threshold_pass =
1057 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1058 label: Some("bloom_threshold_pass"),
1059 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1060 view: &slot_hdr.bloom_threshold_view,
1061 resolve_target: None,
1062 ops: wgpu::Operations {
1063 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1064 store: wgpu::StoreOp::Store,
1065 },
1066 depth_slice: None,
1067 })],
1068 depth_stencil_attachment: None,
1069 timestamp_writes: None,
1070 occlusion_query_set: None,
1071 });
1072 threshold_pass.set_pipeline(bloom_threshold_pipeline);
1073 threshold_pass.set_bind_group(0, &slot_hdr.bloom_threshold_bg, &[]);
1074 threshold_pass.draw(0..3, 0..1);
1075 }
1076
1077 if let Some(blur_pipeline) = &self.resources.bloom_blur_pipeline {
1080 let blur_h_bg = &slot_hdr.bloom_blur_h_bg;
1081 let blur_h_pong_bg = &slot_hdr.bloom_blur_h_pong_bg;
1082 let blur_v_bg = &slot_hdr.bloom_blur_v_bg;
1083 let bloom_ping_view = &slot_hdr.bloom_ping_view;
1084 let bloom_pong_view = &slot_hdr.bloom_pong_view;
1085 const BLUR_ITERATIONS: usize = 4;
1086 for i in 0..BLUR_ITERATIONS {
1087 let h_bg = if i == 0 { blur_h_bg } else { blur_h_pong_bg };
1089 {
1090 let mut h_pass =
1091 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1092 label: Some("bloom_blur_h_pass"),
1093 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1094 view: bloom_ping_view,
1095 resolve_target: None,
1096 ops: wgpu::Operations {
1097 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1098 store: wgpu::StoreOp::Store,
1099 },
1100 depth_slice: None,
1101 })],
1102 depth_stencil_attachment: None,
1103 timestamp_writes: None,
1104 occlusion_query_set: None,
1105 });
1106 h_pass.set_pipeline(blur_pipeline);
1107 h_pass.set_bind_group(0, h_bg, &[]);
1108 h_pass.draw(0..3, 0..1);
1109 }
1110 {
1112 let mut v_pass =
1113 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1114 label: Some("bloom_blur_v_pass"),
1115 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1116 view: bloom_pong_view,
1117 resolve_target: None,
1118 ops: wgpu::Operations {
1119 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1120 store: wgpu::StoreOp::Store,
1121 },
1122 depth_slice: None,
1123 })],
1124 depth_stencil_attachment: None,
1125 timestamp_writes: None,
1126 occlusion_query_set: None,
1127 });
1128 v_pass.set_pipeline(blur_pipeline);
1129 v_pass.set_bind_group(0, blur_v_bg, &[]);
1130 v_pass.draw(0..3, 0..1);
1131 }
1132 }
1133 }
1134 }
1135 }
1136
1137 let use_fxaa = pp.fxaa;
1141 if let Some(tone_map_pipeline) = &self.resources.tone_map_pipeline {
1142 let tone_target: &wgpu::TextureView = if use_fxaa {
1143 &slot_hdr.fxaa_view
1144 } else {
1145 output_view
1146 };
1147 let mut tone_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1148 label: Some("tone_map_pass"),
1149 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1150 view: tone_target,
1151 resolve_target: None,
1152 ops: wgpu::Operations {
1153 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1154 store: wgpu::StoreOp::Store,
1155 },
1156 depth_slice: None,
1157 })],
1158 depth_stencil_attachment: None,
1159 timestamp_writes: None,
1160 occlusion_query_set: None,
1161 });
1162 tone_pass.set_pipeline(tone_map_pipeline);
1163 tone_pass.set_bind_group(0, &slot_hdr.tone_map_bind_group, &[]);
1164 tone_pass.draw(0..3, 0..1);
1165 }
1166
1167 if use_fxaa {
1171 if let Some(fxaa_pipeline) = &self.resources.fxaa_pipeline {
1172 let mut fxaa_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1173 label: Some("fxaa_pass"),
1174 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1175 view: output_view,
1176 resolve_target: None,
1177 ops: wgpu::Operations {
1178 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1179 store: wgpu::StoreOp::Store,
1180 },
1181 depth_slice: None,
1182 })],
1183 depth_stencil_attachment: None,
1184 timestamp_writes: None,
1185 occlusion_query_set: None,
1186 });
1187 fxaa_pass.set_pipeline(fxaa_pipeline);
1188 fxaa_pass.set_bind_group(0, &slot_hdr.fxaa_bind_group, &[]);
1189 fxaa_pass.draw(0..3, 0..1);
1190 }
1191 }
1192
1193 if frame.viewport.show_grid {
1197 let slot = &self.viewport_slots[vp_idx];
1198 let slot_hdr = slot.hdr.as_ref().unwrap();
1199 let grid_bg = &slot.grid_bind_group;
1200 let mut grid_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1201 label: Some("hdr_grid_pass"),
1202 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1203 view: output_view,
1204 resolve_target: None,
1205 ops: wgpu::Operations {
1206 load: wgpu::LoadOp::Load,
1207 store: wgpu::StoreOp::Store,
1208 },
1209 depth_slice: None,
1210 })],
1211 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1212 view: &slot_hdr.hdr_depth_view,
1213 depth_ops: Some(wgpu::Operations {
1214 load: wgpu::LoadOp::Load,
1215 store: wgpu::StoreOp::Store,
1216 }),
1217 stencil_ops: None,
1218 }),
1219 timestamp_writes: None,
1220 occlusion_query_set: None,
1221 });
1222 grid_pass.set_pipeline(&self.resources.grid_pipeline);
1223 grid_pass.set_bind_group(0, grid_bg, &[]);
1224 grid_pass.draw(0..3, 0..1);
1225 }
1226
1227 if !matches!(
1230 frame.effects.ground_plane.mode,
1231 crate::renderer::types::GroundPlaneMode::None
1232 ) {
1233 let slot = &self.viewport_slots[vp_idx];
1234 let slot_hdr = slot.hdr.as_ref().unwrap();
1235 let mut gp_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1236 label: Some("hdr_ground_plane_pass"),
1237 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1238 view: output_view,
1239 resolve_target: None,
1240 ops: wgpu::Operations {
1241 load: wgpu::LoadOp::Load,
1242 store: wgpu::StoreOp::Store,
1243 },
1244 depth_slice: None,
1245 })],
1246 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1247 view: &slot_hdr.hdr_depth_view,
1248 depth_ops: Some(wgpu::Operations {
1249 load: wgpu::LoadOp::Load,
1250 store: wgpu::StoreOp::Store,
1251 }),
1252 stencil_ops: None,
1253 }),
1254 timestamp_writes: None,
1255 occlusion_query_set: None,
1256 });
1257 gp_pass.set_pipeline(&self.resources.ground_plane_pipeline);
1258 gp_pass.set_bind_group(0, &self.resources.ground_plane_bind_group, &[]);
1259 gp_pass.draw(0..3, 0..1);
1260 }
1261
1262 {
1266 let slot = &self.viewport_slots[vp_idx];
1267 let slot_hdr = slot.hdr.as_ref().unwrap();
1268 let has_editor_overlays = (frame.interaction.gizmo_model.is_some()
1269 && slot.gizmo_index_count > 0)
1270 || !slot.constraint_line_buffers.is_empty()
1271 || !slot.clip_plane_fill_buffers.is_empty()
1272 || !slot.clip_plane_line_buffers.is_empty()
1273 || !slot.xray_object_buffers.is_empty();
1274 if has_editor_overlays {
1275 let camera_bg = &slot.camera_bind_group;
1276 let mut overlay_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1277 label: Some("hdr_editor_overlay_pass"),
1278 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1279 view: output_view,
1280 resolve_target: None,
1281 ops: wgpu::Operations {
1282 load: wgpu::LoadOp::Load,
1283 store: wgpu::StoreOp::Store,
1284 },
1285 depth_slice: None,
1286 })],
1287 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1288 view: &slot_hdr.hdr_depth_view,
1289 depth_ops: Some(wgpu::Operations {
1290 load: wgpu::LoadOp::Load,
1291 store: wgpu::StoreOp::Discard,
1292 }),
1293 stencil_ops: None,
1294 }),
1295 timestamp_writes: None,
1296 occlusion_query_set: None,
1297 });
1298
1299 if frame.interaction.gizmo_model.is_some() && slot.gizmo_index_count > 0 {
1300 overlay_pass.set_pipeline(&self.resources.gizmo_pipeline);
1301 overlay_pass.set_bind_group(0, camera_bg, &[]);
1302 overlay_pass.set_bind_group(1, &slot.gizmo_bind_group, &[]);
1303 overlay_pass.set_vertex_buffer(0, slot.gizmo_vertex_buffer.slice(..));
1304 overlay_pass.set_index_buffer(
1305 slot.gizmo_index_buffer.slice(..),
1306 wgpu::IndexFormat::Uint32,
1307 );
1308 overlay_pass.draw_indexed(0..slot.gizmo_index_count, 0, 0..1);
1309 }
1310
1311 if !slot.constraint_line_buffers.is_empty() {
1312 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1313 overlay_pass.set_bind_group(0, camera_bg, &[]);
1314 for (vbuf, ibuf, index_count, _ubuf, bg) in &slot.constraint_line_buffers {
1315 overlay_pass.set_bind_group(1, bg, &[]);
1316 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1317 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1318 overlay_pass.draw_indexed(0..*index_count, 0, 0..1);
1319 }
1320 }
1321
1322 if !slot.clip_plane_fill_buffers.is_empty() {
1323 overlay_pass.set_pipeline(&self.resources.overlay_pipeline);
1324 overlay_pass.set_bind_group(0, camera_bg, &[]);
1325 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_fill_buffers {
1326 overlay_pass.set_bind_group(1, bg, &[]);
1327 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1328 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1329 overlay_pass.draw_indexed(0..*idx_count, 0, 0..1);
1330 }
1331 }
1332
1333 if !slot.clip_plane_line_buffers.is_empty() {
1334 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1335 overlay_pass.set_bind_group(0, camera_bg, &[]);
1336 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_line_buffers {
1337 overlay_pass.set_bind_group(1, bg, &[]);
1338 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1339 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1340 overlay_pass.draw_indexed(0..*idx_count, 0, 0..1);
1341 }
1342 }
1343
1344 if !slot.xray_object_buffers.is_empty() {
1345 overlay_pass.set_pipeline(&self.resources.xray_pipeline);
1346 overlay_pass.set_bind_group(0, camera_bg, &[]);
1347 for (mesh_id, _buf, bg) in &slot.xray_object_buffers {
1348 let Some(mesh) = self
1349 .resources
1350 .mesh_store
1351 .get(*mesh_id)
1352 else {
1353 continue;
1354 };
1355 overlay_pass.set_bind_group(1, bg, &[]);
1356 overlay_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
1357 overlay_pass.set_index_buffer(
1358 mesh.index_buffer.slice(..),
1359 wgpu::IndexFormat::Uint32,
1360 );
1361 overlay_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
1362 }
1363 }
1364 }
1365 }
1366
1367 if frame.viewport.show_axes_indicator {
1370 let slot = &self.viewport_slots[vp_idx];
1371 if slot.axes_vertex_count > 0 {
1372 let slot_hdr = slot.hdr.as_ref().unwrap();
1373 let mut axes_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1374 label: Some("hdr_axes_pass"),
1375 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1376 view: output_view,
1377 resolve_target: None,
1378 ops: wgpu::Operations {
1379 load: wgpu::LoadOp::Load,
1380 store: wgpu::StoreOp::Store,
1381 },
1382 depth_slice: None,
1383 })],
1384 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1385 view: &slot_hdr.hdr_depth_view,
1386 depth_ops: Some(wgpu::Operations {
1387 load: wgpu::LoadOp::Load,
1388 store: wgpu::StoreOp::Discard,
1389 }),
1390 stencil_ops: None,
1391 }),
1392 timestamp_writes: None,
1393 occlusion_query_set: None,
1394 });
1395 axes_pass.set_pipeline(&self.resources.axes_pipeline);
1396 axes_pass.set_vertex_buffer(0, slot.axes_vertex_buffer.slice(..));
1397 axes_pass.draw(0..slot.axes_vertex_count, 0..1);
1398 }
1399 }
1400
1401 if !self.screen_image_gpu_data.is_empty() {
1405 if let Some(overlay_pipeline) = &self.resources.screen_image_pipeline {
1406 let slot_hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
1407 let dc_pipeline = self.resources.screen_image_dc_pipeline.as_ref();
1408 let mut img_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1409 label: Some("screen_image_pass"),
1410 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1411 view: output_view,
1412 resolve_target: None,
1413 ops: wgpu::Operations {
1414 load: wgpu::LoadOp::Load,
1415 store: wgpu::StoreOp::Store,
1416 },
1417 depth_slice: None,
1418 })],
1419 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1420 view: &slot_hdr.hdr_depth_view,
1421 depth_ops: Some(wgpu::Operations {
1422 load: wgpu::LoadOp::Load,
1423 store: wgpu::StoreOp::Discard,
1424 }),
1425 stencil_ops: None,
1426 }),
1427 timestamp_writes: None,
1428 occlusion_query_set: None,
1429 });
1430 for gpu in &self.screen_image_gpu_data {
1431 if let (Some(dc_bg), Some(dc_pipe)) = (&gpu.depth_bind_group, dc_pipeline) {
1432 img_pass.set_pipeline(dc_pipe);
1433 img_pass.set_bind_group(0, dc_bg, &[]);
1434 } else {
1435 img_pass.set_pipeline(overlay_pipeline);
1436 img_pass.set_bind_group(0, &gpu.bind_group, &[]);
1437 }
1438 img_pass.draw(0..6, 0..1);
1439 }
1440 }
1441 }
1442
1443 encoder.finish()
1444 }
1445
1446 pub fn render_offscreen(
1460 &mut self,
1461 device: &wgpu::Device,
1462 queue: &wgpu::Queue,
1463 frame: &FrameData,
1464 width: u32,
1465 height: u32,
1466 ) -> Vec<u8> {
1467 let target_format = self.resources.target_format;
1469 let offscreen_texture = device.create_texture(&wgpu::TextureDescriptor {
1470 label: Some("offscreen_target"),
1471 size: wgpu::Extent3d {
1472 width: width.max(1),
1473 height: height.max(1),
1474 depth_or_array_layers: 1,
1475 },
1476 mip_level_count: 1,
1477 sample_count: 1,
1478 dimension: wgpu::TextureDimension::D2,
1479 format: target_format,
1480 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
1481 view_formats: &[],
1482 });
1483
1484 let output_view = offscreen_texture.create_view(&wgpu::TextureViewDescriptor::default());
1486
1487 let cmd_buf = self.render(device, queue, &output_view, frame);
1495 queue.submit(std::iter::once(cmd_buf));
1496
1497 let bytes_per_pixel = 4u32;
1499 let unpadded_row = width * bytes_per_pixel;
1500 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
1501 let padded_row = (unpadded_row + align - 1) & !(align - 1);
1502 let buffer_size = (padded_row * height.max(1)) as u64;
1503
1504 let staging_buf = device.create_buffer(&wgpu::BufferDescriptor {
1505 label: Some("offscreen_staging"),
1506 size: buffer_size,
1507 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1508 mapped_at_creation: false,
1509 });
1510
1511 let mut copy_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1512 label: Some("offscreen_copy_encoder"),
1513 });
1514 copy_encoder.copy_texture_to_buffer(
1515 wgpu::TexelCopyTextureInfo {
1516 texture: &offscreen_texture,
1517 mip_level: 0,
1518 origin: wgpu::Origin3d::ZERO,
1519 aspect: wgpu::TextureAspect::All,
1520 },
1521 wgpu::TexelCopyBufferInfo {
1522 buffer: &staging_buf,
1523 layout: wgpu::TexelCopyBufferLayout {
1524 offset: 0,
1525 bytes_per_row: Some(padded_row),
1526 rows_per_image: Some(height.max(1)),
1527 },
1528 },
1529 wgpu::Extent3d {
1530 width: width.max(1),
1531 height: height.max(1),
1532 depth_or_array_layers: 1,
1533 },
1534 );
1535 queue.submit(std::iter::once(copy_encoder.finish()));
1536
1537 let (tx, rx) = std::sync::mpsc::channel();
1539 staging_buf
1540 .slice(..)
1541 .map_async(wgpu::MapMode::Read, move |result| {
1542 let _ = tx.send(result);
1543 });
1544 device
1545 .poll(wgpu::PollType::Wait {
1546 submission_index: None,
1547 timeout: Some(std::time::Duration::from_secs(5)),
1548 })
1549 .unwrap();
1550 let _ = rx.recv().unwrap_or(Err(wgpu::BufferAsyncError));
1551
1552 let mut pixels: Vec<u8> = Vec::with_capacity((width * height * 4) as usize);
1553 {
1554 let mapped = staging_buf.slice(..).get_mapped_range();
1555 let data: &[u8] = &mapped;
1556 if padded_row == unpadded_row {
1557 pixels.extend_from_slice(data);
1559 } else {
1560 for row in 0..height as usize {
1562 let start = row * padded_row as usize;
1563 let end = start + unpadded_row as usize;
1564 pixels.extend_from_slice(&data[start..end]);
1565 }
1566 }
1567 }
1568 staging_buf.unmap();
1569
1570 let is_bgra = matches!(
1572 target_format,
1573 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
1574 );
1575 if is_bgra {
1576 for pixel in pixels.chunks_exact_mut(4) {
1577 pixel.swap(0, 2); }
1579 }
1580
1581 pixels
1582 }
1583}