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