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