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() {
229 if let Some(pipeline) = &self.resources.screen_image_pipeline {
230 render_pass.set_pipeline(pipeline);
231 for gpu in &self.screen_image_gpu_data {
232 render_pass.set_bind_group(0, &gpu.bind_group, &[]);
233 render_pass.draw(0..6, 0..1);
234 }
235 }
236 }
237 }
238 return encoder.finish();
239 }
240
241 let pp = &frame.effects.post_process;
243
244 let hdr_clear_rgb = [
245 bg_color[0].powf(2.2),
246 bg_color[1].powf(2.2),
247 bg_color[2].powf(2.2),
248 ];
249
250 let mode = match pp.tone_mapping {
252 crate::renderer::ToneMapping::Reinhard => 0u32,
253 crate::renderer::ToneMapping::Aces => 1u32,
254 crate::renderer::ToneMapping::KhronosNeutral => 2u32,
255 };
256 let tm_uniform = crate::resources::ToneMapUniform {
257 exposure: pp.exposure,
258 mode,
259 bloom_enabled: if pp.bloom { 1 } else { 0 },
260 ssao_enabled: if pp.ssao { 1 } else { 0 },
261 contact_shadows_enabled: if pp.contact_shadows { 1 } else { 0 },
262 _pad_tm: [0; 3],
263 background_color: bg_color,
264 };
265 {
266 let hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
267 queue.write_buffer(
268 &hdr.tone_map_uniform_buf,
269 0,
270 bytemuck::cast_slice(&[tm_uniform]),
271 );
272
273 if pp.ssao {
275 let proj = frame.camera.render_camera.projection;
276 let inv_proj = proj.inverse();
277 let ssao_uniform = crate::resources::SsaoUniform {
278 inv_proj: inv_proj.to_cols_array_2d(),
279 proj: proj.to_cols_array_2d(),
280 radius: 0.5,
281 bias: 0.025,
282 _pad: [0.0; 2],
283 };
284 queue.write_buffer(
285 &hdr.ssao_uniform_buf,
286 0,
287 bytemuck::cast_slice(&[ssao_uniform]),
288 );
289 }
290
291 if pp.contact_shadows {
293 let proj = frame.camera.render_camera.projection;
294 let inv_proj = proj.inverse();
295 let light_dir_world: glam::Vec3 =
296 if let Some(l) = frame.effects.lighting.lights.first() {
297 match l.kind {
298 LightKind::Directional { direction } => {
299 glam::Vec3::from(direction).normalize()
300 }
301 LightKind::Spot { direction, .. } => {
302 glam::Vec3::from(direction).normalize()
303 }
304 _ => glam::Vec3::new(0.0, -1.0, 0.0),
305 }
306 } else {
307 glam::Vec3::new(0.0, -1.0, 0.0)
308 };
309 let view = frame.camera.render_camera.view;
310 let light_dir_view = view.transform_vector3(light_dir_world).normalize();
311 let world_up_view = view.transform_vector3(glam::Vec3::Z).normalize();
312 let cs_uniform = crate::resources::ContactShadowUniform {
313 inv_proj: inv_proj.to_cols_array_2d(),
314 proj: proj.to_cols_array_2d(),
315 light_dir_view: [light_dir_view.x, light_dir_view.y, light_dir_view.z, 0.0],
316 world_up_view: [world_up_view.x, world_up_view.y, world_up_view.z, 0.0],
317 params: [
318 pp.contact_shadow_max_distance,
319 pp.contact_shadow_steps as f32,
320 pp.contact_shadow_thickness,
321 0.0,
322 ],
323 };
324 queue.write_buffer(
325 &hdr.contact_shadow_uniform_buf,
326 0,
327 bytemuck::cast_slice(&[cs_uniform]),
328 );
329 }
330
331 if pp.bloom {
333 let bloom_u = crate::resources::BloomUniform {
334 threshold: pp.bloom_threshold,
335 intensity: pp.bloom_intensity,
336 horizontal: 0,
337 _pad: 0,
338 };
339 queue.write_buffer(&hdr.bloom_uniform_buf, 0, bytemuck::cast_slice(&[bloom_u]));
340 }
341 }
342
343 {
345 let hdr = self.viewport_slots[vp_idx].hdr.as_mut().unwrap();
346 self.resources.rebuild_tone_map_bind_group(
347 device,
348 hdr,
349 pp.bloom,
350 pp.ssao,
351 pp.contact_shadows,
352 );
353 }
354
355 {
360 let needs_oit = if self.use_instancing && !self.instanced_batches.is_empty() {
361 self.instanced_batches.iter().any(|b| b.is_transparent)
362 } else {
363 scene_items
364 .iter()
365 .any(|i| i.visible && i.material.opacity < 1.0)
366 };
367 if needs_oit {
368 let hdr = self.viewport_slots[vp_idx].hdr.as_mut().unwrap();
369 self.resources
370 .ensure_viewport_oit(device, hdr, w.max(1), h.max(1));
371 }
372 }
373
374 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
378 label: Some("hdr_encoder"),
379 });
380
381 let slot = &self.viewport_slots[vp_idx];
383 let camera_bg = &slot.camera_bind_group;
384 let slot_hdr = slot.hdr.as_ref().unwrap();
385
386 {
390 let use_ssaa = ssaa_factor > 1
392 && slot_hdr.ssaa_color_view.is_some()
393 && slot_hdr.ssaa_depth_view.is_some();
394 let scene_color_view = if use_ssaa {
395 slot_hdr.ssaa_color_view.as_ref().unwrap()
396 } else {
397 &slot_hdr.hdr_view
398 };
399 let scene_depth_view = if use_ssaa {
400 slot_hdr.ssaa_depth_view.as_ref().unwrap()
401 } else {
402 &slot_hdr.hdr_depth_view
403 };
404
405 let clear_wgpu = wgpu::Color {
406 r: hdr_clear_rgb[0] as f64,
407 g: hdr_clear_rgb[1] as f64,
408 b: hdr_clear_rgb[2] as f64,
409 a: bg_color[3] as f64,
410 };
411
412 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
413 label: Some("hdr_scene_pass"),
414 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
415 view: scene_color_view,
416 resolve_target: None,
417 ops: wgpu::Operations {
418 load: wgpu::LoadOp::Clear(clear_wgpu),
419 store: wgpu::StoreOp::Store,
420 },
421 depth_slice: None,
422 })],
423 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
424 view: scene_depth_view,
425 depth_ops: Some(wgpu::Operations {
426 load: wgpu::LoadOp::Clear(1.0),
427 store: wgpu::StoreOp::Store,
428 }),
429 stencil_ops: Some(wgpu::Operations {
430 load: wgpu::LoadOp::Clear(0),
431 store: wgpu::StoreOp::Store,
432 }),
433 }),
434 timestamp_writes: None,
435 occlusion_query_set: None,
436 });
437
438 let resources = &self.resources;
439 render_pass.set_bind_group(0, camera_bg, &[]);
440
441 let show_skybox = frame
443 .effects
444 .environment
445 .as_ref()
446 .is_some_and(|e| e.show_skybox)
447 && resources.ibl_skybox_view.is_some();
448
449 let use_instancing = self.use_instancing;
450 let batches = &self.instanced_batches;
451
452 if !scene_items.is_empty() {
453 if use_instancing && !batches.is_empty() {
454 let excluded_items: Vec<&SceneRenderItem> = scene_items
455 .iter()
456 .filter(|item| {
457 item.visible
458 && (item.active_attribute.is_some()
459 || item.two_sided
460 || item.material.is_two_sided()
461 || item.material.matcap_id.is_some())
462 && resources
463 .mesh_store
464 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
465 .is_some()
466 })
467 .collect();
468
469 let mut opaque_batches: Vec<&InstancedBatch> = Vec::new();
471 let mut transparent_batches: Vec<&InstancedBatch> = Vec::new();
472 for batch in batches {
473 if batch.is_transparent {
474 transparent_batches.push(batch);
475 } else {
476 opaque_batches.push(batch);
477 }
478 }
479
480 if !opaque_batches.is_empty() && !frame.viewport.wireframe_mode {
481 if let Some(ref pipeline) = resources.hdr_solid_instanced_pipeline {
482 render_pass.set_pipeline(pipeline);
483 for batch in &opaque_batches {
484 let Some(mesh) = resources
485 .mesh_store
486 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
487 else {
488 continue;
489 };
490 let mat_key = (
491 batch.texture_id.unwrap_or(u64::MAX),
492 batch.normal_map_id.unwrap_or(u64::MAX),
493 batch.ao_map_id.unwrap_or(u64::MAX),
494 );
495 let Some(inst_tex_bg) =
496 resources.instance_bind_groups.get(&mat_key)
497 else {
498 continue;
499 };
500 render_pass.set_bind_group(1, inst_tex_bg, &[]);
501 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
502 render_pass.set_index_buffer(
503 mesh.index_buffer.slice(..),
504 wgpu::IndexFormat::Uint32,
505 );
506 render_pass.draw_indexed(
507 0..mesh.index_count,
508 0,
509 batch.instance_offset
510 ..batch.instance_offset + batch.instance_count,
511 );
512 }
513 }
514 }
515
516 let _ = &transparent_batches; if frame.viewport.wireframe_mode {
521 if let Some(ref hdr_wf) = resources.hdr_wireframe_pipeline {
522 render_pass.set_pipeline(hdr_wf);
523 for item in scene_items {
524 if !item.visible {
525 continue;
526 }
527 let Some(mesh) = resources
528 .mesh_store
529 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
530 else {
531 continue;
532 };
533 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
534 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
535 render_pass.set_index_buffer(
536 mesh.edge_index_buffer.slice(..),
537 wgpu::IndexFormat::Uint32,
538 );
539 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
540 }
541 }
542 } else if let (Some(hdr_solid), Some(hdr_solid_two_sided)) = (
543 &resources.hdr_solid_pipeline,
544 &resources.hdr_solid_two_sided_pipeline,
545 ) {
546 for item in excluded_items
547 .into_iter()
548 .filter(|item| item.material.opacity >= 1.0)
549 {
550 let Some(mesh) = resources
551 .mesh_store
552 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
553 else {
554 continue;
555 };
556 let pipeline = if item.two_sided || item.material.is_two_sided() {
557 hdr_solid_two_sided
558 } else {
559 hdr_solid
560 };
561 render_pass.set_pipeline(pipeline);
562 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
563 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
564 render_pass.set_index_buffer(
565 mesh.index_buffer.slice(..),
566 wgpu::IndexFormat::Uint32,
567 );
568 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
569 }
570 }
571 } else {
572 let eye = glam::Vec3::from(frame.camera.render_camera.eye_position);
574 let dist_from_eye = |item: &&SceneRenderItem| -> f32 {
575 let pos =
576 glam::Vec3::new(item.model[3][0], item.model[3][1], item.model[3][2]);
577 (pos - eye).length()
578 };
579
580 let mut opaque: Vec<&SceneRenderItem> = Vec::new();
581 let mut transparent: Vec<&SceneRenderItem> = Vec::new();
582 for item in scene_items {
583 if !item.visible
584 || resources
585 .mesh_store
586 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
587 .is_none()
588 {
589 continue;
590 }
591 if item.material.opacity < 1.0 {
592 transparent.push(item);
593 } else {
594 opaque.push(item);
595 }
596 }
597 opaque.sort_by(|a, b| {
598 dist_from_eye(a)
599 .partial_cmp(&dist_from_eye(b))
600 .unwrap_or(std::cmp::Ordering::Equal)
601 });
602 transparent.sort_by(|a, b| {
603 dist_from_eye(b)
604 .partial_cmp(&dist_from_eye(a))
605 .unwrap_or(std::cmp::Ordering::Equal)
606 });
607
608 let draw_item_hdr =
609 |render_pass: &mut wgpu::RenderPass<'_>,
610 item: &SceneRenderItem,
611 solid_pl: &wgpu::RenderPipeline,
612 trans_pl: &wgpu::RenderPipeline,
613 wf_pl: &wgpu::RenderPipeline| {
614 let mesh = resources
615 .mesh_store
616 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
617 .unwrap();
618 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
621 let is_face_attr = item.active_attribute.as_ref().map_or(false, |a| {
622 matches!(
623 a.kind,
624 crate::resources::AttributeKind::Face
625 | crate::resources::AttributeKind::FaceColor
626 )
627 });
628 if frame.viewport.wireframe_mode {
629 render_pass.set_pipeline(wf_pl);
630 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
631 render_pass.set_index_buffer(
632 mesh.edge_index_buffer.slice(..),
633 wgpu::IndexFormat::Uint32,
634 );
635 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
636 } else if is_face_attr {
637 if let Some(ref fvb) = mesh.face_vertex_buffer {
638 let pl = if item.material.opacity < 1.0 {
639 trans_pl
640 } else {
641 solid_pl
642 };
643 render_pass.set_pipeline(pl);
644 render_pass.set_vertex_buffer(0, fvb.slice(..));
645 render_pass.draw(0..mesh.index_count, 0..1);
646 }
647 } else if item.material.opacity < 1.0 {
648 render_pass.set_pipeline(trans_pl);
649 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
650 render_pass.set_index_buffer(
651 mesh.index_buffer.slice(..),
652 wgpu::IndexFormat::Uint32,
653 );
654 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
655 } else {
656 render_pass.set_pipeline(solid_pl);
657 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
658 render_pass.set_index_buffer(
659 mesh.index_buffer.slice(..),
660 wgpu::IndexFormat::Uint32,
661 );
662 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
663 }
664 };
665
666 let _ = &transparent; if let (
670 Some(hdr_solid),
671 Some(hdr_solid_two_sided),
672 Some(hdr_trans),
673 Some(hdr_wf),
674 ) = (
675 &resources.hdr_solid_pipeline,
676 &resources.hdr_solid_two_sided_pipeline,
677 &resources.hdr_transparent_pipeline,
678 &resources.hdr_wireframe_pipeline,
679 ) {
680 for item in &opaque {
681 let solid_pl = if item.two_sided || item.material.is_two_sided() {
682 hdr_solid_two_sided
683 } else {
684 hdr_solid
685 };
686 draw_item_hdr(&mut render_pass, item, solid_pl, hdr_trans, hdr_wf);
687 }
688 }
689 }
690 }
691
692 if !slot.cap_buffers.is_empty() {
694 if let Some(ref hdr_overlay) = resources.hdr_overlay_pipeline {
695 render_pass.set_pipeline(hdr_overlay);
696 render_pass.set_bind_group(0, camera_bg, &[]);
697 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.cap_buffers {
698 render_pass.set_bind_group(1, bg, &[]);
699 render_pass.set_vertex_buffer(0, vbuf.slice(..));
700 render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
701 render_pass.draw_indexed(0..*idx_count, 0, 0..1);
702 }
703 }
704 }
705
706 emit_scivis_draw_calls!(
708 &self.resources,
709 &mut render_pass,
710 &self.point_cloud_gpu_data,
711 &self.glyph_gpu_data,
712 &self.polyline_gpu_data,
713 &self.volume_gpu_data,
714 &self.streamtube_gpu_data,
715 camera_bg
716 );
717
718 if show_skybox {
720 render_pass.set_bind_group(0, camera_bg, &[]);
721 render_pass.set_pipeline(&resources.skybox_pipeline);
722 render_pass.draw(0..3, 0..1);
723 }
724 }
725
726 if ssaa_factor > 1 {
731 let slot_hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
732 if let (Some(pipeline), Some(bg)) = (
733 &self.resources.ssaa_resolve_pipeline,
734 &slot_hdr.ssaa_resolve_bind_group,
735 ) {
736 let mut resolve_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
737 label: Some("ssaa_resolve_pass"),
738 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
739 view: &slot_hdr.hdr_view,
740 resolve_target: None,
741 ops: wgpu::Operations {
742 load: wgpu::LoadOp::Load,
743 store: wgpu::StoreOp::Store,
744 },
745 depth_slice: None,
746 })],
747 depth_stencil_attachment: None,
748 timestamp_writes: None,
749 occlusion_query_set: None,
750 });
751 resolve_pass.set_pipeline(pipeline);
752 resolve_pass.set_bind_group(0, bg, &[]);
753 resolve_pass.draw(0..3, 0..1);
754 }
755 }
756
757 let has_transparent = if self.use_instancing && !self.instanced_batches.is_empty() {
762 self.instanced_batches.iter().any(|b| b.is_transparent)
763 } else {
764 scene_items
765 .iter()
766 .any(|i| i.visible && i.material.opacity < 1.0)
767 };
768
769 if has_transparent {
770 if let (Some(accum_view), Some(reveal_view)) = (
772 slot_hdr.oit_accum_view.as_ref(),
773 slot_hdr.oit_reveal_view.as_ref(),
774 ) {
775 let hdr_depth_view = &slot_hdr.hdr_depth_view;
776 let mut oit_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
778 label: Some("oit_pass"),
779 color_attachments: &[
780 Some(wgpu::RenderPassColorAttachment {
781 view: accum_view,
782 resolve_target: None,
783 ops: wgpu::Operations {
784 load: wgpu::LoadOp::Clear(wgpu::Color {
785 r: 0.0,
786 g: 0.0,
787 b: 0.0,
788 a: 0.0,
789 }),
790 store: wgpu::StoreOp::Store,
791 },
792 depth_slice: None,
793 }),
794 Some(wgpu::RenderPassColorAttachment {
795 view: reveal_view,
796 resolve_target: None,
797 ops: wgpu::Operations {
798 load: wgpu::LoadOp::Clear(wgpu::Color {
799 r: 1.0,
800 g: 1.0,
801 b: 1.0,
802 a: 1.0,
803 }),
804 store: wgpu::StoreOp::Store,
805 },
806 depth_slice: None,
807 }),
808 ],
809 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
810 view: hdr_depth_view,
811 depth_ops: Some(wgpu::Operations {
812 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
814 }),
815 stencil_ops: None,
816 }),
817 timestamp_writes: None,
818 occlusion_query_set: None,
819 });
820
821 oit_pass.set_bind_group(0, camera_bg, &[]);
822
823 if self.use_instancing && !self.instanced_batches.is_empty() {
824 if let Some(ref pipeline) = self.resources.oit_instanced_pipeline {
825 oit_pass.set_pipeline(pipeline);
826 for batch in &self.instanced_batches {
827 if !batch.is_transparent {
828 continue;
829 }
830 let Some(mesh) = self
831 .resources
832 .mesh_store
833 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
834 else {
835 continue;
836 };
837 let mat_key = (
838 batch.texture_id.unwrap_or(u64::MAX),
839 batch.normal_map_id.unwrap_or(u64::MAX),
840 batch.ao_map_id.unwrap_or(u64::MAX),
841 );
842 let Some(inst_tex_bg) =
843 self.resources.instance_bind_groups.get(&mat_key)
844 else {
845 continue;
846 };
847 oit_pass.set_bind_group(1, inst_tex_bg, &[]);
848 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
849 oit_pass.set_index_buffer(
850 mesh.index_buffer.slice(..),
851 wgpu::IndexFormat::Uint32,
852 );
853 oit_pass.draw_indexed(
854 0..mesh.index_count,
855 0,
856 batch.instance_offset..batch.instance_offset + batch.instance_count,
857 );
858 }
859 }
860 } else if let Some(ref pipeline) = self.resources.oit_pipeline {
861 oit_pass.set_pipeline(pipeline);
862 for item in scene_items {
863 if !item.visible || item.material.opacity >= 1.0 {
864 continue;
865 }
866 let Some(mesh) = self
867 .resources
868 .mesh_store
869 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
870 else {
871 continue;
872 };
873 oit_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
874 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
875 oit_pass.set_index_buffer(
876 mesh.index_buffer.slice(..),
877 wgpu::IndexFormat::Uint32,
878 );
879 oit_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
880 }
881 }
882 }
883 }
884
885 if has_transparent {
890 if let (Some(pipeline), Some(bg)) = (
891 self.resources.oit_composite_pipeline.as_ref(),
892 slot_hdr.oit_composite_bind_group.as_ref(),
893 ) {
894 let hdr_view = &slot_hdr.hdr_view;
895 let mut composite_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
896 label: Some("oit_composite_pass"),
897 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
898 view: hdr_view,
899 resolve_target: None,
900 ops: wgpu::Operations {
901 load: wgpu::LoadOp::Load,
902 store: wgpu::StoreOp::Store,
903 },
904 depth_slice: None,
905 })],
906 depth_stencil_attachment: None,
907 timestamp_writes: None,
908 occlusion_query_set: None,
909 });
910 composite_pass.set_pipeline(pipeline);
911 composite_pass.set_bind_group(0, bg, &[]);
912 composite_pass.draw(0..3, 0..1);
913 }
914 }
915
916 if !slot.outline_object_buffers.is_empty() {
922 let hdr_pipeline = self
924 .resources
925 .outline_composite_pipeline_hdr
926 .as_ref()
927 .or(self.resources.outline_composite_pipeline_single.as_ref());
928 if let Some(pipeline) = hdr_pipeline {
929 let bg = &slot_hdr.outline_composite_bind_group;
930 let hdr_view = &slot_hdr.hdr_view;
931 let hdr_depth_view = &slot_hdr.hdr_depth_view;
932 let mut outline_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
933 label: Some("hdr_outline_composite_pass"),
934 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
935 view: hdr_view,
936 resolve_target: None,
937 ops: wgpu::Operations {
938 load: wgpu::LoadOp::Load,
939 store: wgpu::StoreOp::Store,
940 },
941 depth_slice: None,
942 })],
943 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
944 view: hdr_depth_view,
945 depth_ops: Some(wgpu::Operations {
946 load: wgpu::LoadOp::Load,
947 store: wgpu::StoreOp::Store,
948 }),
949 stencil_ops: None,
950 }),
951 timestamp_writes: None,
952 occlusion_query_set: None,
953 });
954 outline_pass.set_pipeline(pipeline);
955 outline_pass.set_bind_group(0, bg, &[]);
956 outline_pass.draw(0..3, 0..1);
957 }
958 }
959
960 if pp.ssao {
964 if let Some(ssao_pipeline) = &self.resources.ssao_pipeline {
965 {
966 let mut ssao_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
967 label: Some("ssao_pass"),
968 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
969 view: &slot_hdr.ssao_view,
970 resolve_target: None,
971 ops: wgpu::Operations {
972 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
973 store: wgpu::StoreOp::Store,
974 },
975 depth_slice: None,
976 })],
977 depth_stencil_attachment: None,
978 timestamp_writes: None,
979 occlusion_query_set: None,
980 });
981 ssao_pass.set_pipeline(ssao_pipeline);
982 ssao_pass.set_bind_group(0, &slot_hdr.ssao_bg, &[]);
983 ssao_pass.draw(0..3, 0..1);
984 }
985
986 if let Some(ssao_blur_pipeline) = &self.resources.ssao_blur_pipeline {
988 let mut ssao_blur_pass =
989 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
990 label: Some("ssao_blur_pass"),
991 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
992 view: &slot_hdr.ssao_blur_view,
993 resolve_target: None,
994 ops: wgpu::Operations {
995 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
996 store: wgpu::StoreOp::Store,
997 },
998 depth_slice: None,
999 })],
1000 depth_stencil_attachment: None,
1001 timestamp_writes: None,
1002 occlusion_query_set: None,
1003 });
1004 ssao_blur_pass.set_pipeline(ssao_blur_pipeline);
1005 ssao_blur_pass.set_bind_group(0, &slot_hdr.ssao_blur_bg, &[]);
1006 ssao_blur_pass.draw(0..3, 0..1);
1007 }
1008 }
1009 }
1010
1011 if pp.contact_shadows {
1015 if let Some(cs_pipeline) = &self.resources.contact_shadow_pipeline {
1016 let mut cs_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1017 label: Some("contact_shadow_pass"),
1018 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1019 view: &slot_hdr.contact_shadow_view,
1020 resolve_target: None,
1021 depth_slice: None,
1022 ops: wgpu::Operations {
1023 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
1024 store: wgpu::StoreOp::Store,
1025 },
1026 })],
1027 depth_stencil_attachment: None,
1028 timestamp_writes: None,
1029 occlusion_query_set: None,
1030 });
1031 cs_pass.set_pipeline(cs_pipeline);
1032 cs_pass.set_bind_group(0, &slot_hdr.contact_shadow_bg, &[]);
1033 cs_pass.draw(0..3, 0..1);
1034 }
1035 }
1036
1037 if pp.bloom {
1041 if let Some(bloom_threshold_pipeline) = &self.resources.bloom_threshold_pipeline {
1043 {
1044 let mut threshold_pass =
1045 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1046 label: Some("bloom_threshold_pass"),
1047 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1048 view: &slot_hdr.bloom_threshold_view,
1049 resolve_target: None,
1050 ops: wgpu::Operations {
1051 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1052 store: wgpu::StoreOp::Store,
1053 },
1054 depth_slice: None,
1055 })],
1056 depth_stencil_attachment: None,
1057 timestamp_writes: None,
1058 occlusion_query_set: None,
1059 });
1060 threshold_pass.set_pipeline(bloom_threshold_pipeline);
1061 threshold_pass.set_bind_group(0, &slot_hdr.bloom_threshold_bg, &[]);
1062 threshold_pass.draw(0..3, 0..1);
1063 }
1064
1065 if let Some(blur_pipeline) = &self.resources.bloom_blur_pipeline {
1068 let blur_h_bg = &slot_hdr.bloom_blur_h_bg;
1069 let blur_h_pong_bg = &slot_hdr.bloom_blur_h_pong_bg;
1070 let blur_v_bg = &slot_hdr.bloom_blur_v_bg;
1071 let bloom_ping_view = &slot_hdr.bloom_ping_view;
1072 let bloom_pong_view = &slot_hdr.bloom_pong_view;
1073 const BLUR_ITERATIONS: usize = 4;
1074 for i in 0..BLUR_ITERATIONS {
1075 let h_bg = if i == 0 { blur_h_bg } else { blur_h_pong_bg };
1077 {
1078 let mut h_pass =
1079 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1080 label: Some("bloom_blur_h_pass"),
1081 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1082 view: bloom_ping_view,
1083 resolve_target: None,
1084 ops: wgpu::Operations {
1085 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1086 store: wgpu::StoreOp::Store,
1087 },
1088 depth_slice: None,
1089 })],
1090 depth_stencil_attachment: None,
1091 timestamp_writes: None,
1092 occlusion_query_set: None,
1093 });
1094 h_pass.set_pipeline(blur_pipeline);
1095 h_pass.set_bind_group(0, h_bg, &[]);
1096 h_pass.draw(0..3, 0..1);
1097 }
1098 {
1100 let mut v_pass =
1101 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1102 label: Some("bloom_blur_v_pass"),
1103 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1104 view: bloom_pong_view,
1105 resolve_target: None,
1106 ops: wgpu::Operations {
1107 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1108 store: wgpu::StoreOp::Store,
1109 },
1110 depth_slice: None,
1111 })],
1112 depth_stencil_attachment: None,
1113 timestamp_writes: None,
1114 occlusion_query_set: None,
1115 });
1116 v_pass.set_pipeline(blur_pipeline);
1117 v_pass.set_bind_group(0, blur_v_bg, &[]);
1118 v_pass.draw(0..3, 0..1);
1119 }
1120 }
1121 }
1122 }
1123 }
1124
1125 let use_fxaa = pp.fxaa;
1129 if let Some(tone_map_pipeline) = &self.resources.tone_map_pipeline {
1130 let tone_target: &wgpu::TextureView = if use_fxaa {
1131 &slot_hdr.fxaa_view
1132 } else {
1133 output_view
1134 };
1135 let mut tone_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1136 label: Some("tone_map_pass"),
1137 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1138 view: tone_target,
1139 resolve_target: None,
1140 ops: wgpu::Operations {
1141 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1142 store: wgpu::StoreOp::Store,
1143 },
1144 depth_slice: None,
1145 })],
1146 depth_stencil_attachment: None,
1147 timestamp_writes: None,
1148 occlusion_query_set: None,
1149 });
1150 tone_pass.set_pipeline(tone_map_pipeline);
1151 tone_pass.set_bind_group(0, &slot_hdr.tone_map_bind_group, &[]);
1152 tone_pass.draw(0..3, 0..1);
1153 }
1154
1155 if use_fxaa {
1159 if let Some(fxaa_pipeline) = &self.resources.fxaa_pipeline {
1160 let mut fxaa_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1161 label: Some("fxaa_pass"),
1162 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1163 view: output_view,
1164 resolve_target: None,
1165 ops: wgpu::Operations {
1166 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1167 store: wgpu::StoreOp::Store,
1168 },
1169 depth_slice: None,
1170 })],
1171 depth_stencil_attachment: None,
1172 timestamp_writes: None,
1173 occlusion_query_set: None,
1174 });
1175 fxaa_pass.set_pipeline(fxaa_pipeline);
1176 fxaa_pass.set_bind_group(0, &slot_hdr.fxaa_bind_group, &[]);
1177 fxaa_pass.draw(0..3, 0..1);
1178 }
1179 }
1180
1181 if frame.viewport.show_grid {
1185 let slot = &self.viewport_slots[vp_idx];
1186 let slot_hdr = slot.hdr.as_ref().unwrap();
1187 let grid_bg = &slot.grid_bind_group;
1188 let mut grid_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1189 label: Some("hdr_grid_pass"),
1190 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1191 view: output_view,
1192 resolve_target: None,
1193 ops: wgpu::Operations {
1194 load: wgpu::LoadOp::Load,
1195 store: wgpu::StoreOp::Store,
1196 },
1197 depth_slice: None,
1198 })],
1199 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1200 view: &slot_hdr.hdr_depth_view,
1201 depth_ops: Some(wgpu::Operations {
1202 load: wgpu::LoadOp::Load,
1203 store: wgpu::StoreOp::Store,
1204 }),
1205 stencil_ops: None,
1206 }),
1207 timestamp_writes: None,
1208 occlusion_query_set: None,
1209 });
1210 grid_pass.set_pipeline(&self.resources.grid_pipeline);
1211 grid_pass.set_bind_group(0, grid_bg, &[]);
1212 grid_pass.draw(0..3, 0..1);
1213 }
1214
1215 if !matches!(
1218 frame.effects.ground_plane.mode,
1219 crate::renderer::types::GroundPlaneMode::None
1220 ) {
1221 let slot = &self.viewport_slots[vp_idx];
1222 let slot_hdr = slot.hdr.as_ref().unwrap();
1223 let mut gp_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1224 label: Some("hdr_ground_plane_pass"),
1225 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1226 view: output_view,
1227 resolve_target: None,
1228 ops: wgpu::Operations {
1229 load: wgpu::LoadOp::Load,
1230 store: wgpu::StoreOp::Store,
1231 },
1232 depth_slice: None,
1233 })],
1234 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1235 view: &slot_hdr.hdr_depth_view,
1236 depth_ops: Some(wgpu::Operations {
1237 load: wgpu::LoadOp::Load,
1238 store: wgpu::StoreOp::Store,
1239 }),
1240 stencil_ops: None,
1241 }),
1242 timestamp_writes: None,
1243 occlusion_query_set: None,
1244 });
1245 gp_pass.set_pipeline(&self.resources.ground_plane_pipeline);
1246 gp_pass.set_bind_group(0, &self.resources.ground_plane_bind_group, &[]);
1247 gp_pass.draw(0..3, 0..1);
1248 }
1249
1250 {
1254 let slot = &self.viewport_slots[vp_idx];
1255 let slot_hdr = slot.hdr.as_ref().unwrap();
1256 let has_editor_overlays = (frame.interaction.gizmo_model.is_some()
1257 && slot.gizmo_index_count > 0)
1258 || !slot.constraint_line_buffers.is_empty()
1259 || !slot.clip_plane_fill_buffers.is_empty()
1260 || !slot.clip_plane_line_buffers.is_empty()
1261 || !slot.xray_object_buffers.is_empty();
1262 if has_editor_overlays {
1263 let camera_bg = &slot.camera_bind_group;
1264 let mut overlay_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1265 label: Some("hdr_editor_overlay_pass"),
1266 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1267 view: output_view,
1268 resolve_target: None,
1269 ops: wgpu::Operations {
1270 load: wgpu::LoadOp::Load,
1271 store: wgpu::StoreOp::Store,
1272 },
1273 depth_slice: None,
1274 })],
1275 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1276 view: &slot_hdr.hdr_depth_view,
1277 depth_ops: Some(wgpu::Operations {
1278 load: wgpu::LoadOp::Load,
1279 store: wgpu::StoreOp::Discard,
1280 }),
1281 stencil_ops: None,
1282 }),
1283 timestamp_writes: None,
1284 occlusion_query_set: None,
1285 });
1286
1287 if frame.interaction.gizmo_model.is_some() && slot.gizmo_index_count > 0 {
1288 overlay_pass.set_pipeline(&self.resources.gizmo_pipeline);
1289 overlay_pass.set_bind_group(0, camera_bg, &[]);
1290 overlay_pass.set_bind_group(1, &slot.gizmo_bind_group, &[]);
1291 overlay_pass.set_vertex_buffer(0, slot.gizmo_vertex_buffer.slice(..));
1292 overlay_pass.set_index_buffer(
1293 slot.gizmo_index_buffer.slice(..),
1294 wgpu::IndexFormat::Uint32,
1295 );
1296 overlay_pass.draw_indexed(0..slot.gizmo_index_count, 0, 0..1);
1297 }
1298
1299 if !slot.constraint_line_buffers.is_empty() {
1300 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1301 overlay_pass.set_bind_group(0, camera_bg, &[]);
1302 for (vbuf, ibuf, index_count, _ubuf, bg) in &slot.constraint_line_buffers {
1303 overlay_pass.set_bind_group(1, bg, &[]);
1304 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1305 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1306 overlay_pass.draw_indexed(0..*index_count, 0, 0..1);
1307 }
1308 }
1309
1310 if !slot.clip_plane_fill_buffers.is_empty() {
1311 overlay_pass.set_pipeline(&self.resources.overlay_pipeline);
1312 overlay_pass.set_bind_group(0, camera_bg, &[]);
1313 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_fill_buffers {
1314 overlay_pass.set_bind_group(1, bg, &[]);
1315 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1316 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1317 overlay_pass.draw_indexed(0..*idx_count, 0, 0..1);
1318 }
1319 }
1320
1321 if !slot.clip_plane_line_buffers.is_empty() {
1322 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1323 overlay_pass.set_bind_group(0, camera_bg, &[]);
1324 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_line_buffers {
1325 overlay_pass.set_bind_group(1, bg, &[]);
1326 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1327 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1328 overlay_pass.draw_indexed(0..*idx_count, 0, 0..1);
1329 }
1330 }
1331
1332 if !slot.xray_object_buffers.is_empty() {
1333 overlay_pass.set_pipeline(&self.resources.xray_pipeline);
1334 overlay_pass.set_bind_group(0, camera_bg, &[]);
1335 for (mesh_idx, _buf, bg) in &slot.xray_object_buffers {
1336 let Some(mesh) = self
1337 .resources
1338 .mesh_store
1339 .get(crate::resources::mesh_store::MeshId(*mesh_idx))
1340 else {
1341 continue;
1342 };
1343 overlay_pass.set_bind_group(1, bg, &[]);
1344 overlay_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
1345 overlay_pass.set_index_buffer(
1346 mesh.index_buffer.slice(..),
1347 wgpu::IndexFormat::Uint32,
1348 );
1349 overlay_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
1350 }
1351 }
1352 }
1353 }
1354
1355 if frame.viewport.show_axes_indicator {
1358 let slot = &self.viewport_slots[vp_idx];
1359 if slot.axes_vertex_count > 0 {
1360 let slot_hdr = slot.hdr.as_ref().unwrap();
1361 let mut axes_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1362 label: Some("hdr_axes_pass"),
1363 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1364 view: output_view,
1365 resolve_target: None,
1366 ops: wgpu::Operations {
1367 load: wgpu::LoadOp::Load,
1368 store: wgpu::StoreOp::Store,
1369 },
1370 depth_slice: None,
1371 })],
1372 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1373 view: &slot_hdr.hdr_depth_view,
1374 depth_ops: Some(wgpu::Operations {
1375 load: wgpu::LoadOp::Load,
1376 store: wgpu::StoreOp::Discard,
1377 }),
1378 stencil_ops: None,
1379 }),
1380 timestamp_writes: None,
1381 occlusion_query_set: None,
1382 });
1383 axes_pass.set_pipeline(&self.resources.axes_pipeline);
1384 axes_pass.set_vertex_buffer(0, slot.axes_vertex_buffer.slice(..));
1385 axes_pass.draw(0..slot.axes_vertex_count, 0..1);
1386 }
1387 }
1388
1389 if !self.screen_image_gpu_data.is_empty() {
1392 if let Some(pipeline) = &self.resources.screen_image_pipeline {
1393 let slot_hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
1394 let mut img_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1395 label: Some("screen_image_pass"),
1396 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1397 view: output_view,
1398 resolve_target: None,
1399 ops: wgpu::Operations {
1400 load: wgpu::LoadOp::Load,
1401 store: wgpu::StoreOp::Store,
1402 },
1403 depth_slice: None,
1404 })],
1405 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1406 view: &slot_hdr.hdr_depth_view,
1407 depth_ops: Some(wgpu::Operations {
1408 load: wgpu::LoadOp::Load,
1409 store: wgpu::StoreOp::Discard,
1410 }),
1411 stencil_ops: None,
1412 }),
1413 timestamp_writes: None,
1414 occlusion_query_set: None,
1415 });
1416 img_pass.set_pipeline(pipeline);
1417 for gpu in &self.screen_image_gpu_data {
1418 img_pass.set_bind_group(0, &gpu.bind_group, &[]);
1419 img_pass.draw(0..6, 0..1);
1420 }
1421 }
1422 }
1423
1424 encoder.finish()
1425 }
1426
1427 pub fn render_offscreen(
1441 &mut self,
1442 device: &wgpu::Device,
1443 queue: &wgpu::Queue,
1444 frame: &FrameData,
1445 width: u32,
1446 height: u32,
1447 ) -> Vec<u8> {
1448 let target_format = self.resources.target_format;
1450 let offscreen_texture = device.create_texture(&wgpu::TextureDescriptor {
1451 label: Some("offscreen_target"),
1452 size: wgpu::Extent3d {
1453 width: width.max(1),
1454 height: height.max(1),
1455 depth_or_array_layers: 1,
1456 },
1457 mip_level_count: 1,
1458 sample_count: 1,
1459 dimension: wgpu::TextureDimension::D2,
1460 format: target_format,
1461 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
1462 view_formats: &[],
1463 });
1464
1465 let output_view = offscreen_texture.create_view(&wgpu::TextureViewDescriptor::default());
1467
1468 let cmd_buf = self.render(device, queue, &output_view, frame);
1476 queue.submit(std::iter::once(cmd_buf));
1477
1478 let bytes_per_pixel = 4u32;
1480 let unpadded_row = width * bytes_per_pixel;
1481 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
1482 let padded_row = (unpadded_row + align - 1) & !(align - 1);
1483 let buffer_size = (padded_row * height.max(1)) as u64;
1484
1485 let staging_buf = device.create_buffer(&wgpu::BufferDescriptor {
1486 label: Some("offscreen_staging"),
1487 size: buffer_size,
1488 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1489 mapped_at_creation: false,
1490 });
1491
1492 let mut copy_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1493 label: Some("offscreen_copy_encoder"),
1494 });
1495 copy_encoder.copy_texture_to_buffer(
1496 wgpu::TexelCopyTextureInfo {
1497 texture: &offscreen_texture,
1498 mip_level: 0,
1499 origin: wgpu::Origin3d::ZERO,
1500 aspect: wgpu::TextureAspect::All,
1501 },
1502 wgpu::TexelCopyBufferInfo {
1503 buffer: &staging_buf,
1504 layout: wgpu::TexelCopyBufferLayout {
1505 offset: 0,
1506 bytes_per_row: Some(padded_row),
1507 rows_per_image: Some(height.max(1)),
1508 },
1509 },
1510 wgpu::Extent3d {
1511 width: width.max(1),
1512 height: height.max(1),
1513 depth_or_array_layers: 1,
1514 },
1515 );
1516 queue.submit(std::iter::once(copy_encoder.finish()));
1517
1518 let (tx, rx) = std::sync::mpsc::channel();
1520 staging_buf
1521 .slice(..)
1522 .map_async(wgpu::MapMode::Read, move |result| {
1523 let _ = tx.send(result);
1524 });
1525 device
1526 .poll(wgpu::PollType::Wait {
1527 submission_index: None,
1528 timeout: Some(std::time::Duration::from_secs(5)),
1529 })
1530 .unwrap();
1531 let _ = rx.recv().unwrap_or(Err(wgpu::BufferAsyncError));
1532
1533 let mut pixels: Vec<u8> = Vec::with_capacity((width * height * 4) as usize);
1534 {
1535 let mapped = staging_buf.slice(..).get_mapped_range();
1536 let data: &[u8] = &mapped;
1537 if padded_row == unpadded_row {
1538 pixels.extend_from_slice(data);
1540 } else {
1541 for row in 0..height as usize {
1543 let start = row * padded_row as usize;
1544 let end = start + unpadded_row as usize;
1545 pixels.extend_from_slice(&data[start..end]);
1546 }
1547 }
1548 }
1549 staging_buf.unmap();
1550
1551 let is_bgra = matches!(
1553 target_format,
1554 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
1555 );
1556 if is_bgra {
1557 for pixel in pixels.chunks_exact_mut(4) {
1558 pixel.swap(0, 2); }
1560 }
1561
1562 pixels
1563 }
1564}