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 )
637 });
638 if frame.viewport.wireframe_mode {
639 render_pass.set_pipeline(wf_pl);
640 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
641 render_pass.set_index_buffer(
642 mesh.edge_index_buffer.slice(..),
643 wgpu::IndexFormat::Uint32,
644 );
645 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
646 } else if is_face_attr {
647 if let Some(ref fvb) = mesh.face_vertex_buffer {
648 let pl = if item.material.opacity < 1.0 {
649 trans_pl
650 } else {
651 solid_pl
652 };
653 render_pass.set_pipeline(pl);
654 render_pass.set_vertex_buffer(0, fvb.slice(..));
655 render_pass.draw(0..mesh.index_count, 0..1);
656 }
657 } else if item.material.opacity < 1.0 {
658 render_pass.set_pipeline(trans_pl);
659 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
660 render_pass.set_index_buffer(
661 mesh.index_buffer.slice(..),
662 wgpu::IndexFormat::Uint32,
663 );
664 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
665 } else {
666 render_pass.set_pipeline(solid_pl);
667 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
668 render_pass.set_index_buffer(
669 mesh.index_buffer.slice(..),
670 wgpu::IndexFormat::Uint32,
671 );
672 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
673 }
674 };
675
676 let _ = &transparent; if let (
680 Some(hdr_solid),
681 Some(hdr_solid_two_sided),
682 Some(hdr_trans),
683 Some(hdr_wf),
684 ) = (
685 &resources.hdr_solid_pipeline,
686 &resources.hdr_solid_two_sided_pipeline,
687 &resources.hdr_transparent_pipeline,
688 &resources.hdr_wireframe_pipeline,
689 ) {
690 for item in &opaque {
691 let solid_pl = if item.material.is_two_sided() {
692 hdr_solid_two_sided
693 } else {
694 hdr_solid
695 };
696 draw_item_hdr(&mut render_pass, item, solid_pl, hdr_trans, hdr_wf);
697 }
698 }
699 }
700 }
701
702 if !slot.cap_buffers.is_empty() {
704 if let Some(ref hdr_overlay) = resources.hdr_overlay_pipeline {
705 render_pass.set_pipeline(hdr_overlay);
706 render_pass.set_bind_group(0, camera_bg, &[]);
707 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.cap_buffers {
708 render_pass.set_bind_group(1, bg, &[]);
709 render_pass.set_vertex_buffer(0, vbuf.slice(..));
710 render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
711 render_pass.draw_indexed(0..*idx_count, 0, 0..1);
712 }
713 }
714 }
715
716 emit_scivis_draw_calls!(
718 &self.resources,
719 &mut render_pass,
720 &self.point_cloud_gpu_data,
721 &self.glyph_gpu_data,
722 &self.polyline_gpu_data,
723 &self.volume_gpu_data,
724 &self.streamtube_gpu_data,
725 camera_bg
726 );
727
728 if show_skybox {
730 render_pass.set_bind_group(0, camera_bg, &[]);
731 render_pass.set_pipeline(&resources.skybox_pipeline);
732 render_pass.draw(0..3, 0..1);
733 }
734 }
735
736 if ssaa_factor > 1 {
741 let slot_hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
742 if let (Some(pipeline), Some(bg)) = (
743 &self.resources.ssaa_resolve_pipeline,
744 &slot_hdr.ssaa_resolve_bind_group,
745 ) {
746 let mut resolve_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
747 label: Some("ssaa_resolve_pass"),
748 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
749 view: &slot_hdr.hdr_view,
750 resolve_target: None,
751 ops: wgpu::Operations {
752 load: wgpu::LoadOp::Load,
753 store: wgpu::StoreOp::Store,
754 },
755 depth_slice: None,
756 })],
757 depth_stencil_attachment: None,
758 timestamp_writes: None,
759 occlusion_query_set: None,
760 });
761 resolve_pass.set_pipeline(pipeline);
762 resolve_pass.set_bind_group(0, bg, &[]);
763 resolve_pass.draw(0..3, 0..1);
764 }
765 }
766
767 let has_transparent = if self.use_instancing && !self.instanced_batches.is_empty() {
772 self.instanced_batches.iter().any(|b| b.is_transparent)
773 } else {
774 scene_items
775 .iter()
776 .any(|i| i.visible && i.material.opacity < 1.0)
777 };
778
779 if has_transparent {
780 if let (Some(accum_view), Some(reveal_view)) = (
782 slot_hdr.oit_accum_view.as_ref(),
783 slot_hdr.oit_reveal_view.as_ref(),
784 ) {
785 let hdr_depth_view = &slot_hdr.hdr_depth_view;
786 let mut oit_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
788 label: Some("oit_pass"),
789 color_attachments: &[
790 Some(wgpu::RenderPassColorAttachment {
791 view: accum_view,
792 resolve_target: None,
793 ops: wgpu::Operations {
794 load: wgpu::LoadOp::Clear(wgpu::Color {
795 r: 0.0,
796 g: 0.0,
797 b: 0.0,
798 a: 0.0,
799 }),
800 store: wgpu::StoreOp::Store,
801 },
802 depth_slice: None,
803 }),
804 Some(wgpu::RenderPassColorAttachment {
805 view: reveal_view,
806 resolve_target: None,
807 ops: wgpu::Operations {
808 load: wgpu::LoadOp::Clear(wgpu::Color {
809 r: 1.0,
810 g: 1.0,
811 b: 1.0,
812 a: 1.0,
813 }),
814 store: wgpu::StoreOp::Store,
815 },
816 depth_slice: None,
817 }),
818 ],
819 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
820 view: hdr_depth_view,
821 depth_ops: Some(wgpu::Operations {
822 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
824 }),
825 stencil_ops: None,
826 }),
827 timestamp_writes: None,
828 occlusion_query_set: None,
829 });
830
831 oit_pass.set_bind_group(0, camera_bg, &[]);
832
833 if self.use_instancing && !self.instanced_batches.is_empty() {
834 if let Some(ref pipeline) = self.resources.oit_instanced_pipeline {
835 oit_pass.set_pipeline(pipeline);
836 for batch in &self.instanced_batches {
837 if !batch.is_transparent {
838 continue;
839 }
840 let Some(mesh) = self
841 .resources
842 .mesh_store
843 .get(batch.mesh_id)
844 else {
845 continue;
846 };
847 let mat_key = (
848 batch.texture_id.unwrap_or(u64::MAX),
849 batch.normal_map_id.unwrap_or(u64::MAX),
850 batch.ao_map_id.unwrap_or(u64::MAX),
851 );
852 let Some(inst_tex_bg) =
853 self.resources.instance_bind_groups.get(&mat_key)
854 else {
855 continue;
856 };
857 oit_pass.set_bind_group(1, inst_tex_bg, &[]);
858 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
859 oit_pass.set_index_buffer(
860 mesh.index_buffer.slice(..),
861 wgpu::IndexFormat::Uint32,
862 );
863 oit_pass.draw_indexed(
864 0..mesh.index_count,
865 0,
866 batch.instance_offset..batch.instance_offset + batch.instance_count,
867 );
868 }
869 }
870 } else if let Some(ref pipeline) = self.resources.oit_pipeline {
871 oit_pass.set_pipeline(pipeline);
872 for item in scene_items {
873 if !item.visible || item.material.opacity >= 1.0 {
874 continue;
875 }
876 let Some(mesh) = self
877 .resources
878 .mesh_store
879 .get(item.mesh_id)
880 else {
881 continue;
882 };
883 oit_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
884 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
885 oit_pass.set_index_buffer(
886 mesh.index_buffer.slice(..),
887 wgpu::IndexFormat::Uint32,
888 );
889 oit_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
890 }
891 }
892 }
893 }
894
895 if has_transparent {
900 if let (Some(pipeline), Some(bg)) = (
901 self.resources.oit_composite_pipeline.as_ref(),
902 slot_hdr.oit_composite_bind_group.as_ref(),
903 ) {
904 let hdr_view = &slot_hdr.hdr_view;
905 let mut composite_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
906 label: Some("oit_composite_pass"),
907 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
908 view: hdr_view,
909 resolve_target: None,
910 ops: wgpu::Operations {
911 load: wgpu::LoadOp::Load,
912 store: wgpu::StoreOp::Store,
913 },
914 depth_slice: None,
915 })],
916 depth_stencil_attachment: None,
917 timestamp_writes: None,
918 occlusion_query_set: None,
919 });
920 composite_pass.set_pipeline(pipeline);
921 composite_pass.set_bind_group(0, bg, &[]);
922 composite_pass.draw(0..3, 0..1);
923 }
924 }
925
926 if !slot.outline_object_buffers.is_empty() {
932 let hdr_pipeline = self
934 .resources
935 .outline_composite_pipeline_hdr
936 .as_ref()
937 .or(self.resources.outline_composite_pipeline_single.as_ref());
938 if let Some(pipeline) = hdr_pipeline {
939 let bg = &slot_hdr.outline_composite_bind_group;
940 let hdr_view = &slot_hdr.hdr_view;
941 let hdr_depth_view = &slot_hdr.hdr_depth_view;
942 let mut outline_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
943 label: Some("hdr_outline_composite_pass"),
944 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
945 view: hdr_view,
946 resolve_target: None,
947 ops: wgpu::Operations {
948 load: wgpu::LoadOp::Load,
949 store: wgpu::StoreOp::Store,
950 },
951 depth_slice: None,
952 })],
953 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
954 view: hdr_depth_view,
955 depth_ops: Some(wgpu::Operations {
956 load: wgpu::LoadOp::Load,
957 store: wgpu::StoreOp::Store,
958 }),
959 stencil_ops: None,
960 }),
961 timestamp_writes: None,
962 occlusion_query_set: None,
963 });
964 outline_pass.set_pipeline(pipeline);
965 outline_pass.set_bind_group(0, bg, &[]);
966 outline_pass.draw(0..3, 0..1);
967 }
968 }
969
970 if pp.ssao {
974 if let Some(ssao_pipeline) = &self.resources.ssao_pipeline {
975 {
976 let mut ssao_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
977 label: Some("ssao_pass"),
978 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
979 view: &slot_hdr.ssao_view,
980 resolve_target: None,
981 ops: wgpu::Operations {
982 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
983 store: wgpu::StoreOp::Store,
984 },
985 depth_slice: None,
986 })],
987 depth_stencil_attachment: None,
988 timestamp_writes: None,
989 occlusion_query_set: None,
990 });
991 ssao_pass.set_pipeline(ssao_pipeline);
992 ssao_pass.set_bind_group(0, &slot_hdr.ssao_bg, &[]);
993 ssao_pass.draw(0..3, 0..1);
994 }
995
996 if let Some(ssao_blur_pipeline) = &self.resources.ssao_blur_pipeline {
998 let mut ssao_blur_pass =
999 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1000 label: Some("ssao_blur_pass"),
1001 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1002 view: &slot_hdr.ssao_blur_view,
1003 resolve_target: None,
1004 ops: wgpu::Operations {
1005 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
1006 store: wgpu::StoreOp::Store,
1007 },
1008 depth_slice: None,
1009 })],
1010 depth_stencil_attachment: None,
1011 timestamp_writes: None,
1012 occlusion_query_set: None,
1013 });
1014 ssao_blur_pass.set_pipeline(ssao_blur_pipeline);
1015 ssao_blur_pass.set_bind_group(0, &slot_hdr.ssao_blur_bg, &[]);
1016 ssao_blur_pass.draw(0..3, 0..1);
1017 }
1018 }
1019 }
1020
1021 if pp.contact_shadows {
1025 if let Some(cs_pipeline) = &self.resources.contact_shadow_pipeline {
1026 let mut cs_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1027 label: Some("contact_shadow_pass"),
1028 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1029 view: &slot_hdr.contact_shadow_view,
1030 resolve_target: None,
1031 depth_slice: None,
1032 ops: wgpu::Operations {
1033 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
1034 store: wgpu::StoreOp::Store,
1035 },
1036 })],
1037 depth_stencil_attachment: None,
1038 timestamp_writes: None,
1039 occlusion_query_set: None,
1040 });
1041 cs_pass.set_pipeline(cs_pipeline);
1042 cs_pass.set_bind_group(0, &slot_hdr.contact_shadow_bg, &[]);
1043 cs_pass.draw(0..3, 0..1);
1044 }
1045 }
1046
1047 if pp.bloom {
1051 if let Some(bloom_threshold_pipeline) = &self.resources.bloom_threshold_pipeline {
1053 {
1054 let mut threshold_pass =
1055 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1056 label: Some("bloom_threshold_pass"),
1057 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1058 view: &slot_hdr.bloom_threshold_view,
1059 resolve_target: None,
1060 ops: wgpu::Operations {
1061 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1062 store: wgpu::StoreOp::Store,
1063 },
1064 depth_slice: None,
1065 })],
1066 depth_stencil_attachment: None,
1067 timestamp_writes: None,
1068 occlusion_query_set: None,
1069 });
1070 threshold_pass.set_pipeline(bloom_threshold_pipeline);
1071 threshold_pass.set_bind_group(0, &slot_hdr.bloom_threshold_bg, &[]);
1072 threshold_pass.draw(0..3, 0..1);
1073 }
1074
1075 if let Some(blur_pipeline) = &self.resources.bloom_blur_pipeline {
1078 let blur_h_bg = &slot_hdr.bloom_blur_h_bg;
1079 let blur_h_pong_bg = &slot_hdr.bloom_blur_h_pong_bg;
1080 let blur_v_bg = &slot_hdr.bloom_blur_v_bg;
1081 let bloom_ping_view = &slot_hdr.bloom_ping_view;
1082 let bloom_pong_view = &slot_hdr.bloom_pong_view;
1083 const BLUR_ITERATIONS: usize = 4;
1084 for i in 0..BLUR_ITERATIONS {
1085 let h_bg = if i == 0 { blur_h_bg } else { blur_h_pong_bg };
1087 {
1088 let mut h_pass =
1089 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1090 label: Some("bloom_blur_h_pass"),
1091 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1092 view: bloom_ping_view,
1093 resolve_target: None,
1094 ops: wgpu::Operations {
1095 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1096 store: wgpu::StoreOp::Store,
1097 },
1098 depth_slice: None,
1099 })],
1100 depth_stencil_attachment: None,
1101 timestamp_writes: None,
1102 occlusion_query_set: None,
1103 });
1104 h_pass.set_pipeline(blur_pipeline);
1105 h_pass.set_bind_group(0, h_bg, &[]);
1106 h_pass.draw(0..3, 0..1);
1107 }
1108 {
1110 let mut v_pass =
1111 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1112 label: Some("bloom_blur_v_pass"),
1113 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1114 view: bloom_pong_view,
1115 resolve_target: None,
1116 ops: wgpu::Operations {
1117 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1118 store: wgpu::StoreOp::Store,
1119 },
1120 depth_slice: None,
1121 })],
1122 depth_stencil_attachment: None,
1123 timestamp_writes: None,
1124 occlusion_query_set: None,
1125 });
1126 v_pass.set_pipeline(blur_pipeline);
1127 v_pass.set_bind_group(0, blur_v_bg, &[]);
1128 v_pass.draw(0..3, 0..1);
1129 }
1130 }
1131 }
1132 }
1133 }
1134
1135 let use_fxaa = pp.fxaa;
1139 if let Some(tone_map_pipeline) = &self.resources.tone_map_pipeline {
1140 let tone_target: &wgpu::TextureView = if use_fxaa {
1141 &slot_hdr.fxaa_view
1142 } else {
1143 output_view
1144 };
1145 let mut tone_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1146 label: Some("tone_map_pass"),
1147 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1148 view: tone_target,
1149 resolve_target: None,
1150 ops: wgpu::Operations {
1151 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1152 store: wgpu::StoreOp::Store,
1153 },
1154 depth_slice: None,
1155 })],
1156 depth_stencil_attachment: None,
1157 timestamp_writes: None,
1158 occlusion_query_set: None,
1159 });
1160 tone_pass.set_pipeline(tone_map_pipeline);
1161 tone_pass.set_bind_group(0, &slot_hdr.tone_map_bind_group, &[]);
1162 tone_pass.draw(0..3, 0..1);
1163 }
1164
1165 if use_fxaa {
1169 if let Some(fxaa_pipeline) = &self.resources.fxaa_pipeline {
1170 let mut fxaa_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1171 label: Some("fxaa_pass"),
1172 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1173 view: output_view,
1174 resolve_target: None,
1175 ops: wgpu::Operations {
1176 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1177 store: wgpu::StoreOp::Store,
1178 },
1179 depth_slice: None,
1180 })],
1181 depth_stencil_attachment: None,
1182 timestamp_writes: None,
1183 occlusion_query_set: None,
1184 });
1185 fxaa_pass.set_pipeline(fxaa_pipeline);
1186 fxaa_pass.set_bind_group(0, &slot_hdr.fxaa_bind_group, &[]);
1187 fxaa_pass.draw(0..3, 0..1);
1188 }
1189 }
1190
1191 if frame.viewport.show_grid {
1195 let slot = &self.viewport_slots[vp_idx];
1196 let slot_hdr = slot.hdr.as_ref().unwrap();
1197 let grid_bg = &slot.grid_bind_group;
1198 let mut grid_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1199 label: Some("hdr_grid_pass"),
1200 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1201 view: output_view,
1202 resolve_target: None,
1203 ops: wgpu::Operations {
1204 load: wgpu::LoadOp::Load,
1205 store: wgpu::StoreOp::Store,
1206 },
1207 depth_slice: None,
1208 })],
1209 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1210 view: &slot_hdr.hdr_depth_view,
1211 depth_ops: Some(wgpu::Operations {
1212 load: wgpu::LoadOp::Load,
1213 store: wgpu::StoreOp::Store,
1214 }),
1215 stencil_ops: None,
1216 }),
1217 timestamp_writes: None,
1218 occlusion_query_set: None,
1219 });
1220 grid_pass.set_pipeline(&self.resources.grid_pipeline);
1221 grid_pass.set_bind_group(0, grid_bg, &[]);
1222 grid_pass.draw(0..3, 0..1);
1223 }
1224
1225 if !matches!(
1228 frame.effects.ground_plane.mode,
1229 crate::renderer::types::GroundPlaneMode::None
1230 ) {
1231 let slot = &self.viewport_slots[vp_idx];
1232 let slot_hdr = slot.hdr.as_ref().unwrap();
1233 let mut gp_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1234 label: Some("hdr_ground_plane_pass"),
1235 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1236 view: output_view,
1237 resolve_target: None,
1238 ops: wgpu::Operations {
1239 load: wgpu::LoadOp::Load,
1240 store: wgpu::StoreOp::Store,
1241 },
1242 depth_slice: None,
1243 })],
1244 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1245 view: &slot_hdr.hdr_depth_view,
1246 depth_ops: Some(wgpu::Operations {
1247 load: wgpu::LoadOp::Load,
1248 store: wgpu::StoreOp::Store,
1249 }),
1250 stencil_ops: None,
1251 }),
1252 timestamp_writes: None,
1253 occlusion_query_set: None,
1254 });
1255 gp_pass.set_pipeline(&self.resources.ground_plane_pipeline);
1256 gp_pass.set_bind_group(0, &self.resources.ground_plane_bind_group, &[]);
1257 gp_pass.draw(0..3, 0..1);
1258 }
1259
1260 {
1264 let slot = &self.viewport_slots[vp_idx];
1265 let slot_hdr = slot.hdr.as_ref().unwrap();
1266 let has_editor_overlays = (frame.interaction.gizmo_model.is_some()
1267 && slot.gizmo_index_count > 0)
1268 || !slot.constraint_line_buffers.is_empty()
1269 || !slot.clip_plane_fill_buffers.is_empty()
1270 || !slot.clip_plane_line_buffers.is_empty()
1271 || !slot.xray_object_buffers.is_empty();
1272 if has_editor_overlays {
1273 let camera_bg = &slot.camera_bind_group;
1274 let mut overlay_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1275 label: Some("hdr_editor_overlay_pass"),
1276 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1277 view: output_view,
1278 resolve_target: None,
1279 ops: wgpu::Operations {
1280 load: wgpu::LoadOp::Load,
1281 store: wgpu::StoreOp::Store,
1282 },
1283 depth_slice: None,
1284 })],
1285 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1286 view: &slot_hdr.hdr_depth_view,
1287 depth_ops: Some(wgpu::Operations {
1288 load: wgpu::LoadOp::Load,
1289 store: wgpu::StoreOp::Discard,
1290 }),
1291 stencil_ops: None,
1292 }),
1293 timestamp_writes: None,
1294 occlusion_query_set: None,
1295 });
1296
1297 if frame.interaction.gizmo_model.is_some() && slot.gizmo_index_count > 0 {
1298 overlay_pass.set_pipeline(&self.resources.gizmo_pipeline);
1299 overlay_pass.set_bind_group(0, camera_bg, &[]);
1300 overlay_pass.set_bind_group(1, &slot.gizmo_bind_group, &[]);
1301 overlay_pass.set_vertex_buffer(0, slot.gizmo_vertex_buffer.slice(..));
1302 overlay_pass.set_index_buffer(
1303 slot.gizmo_index_buffer.slice(..),
1304 wgpu::IndexFormat::Uint32,
1305 );
1306 overlay_pass.draw_indexed(0..slot.gizmo_index_count, 0, 0..1);
1307 }
1308
1309 if !slot.constraint_line_buffers.is_empty() {
1310 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1311 overlay_pass.set_bind_group(0, camera_bg, &[]);
1312 for (vbuf, ibuf, index_count, _ubuf, bg) in &slot.constraint_line_buffers {
1313 overlay_pass.set_bind_group(1, bg, &[]);
1314 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1315 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1316 overlay_pass.draw_indexed(0..*index_count, 0, 0..1);
1317 }
1318 }
1319
1320 if !slot.clip_plane_fill_buffers.is_empty() {
1321 overlay_pass.set_pipeline(&self.resources.overlay_pipeline);
1322 overlay_pass.set_bind_group(0, camera_bg, &[]);
1323 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_fill_buffers {
1324 overlay_pass.set_bind_group(1, bg, &[]);
1325 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1326 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1327 overlay_pass.draw_indexed(0..*idx_count, 0, 0..1);
1328 }
1329 }
1330
1331 if !slot.clip_plane_line_buffers.is_empty() {
1332 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1333 overlay_pass.set_bind_group(0, camera_bg, &[]);
1334 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_line_buffers {
1335 overlay_pass.set_bind_group(1, bg, &[]);
1336 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1337 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1338 overlay_pass.draw_indexed(0..*idx_count, 0, 0..1);
1339 }
1340 }
1341
1342 if !slot.xray_object_buffers.is_empty() {
1343 overlay_pass.set_pipeline(&self.resources.xray_pipeline);
1344 overlay_pass.set_bind_group(0, camera_bg, &[]);
1345 for (mesh_id, _buf, bg) in &slot.xray_object_buffers {
1346 let Some(mesh) = self
1347 .resources
1348 .mesh_store
1349 .get(*mesh_id)
1350 else {
1351 continue;
1352 };
1353 overlay_pass.set_bind_group(1, bg, &[]);
1354 overlay_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
1355 overlay_pass.set_index_buffer(
1356 mesh.index_buffer.slice(..),
1357 wgpu::IndexFormat::Uint32,
1358 );
1359 overlay_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
1360 }
1361 }
1362 }
1363 }
1364
1365 if frame.viewport.show_axes_indicator {
1368 let slot = &self.viewport_slots[vp_idx];
1369 if slot.axes_vertex_count > 0 {
1370 let slot_hdr = slot.hdr.as_ref().unwrap();
1371 let mut axes_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1372 label: Some("hdr_axes_pass"),
1373 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1374 view: output_view,
1375 resolve_target: None,
1376 ops: wgpu::Operations {
1377 load: wgpu::LoadOp::Load,
1378 store: wgpu::StoreOp::Store,
1379 },
1380 depth_slice: None,
1381 })],
1382 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1383 view: &slot_hdr.hdr_depth_view,
1384 depth_ops: Some(wgpu::Operations {
1385 load: wgpu::LoadOp::Load,
1386 store: wgpu::StoreOp::Discard,
1387 }),
1388 stencil_ops: None,
1389 }),
1390 timestamp_writes: None,
1391 occlusion_query_set: None,
1392 });
1393 axes_pass.set_pipeline(&self.resources.axes_pipeline);
1394 axes_pass.set_vertex_buffer(0, slot.axes_vertex_buffer.slice(..));
1395 axes_pass.draw(0..slot.axes_vertex_count, 0..1);
1396 }
1397 }
1398
1399 if !self.screen_image_gpu_data.is_empty() {
1403 if let Some(overlay_pipeline) = &self.resources.screen_image_pipeline {
1404 let slot_hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
1405 let dc_pipeline = self.resources.screen_image_dc_pipeline.as_ref();
1406 let mut img_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1407 label: Some("screen_image_pass"),
1408 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1409 view: output_view,
1410 resolve_target: None,
1411 ops: wgpu::Operations {
1412 load: wgpu::LoadOp::Load,
1413 store: wgpu::StoreOp::Store,
1414 },
1415 depth_slice: None,
1416 })],
1417 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1418 view: &slot_hdr.hdr_depth_view,
1419 depth_ops: Some(wgpu::Operations {
1420 load: wgpu::LoadOp::Load,
1421 store: wgpu::StoreOp::Discard,
1422 }),
1423 stencil_ops: None,
1424 }),
1425 timestamp_writes: None,
1426 occlusion_query_set: None,
1427 });
1428 for gpu in &self.screen_image_gpu_data {
1429 if let (Some(dc_bg), Some(dc_pipe)) = (&gpu.depth_bind_group, dc_pipeline) {
1430 img_pass.set_pipeline(dc_pipe);
1431 img_pass.set_bind_group(0, dc_bg, &[]);
1432 } else {
1433 img_pass.set_pipeline(overlay_pipeline);
1434 img_pass.set_bind_group(0, &gpu.bind_group, &[]);
1435 }
1436 img_pass.draw(0..6, 0..1);
1437 }
1438 }
1439 }
1440
1441 encoder.finish()
1442 }
1443
1444 pub fn render_offscreen(
1458 &mut self,
1459 device: &wgpu::Device,
1460 queue: &wgpu::Queue,
1461 frame: &FrameData,
1462 width: u32,
1463 height: u32,
1464 ) -> Vec<u8> {
1465 let target_format = self.resources.target_format;
1467 let offscreen_texture = device.create_texture(&wgpu::TextureDescriptor {
1468 label: Some("offscreen_target"),
1469 size: wgpu::Extent3d {
1470 width: width.max(1),
1471 height: height.max(1),
1472 depth_or_array_layers: 1,
1473 },
1474 mip_level_count: 1,
1475 sample_count: 1,
1476 dimension: wgpu::TextureDimension::D2,
1477 format: target_format,
1478 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
1479 view_formats: &[],
1480 });
1481
1482 let output_view = offscreen_texture.create_view(&wgpu::TextureViewDescriptor::default());
1484
1485 let cmd_buf = self.render(device, queue, &output_view, frame);
1493 queue.submit(std::iter::once(cmd_buf));
1494
1495 let bytes_per_pixel = 4u32;
1497 let unpadded_row = width * bytes_per_pixel;
1498 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
1499 let padded_row = (unpadded_row + align - 1) & !(align - 1);
1500 let buffer_size = (padded_row * height.max(1)) as u64;
1501
1502 let staging_buf = device.create_buffer(&wgpu::BufferDescriptor {
1503 label: Some("offscreen_staging"),
1504 size: buffer_size,
1505 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1506 mapped_at_creation: false,
1507 });
1508
1509 let mut copy_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1510 label: Some("offscreen_copy_encoder"),
1511 });
1512 copy_encoder.copy_texture_to_buffer(
1513 wgpu::TexelCopyTextureInfo {
1514 texture: &offscreen_texture,
1515 mip_level: 0,
1516 origin: wgpu::Origin3d::ZERO,
1517 aspect: wgpu::TextureAspect::All,
1518 },
1519 wgpu::TexelCopyBufferInfo {
1520 buffer: &staging_buf,
1521 layout: wgpu::TexelCopyBufferLayout {
1522 offset: 0,
1523 bytes_per_row: Some(padded_row),
1524 rows_per_image: Some(height.max(1)),
1525 },
1526 },
1527 wgpu::Extent3d {
1528 width: width.max(1),
1529 height: height.max(1),
1530 depth_or_array_layers: 1,
1531 },
1532 );
1533 queue.submit(std::iter::once(copy_encoder.finish()));
1534
1535 let (tx, rx) = std::sync::mpsc::channel();
1537 staging_buf
1538 .slice(..)
1539 .map_async(wgpu::MapMode::Read, move |result| {
1540 let _ = tx.send(result);
1541 });
1542 device
1543 .poll(wgpu::PollType::Wait {
1544 submission_index: None,
1545 timeout: Some(std::time::Duration::from_secs(5)),
1546 })
1547 .unwrap();
1548 let _ = rx.recv().unwrap_or(Err(wgpu::BufferAsyncError));
1549
1550 let mut pixels: Vec<u8> = Vec::with_capacity((width * height * 4) as usize);
1551 {
1552 let mapped = staging_buf.slice(..).get_mapped_range();
1553 let data: &[u8] = &mapped;
1554 if padded_row == unpadded_row {
1555 pixels.extend_from_slice(data);
1557 } else {
1558 for row in 0..height as usize {
1560 let start = row * padded_row as usize;
1561 let end = start + unpadded_row as usize;
1562 pixels.extend_from_slice(&data[start..end]);
1563 }
1564 }
1565 }
1566 staging_buf.unmap();
1567
1568 let is_bgra = matches!(
1570 target_format,
1571 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
1572 );
1573 if is_bgra {
1574 for pixel in pixels.chunks_exact_mut(4) {
1575 pixel.swap(0, 2); }
1577 }
1578
1579 pixels
1580 }
1581}