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.material.is_two_sided()
460 || item.material.matcap_id.is_some())
461 && resources
462 .mesh_store
463 .get(item.mesh_id)
464 .is_some()
465 })
466 .collect();
467
468 let mut opaque_batches: Vec<&InstancedBatch> = Vec::new();
470 let mut transparent_batches: Vec<&InstancedBatch> = Vec::new();
471 for batch in batches {
472 if batch.is_transparent {
473 transparent_batches.push(batch);
474 } else {
475 opaque_batches.push(batch);
476 }
477 }
478
479 if !opaque_batches.is_empty() && !frame.viewport.wireframe_mode {
480 if let Some(ref pipeline) = resources.hdr_solid_instanced_pipeline {
481 render_pass.set_pipeline(pipeline);
482 for batch in &opaque_batches {
483 let Some(mesh) = resources
484 .mesh_store
485 .get(batch.mesh_id)
486 else {
487 continue;
488 };
489 let mat_key = (
490 batch.texture_id.unwrap_or(u64::MAX),
491 batch.normal_map_id.unwrap_or(u64::MAX),
492 batch.ao_map_id.unwrap_or(u64::MAX),
493 );
494 let Some(inst_tex_bg) =
495 resources.instance_bind_groups.get(&mat_key)
496 else {
497 continue;
498 };
499 render_pass.set_bind_group(1, inst_tex_bg, &[]);
500 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
501 render_pass.set_index_buffer(
502 mesh.index_buffer.slice(..),
503 wgpu::IndexFormat::Uint32,
504 );
505 render_pass.draw_indexed(
506 0..mesh.index_count,
507 0,
508 batch.instance_offset
509 ..batch.instance_offset + batch.instance_count,
510 );
511 }
512 }
513 }
514
515 let _ = &transparent_batches; if frame.viewport.wireframe_mode {
520 if let Some(ref hdr_wf) = resources.hdr_wireframe_pipeline {
521 render_pass.set_pipeline(hdr_wf);
522 for item in scene_items {
523 if !item.visible {
524 continue;
525 }
526 let Some(mesh) = resources
527 .mesh_store
528 .get(item.mesh_id)
529 else {
530 continue;
531 };
532 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
533 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
534 render_pass.set_index_buffer(
535 mesh.edge_index_buffer.slice(..),
536 wgpu::IndexFormat::Uint32,
537 );
538 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
539 }
540 }
541 } else if let (Some(hdr_solid), Some(hdr_solid_two_sided)) = (
542 &resources.hdr_solid_pipeline,
543 &resources.hdr_solid_two_sided_pipeline,
544 ) {
545 for item in excluded_items
546 .into_iter()
547 .filter(|item| item.material.opacity >= 1.0)
548 {
549 let Some(mesh) = resources
550 .mesh_store
551 .get(item.mesh_id)
552 else {
553 continue;
554 };
555 let pipeline = if item.material.is_two_sided() {
556 hdr_solid_two_sided
557 } else {
558 hdr_solid
559 };
560 render_pass.set_pipeline(pipeline);
561 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
562 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
563 render_pass.set_index_buffer(
564 mesh.index_buffer.slice(..),
565 wgpu::IndexFormat::Uint32,
566 );
567 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
568 }
569 }
570 } else {
571 let eye = glam::Vec3::from(frame.camera.render_camera.eye_position);
573 let dist_from_eye = |item: &&SceneRenderItem| -> f32 {
574 let pos =
575 glam::Vec3::new(item.model[3][0], item.model[3][1], item.model[3][2]);
576 (pos - eye).length()
577 };
578
579 let mut opaque: Vec<&SceneRenderItem> = Vec::new();
580 let mut transparent: Vec<&SceneRenderItem> = Vec::new();
581 for item in scene_items {
582 if !item.visible
583 || resources
584 .mesh_store
585 .get(item.mesh_id)
586 .is_none()
587 {
588 continue;
589 }
590 if item.material.opacity < 1.0 {
591 transparent.push(item);
592 } else {
593 opaque.push(item);
594 }
595 }
596 opaque.sort_by(|a, b| {
597 dist_from_eye(a)
598 .partial_cmp(&dist_from_eye(b))
599 .unwrap_or(std::cmp::Ordering::Equal)
600 });
601 transparent.sort_by(|a, b| {
602 dist_from_eye(b)
603 .partial_cmp(&dist_from_eye(a))
604 .unwrap_or(std::cmp::Ordering::Equal)
605 });
606
607 let draw_item_hdr =
608 |render_pass: &mut wgpu::RenderPass<'_>,
609 item: &SceneRenderItem,
610 solid_pl: &wgpu::RenderPipeline,
611 trans_pl: &wgpu::RenderPipeline,
612 wf_pl: &wgpu::RenderPipeline| {
613 let mesh = resources
614 .mesh_store
615 .get(item.mesh_id)
616 .unwrap();
617 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
620 let is_face_attr = item.active_attribute.as_ref().map_or(false, |a| {
621 matches!(
622 a.kind,
623 crate::resources::AttributeKind::Face
624 | crate::resources::AttributeKind::FaceColor
625 )
626 });
627 if frame.viewport.wireframe_mode {
628 render_pass.set_pipeline(wf_pl);
629 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
630 render_pass.set_index_buffer(
631 mesh.edge_index_buffer.slice(..),
632 wgpu::IndexFormat::Uint32,
633 );
634 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
635 } else if is_face_attr {
636 if let Some(ref fvb) = mesh.face_vertex_buffer {
637 let pl = if item.material.opacity < 1.0 {
638 trans_pl
639 } else {
640 solid_pl
641 };
642 render_pass.set_pipeline(pl);
643 render_pass.set_vertex_buffer(0, fvb.slice(..));
644 render_pass.draw(0..mesh.index_count, 0..1);
645 }
646 } else if item.material.opacity < 1.0 {
647 render_pass.set_pipeline(trans_pl);
648 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
649 render_pass.set_index_buffer(
650 mesh.index_buffer.slice(..),
651 wgpu::IndexFormat::Uint32,
652 );
653 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
654 } else {
655 render_pass.set_pipeline(solid_pl);
656 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
657 render_pass.set_index_buffer(
658 mesh.index_buffer.slice(..),
659 wgpu::IndexFormat::Uint32,
660 );
661 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
662 }
663 };
664
665 let _ = &transparent; if let (
669 Some(hdr_solid),
670 Some(hdr_solid_two_sided),
671 Some(hdr_trans),
672 Some(hdr_wf),
673 ) = (
674 &resources.hdr_solid_pipeline,
675 &resources.hdr_solid_two_sided_pipeline,
676 &resources.hdr_transparent_pipeline,
677 &resources.hdr_wireframe_pipeline,
678 ) {
679 for item in &opaque {
680 let solid_pl = if item.material.is_two_sided() {
681 hdr_solid_two_sided
682 } else {
683 hdr_solid
684 };
685 draw_item_hdr(&mut render_pass, item, solid_pl, hdr_trans, hdr_wf);
686 }
687 }
688 }
689 }
690
691 if !slot.cap_buffers.is_empty() {
693 if let Some(ref hdr_overlay) = resources.hdr_overlay_pipeline {
694 render_pass.set_pipeline(hdr_overlay);
695 render_pass.set_bind_group(0, camera_bg, &[]);
696 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.cap_buffers {
697 render_pass.set_bind_group(1, bg, &[]);
698 render_pass.set_vertex_buffer(0, vbuf.slice(..));
699 render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
700 render_pass.draw_indexed(0..*idx_count, 0, 0..1);
701 }
702 }
703 }
704
705 emit_scivis_draw_calls!(
707 &self.resources,
708 &mut render_pass,
709 &self.point_cloud_gpu_data,
710 &self.glyph_gpu_data,
711 &self.polyline_gpu_data,
712 &self.volume_gpu_data,
713 &self.streamtube_gpu_data,
714 camera_bg
715 );
716
717 if show_skybox {
719 render_pass.set_bind_group(0, camera_bg, &[]);
720 render_pass.set_pipeline(&resources.skybox_pipeline);
721 render_pass.draw(0..3, 0..1);
722 }
723 }
724
725 if ssaa_factor > 1 {
730 let slot_hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
731 if let (Some(pipeline), Some(bg)) = (
732 &self.resources.ssaa_resolve_pipeline,
733 &slot_hdr.ssaa_resolve_bind_group,
734 ) {
735 let mut resolve_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
736 label: Some("ssaa_resolve_pass"),
737 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
738 view: &slot_hdr.hdr_view,
739 resolve_target: None,
740 ops: wgpu::Operations {
741 load: wgpu::LoadOp::Load,
742 store: wgpu::StoreOp::Store,
743 },
744 depth_slice: None,
745 })],
746 depth_stencil_attachment: None,
747 timestamp_writes: None,
748 occlusion_query_set: None,
749 });
750 resolve_pass.set_pipeline(pipeline);
751 resolve_pass.set_bind_group(0, bg, &[]);
752 resolve_pass.draw(0..3, 0..1);
753 }
754 }
755
756 let has_transparent = if self.use_instancing && !self.instanced_batches.is_empty() {
761 self.instanced_batches.iter().any(|b| b.is_transparent)
762 } else {
763 scene_items
764 .iter()
765 .any(|i| i.visible && i.material.opacity < 1.0)
766 };
767
768 if has_transparent {
769 if let (Some(accum_view), Some(reveal_view)) = (
771 slot_hdr.oit_accum_view.as_ref(),
772 slot_hdr.oit_reveal_view.as_ref(),
773 ) {
774 let hdr_depth_view = &slot_hdr.hdr_depth_view;
775 let mut oit_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
777 label: Some("oit_pass"),
778 color_attachments: &[
779 Some(wgpu::RenderPassColorAttachment {
780 view: accum_view,
781 resolve_target: None,
782 ops: wgpu::Operations {
783 load: wgpu::LoadOp::Clear(wgpu::Color {
784 r: 0.0,
785 g: 0.0,
786 b: 0.0,
787 a: 0.0,
788 }),
789 store: wgpu::StoreOp::Store,
790 },
791 depth_slice: None,
792 }),
793 Some(wgpu::RenderPassColorAttachment {
794 view: reveal_view,
795 resolve_target: None,
796 ops: wgpu::Operations {
797 load: wgpu::LoadOp::Clear(wgpu::Color {
798 r: 1.0,
799 g: 1.0,
800 b: 1.0,
801 a: 1.0,
802 }),
803 store: wgpu::StoreOp::Store,
804 },
805 depth_slice: None,
806 }),
807 ],
808 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
809 view: hdr_depth_view,
810 depth_ops: Some(wgpu::Operations {
811 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
813 }),
814 stencil_ops: None,
815 }),
816 timestamp_writes: None,
817 occlusion_query_set: None,
818 });
819
820 oit_pass.set_bind_group(0, camera_bg, &[]);
821
822 if self.use_instancing && !self.instanced_batches.is_empty() {
823 if let Some(ref pipeline) = self.resources.oit_instanced_pipeline {
824 oit_pass.set_pipeline(pipeline);
825 for batch in &self.instanced_batches {
826 if !batch.is_transparent {
827 continue;
828 }
829 let Some(mesh) = self
830 .resources
831 .mesh_store
832 .get(batch.mesh_id)
833 else {
834 continue;
835 };
836 let mat_key = (
837 batch.texture_id.unwrap_or(u64::MAX),
838 batch.normal_map_id.unwrap_or(u64::MAX),
839 batch.ao_map_id.unwrap_or(u64::MAX),
840 );
841 let Some(inst_tex_bg) =
842 self.resources.instance_bind_groups.get(&mat_key)
843 else {
844 continue;
845 };
846 oit_pass.set_bind_group(1, inst_tex_bg, &[]);
847 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
848 oit_pass.set_index_buffer(
849 mesh.index_buffer.slice(..),
850 wgpu::IndexFormat::Uint32,
851 );
852 oit_pass.draw_indexed(
853 0..mesh.index_count,
854 0,
855 batch.instance_offset..batch.instance_offset + batch.instance_count,
856 );
857 }
858 }
859 } else if let Some(ref pipeline) = self.resources.oit_pipeline {
860 oit_pass.set_pipeline(pipeline);
861 for item in scene_items {
862 if !item.visible || item.material.opacity >= 1.0 {
863 continue;
864 }
865 let Some(mesh) = self
866 .resources
867 .mesh_store
868 .get(item.mesh_id)
869 else {
870 continue;
871 };
872 oit_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
873 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
874 oit_pass.set_index_buffer(
875 mesh.index_buffer.slice(..),
876 wgpu::IndexFormat::Uint32,
877 );
878 oit_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
879 }
880 }
881 }
882 }
883
884 if has_transparent {
889 if let (Some(pipeline), Some(bg)) = (
890 self.resources.oit_composite_pipeline.as_ref(),
891 slot_hdr.oit_composite_bind_group.as_ref(),
892 ) {
893 let hdr_view = &slot_hdr.hdr_view;
894 let mut composite_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
895 label: Some("oit_composite_pass"),
896 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
897 view: hdr_view,
898 resolve_target: None,
899 ops: wgpu::Operations {
900 load: wgpu::LoadOp::Load,
901 store: wgpu::StoreOp::Store,
902 },
903 depth_slice: None,
904 })],
905 depth_stencil_attachment: None,
906 timestamp_writes: None,
907 occlusion_query_set: None,
908 });
909 composite_pass.set_pipeline(pipeline);
910 composite_pass.set_bind_group(0, bg, &[]);
911 composite_pass.draw(0..3, 0..1);
912 }
913 }
914
915 if !slot.outline_object_buffers.is_empty() {
921 let hdr_pipeline = self
923 .resources
924 .outline_composite_pipeline_hdr
925 .as_ref()
926 .or(self.resources.outline_composite_pipeline_single.as_ref());
927 if let Some(pipeline) = hdr_pipeline {
928 let bg = &slot_hdr.outline_composite_bind_group;
929 let hdr_view = &slot_hdr.hdr_view;
930 let hdr_depth_view = &slot_hdr.hdr_depth_view;
931 let mut outline_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
932 label: Some("hdr_outline_composite_pass"),
933 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
934 view: hdr_view,
935 resolve_target: None,
936 ops: wgpu::Operations {
937 load: wgpu::LoadOp::Load,
938 store: wgpu::StoreOp::Store,
939 },
940 depth_slice: None,
941 })],
942 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
943 view: hdr_depth_view,
944 depth_ops: Some(wgpu::Operations {
945 load: wgpu::LoadOp::Load,
946 store: wgpu::StoreOp::Store,
947 }),
948 stencil_ops: None,
949 }),
950 timestamp_writes: None,
951 occlusion_query_set: None,
952 });
953 outline_pass.set_pipeline(pipeline);
954 outline_pass.set_bind_group(0, bg, &[]);
955 outline_pass.draw(0..3, 0..1);
956 }
957 }
958
959 if pp.ssao {
963 if let Some(ssao_pipeline) = &self.resources.ssao_pipeline {
964 {
965 let mut ssao_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
966 label: Some("ssao_pass"),
967 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
968 view: &slot_hdr.ssao_view,
969 resolve_target: None,
970 ops: wgpu::Operations {
971 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
972 store: wgpu::StoreOp::Store,
973 },
974 depth_slice: None,
975 })],
976 depth_stencil_attachment: None,
977 timestamp_writes: None,
978 occlusion_query_set: None,
979 });
980 ssao_pass.set_pipeline(ssao_pipeline);
981 ssao_pass.set_bind_group(0, &slot_hdr.ssao_bg, &[]);
982 ssao_pass.draw(0..3, 0..1);
983 }
984
985 if let Some(ssao_blur_pipeline) = &self.resources.ssao_blur_pipeline {
987 let mut ssao_blur_pass =
988 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
989 label: Some("ssao_blur_pass"),
990 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
991 view: &slot_hdr.ssao_blur_view,
992 resolve_target: None,
993 ops: wgpu::Operations {
994 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
995 store: wgpu::StoreOp::Store,
996 },
997 depth_slice: None,
998 })],
999 depth_stencil_attachment: None,
1000 timestamp_writes: None,
1001 occlusion_query_set: None,
1002 });
1003 ssao_blur_pass.set_pipeline(ssao_blur_pipeline);
1004 ssao_blur_pass.set_bind_group(0, &slot_hdr.ssao_blur_bg, &[]);
1005 ssao_blur_pass.draw(0..3, 0..1);
1006 }
1007 }
1008 }
1009
1010 if pp.contact_shadows {
1014 if let Some(cs_pipeline) = &self.resources.contact_shadow_pipeline {
1015 let mut cs_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1016 label: Some("contact_shadow_pass"),
1017 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1018 view: &slot_hdr.contact_shadow_view,
1019 resolve_target: None,
1020 depth_slice: None,
1021 ops: wgpu::Operations {
1022 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
1023 store: wgpu::StoreOp::Store,
1024 },
1025 })],
1026 depth_stencil_attachment: None,
1027 timestamp_writes: None,
1028 occlusion_query_set: None,
1029 });
1030 cs_pass.set_pipeline(cs_pipeline);
1031 cs_pass.set_bind_group(0, &slot_hdr.contact_shadow_bg, &[]);
1032 cs_pass.draw(0..3, 0..1);
1033 }
1034 }
1035
1036 if pp.bloom {
1040 if let Some(bloom_threshold_pipeline) = &self.resources.bloom_threshold_pipeline {
1042 {
1043 let mut threshold_pass =
1044 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1045 label: Some("bloom_threshold_pass"),
1046 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1047 view: &slot_hdr.bloom_threshold_view,
1048 resolve_target: None,
1049 ops: wgpu::Operations {
1050 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1051 store: wgpu::StoreOp::Store,
1052 },
1053 depth_slice: None,
1054 })],
1055 depth_stencil_attachment: None,
1056 timestamp_writes: None,
1057 occlusion_query_set: None,
1058 });
1059 threshold_pass.set_pipeline(bloom_threshold_pipeline);
1060 threshold_pass.set_bind_group(0, &slot_hdr.bloom_threshold_bg, &[]);
1061 threshold_pass.draw(0..3, 0..1);
1062 }
1063
1064 if let Some(blur_pipeline) = &self.resources.bloom_blur_pipeline {
1067 let blur_h_bg = &slot_hdr.bloom_blur_h_bg;
1068 let blur_h_pong_bg = &slot_hdr.bloom_blur_h_pong_bg;
1069 let blur_v_bg = &slot_hdr.bloom_blur_v_bg;
1070 let bloom_ping_view = &slot_hdr.bloom_ping_view;
1071 let bloom_pong_view = &slot_hdr.bloom_pong_view;
1072 const BLUR_ITERATIONS: usize = 4;
1073 for i in 0..BLUR_ITERATIONS {
1074 let h_bg = if i == 0 { blur_h_bg } else { blur_h_pong_bg };
1076 {
1077 let mut h_pass =
1078 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1079 label: Some("bloom_blur_h_pass"),
1080 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1081 view: bloom_ping_view,
1082 resolve_target: None,
1083 ops: wgpu::Operations {
1084 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1085 store: wgpu::StoreOp::Store,
1086 },
1087 depth_slice: None,
1088 })],
1089 depth_stencil_attachment: None,
1090 timestamp_writes: None,
1091 occlusion_query_set: None,
1092 });
1093 h_pass.set_pipeline(blur_pipeline);
1094 h_pass.set_bind_group(0, h_bg, &[]);
1095 h_pass.draw(0..3, 0..1);
1096 }
1097 {
1099 let mut v_pass =
1100 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1101 label: Some("bloom_blur_v_pass"),
1102 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1103 view: bloom_pong_view,
1104 resolve_target: None,
1105 ops: wgpu::Operations {
1106 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1107 store: wgpu::StoreOp::Store,
1108 },
1109 depth_slice: None,
1110 })],
1111 depth_stencil_attachment: None,
1112 timestamp_writes: None,
1113 occlusion_query_set: None,
1114 });
1115 v_pass.set_pipeline(blur_pipeline);
1116 v_pass.set_bind_group(0, blur_v_bg, &[]);
1117 v_pass.draw(0..3, 0..1);
1118 }
1119 }
1120 }
1121 }
1122 }
1123
1124 let use_fxaa = pp.fxaa;
1128 if let Some(tone_map_pipeline) = &self.resources.tone_map_pipeline {
1129 let tone_target: &wgpu::TextureView = if use_fxaa {
1130 &slot_hdr.fxaa_view
1131 } else {
1132 output_view
1133 };
1134 let mut tone_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1135 label: Some("tone_map_pass"),
1136 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1137 view: tone_target,
1138 resolve_target: None,
1139 ops: wgpu::Operations {
1140 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1141 store: wgpu::StoreOp::Store,
1142 },
1143 depth_slice: None,
1144 })],
1145 depth_stencil_attachment: None,
1146 timestamp_writes: None,
1147 occlusion_query_set: None,
1148 });
1149 tone_pass.set_pipeline(tone_map_pipeline);
1150 tone_pass.set_bind_group(0, &slot_hdr.tone_map_bind_group, &[]);
1151 tone_pass.draw(0..3, 0..1);
1152 }
1153
1154 if use_fxaa {
1158 if let Some(fxaa_pipeline) = &self.resources.fxaa_pipeline {
1159 let mut fxaa_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1160 label: Some("fxaa_pass"),
1161 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1162 view: output_view,
1163 resolve_target: None,
1164 ops: wgpu::Operations {
1165 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1166 store: wgpu::StoreOp::Store,
1167 },
1168 depth_slice: None,
1169 })],
1170 depth_stencil_attachment: None,
1171 timestamp_writes: None,
1172 occlusion_query_set: None,
1173 });
1174 fxaa_pass.set_pipeline(fxaa_pipeline);
1175 fxaa_pass.set_bind_group(0, &slot_hdr.fxaa_bind_group, &[]);
1176 fxaa_pass.draw(0..3, 0..1);
1177 }
1178 }
1179
1180 if frame.viewport.show_grid {
1184 let slot = &self.viewport_slots[vp_idx];
1185 let slot_hdr = slot.hdr.as_ref().unwrap();
1186 let grid_bg = &slot.grid_bind_group;
1187 let mut grid_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1188 label: Some("hdr_grid_pass"),
1189 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1190 view: output_view,
1191 resolve_target: None,
1192 ops: wgpu::Operations {
1193 load: wgpu::LoadOp::Load,
1194 store: wgpu::StoreOp::Store,
1195 },
1196 depth_slice: None,
1197 })],
1198 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1199 view: &slot_hdr.hdr_depth_view,
1200 depth_ops: Some(wgpu::Operations {
1201 load: wgpu::LoadOp::Load,
1202 store: wgpu::StoreOp::Store,
1203 }),
1204 stencil_ops: None,
1205 }),
1206 timestamp_writes: None,
1207 occlusion_query_set: None,
1208 });
1209 grid_pass.set_pipeline(&self.resources.grid_pipeline);
1210 grid_pass.set_bind_group(0, grid_bg, &[]);
1211 grid_pass.draw(0..3, 0..1);
1212 }
1213
1214 if !matches!(
1217 frame.effects.ground_plane.mode,
1218 crate::renderer::types::GroundPlaneMode::None
1219 ) {
1220 let slot = &self.viewport_slots[vp_idx];
1221 let slot_hdr = slot.hdr.as_ref().unwrap();
1222 let mut gp_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1223 label: Some("hdr_ground_plane_pass"),
1224 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1225 view: output_view,
1226 resolve_target: None,
1227 ops: wgpu::Operations {
1228 load: wgpu::LoadOp::Load,
1229 store: wgpu::StoreOp::Store,
1230 },
1231 depth_slice: None,
1232 })],
1233 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1234 view: &slot_hdr.hdr_depth_view,
1235 depth_ops: Some(wgpu::Operations {
1236 load: wgpu::LoadOp::Load,
1237 store: wgpu::StoreOp::Store,
1238 }),
1239 stencil_ops: None,
1240 }),
1241 timestamp_writes: None,
1242 occlusion_query_set: None,
1243 });
1244 gp_pass.set_pipeline(&self.resources.ground_plane_pipeline);
1245 gp_pass.set_bind_group(0, &self.resources.ground_plane_bind_group, &[]);
1246 gp_pass.draw(0..3, 0..1);
1247 }
1248
1249 {
1253 let slot = &self.viewport_slots[vp_idx];
1254 let slot_hdr = slot.hdr.as_ref().unwrap();
1255 let has_editor_overlays = (frame.interaction.gizmo_model.is_some()
1256 && slot.gizmo_index_count > 0)
1257 || !slot.constraint_line_buffers.is_empty()
1258 || !slot.clip_plane_fill_buffers.is_empty()
1259 || !slot.clip_plane_line_buffers.is_empty()
1260 || !slot.xray_object_buffers.is_empty();
1261 if has_editor_overlays {
1262 let camera_bg = &slot.camera_bind_group;
1263 let mut overlay_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1264 label: Some("hdr_editor_overlay_pass"),
1265 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1266 view: output_view,
1267 resolve_target: None,
1268 ops: wgpu::Operations {
1269 load: wgpu::LoadOp::Load,
1270 store: wgpu::StoreOp::Store,
1271 },
1272 depth_slice: None,
1273 })],
1274 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1275 view: &slot_hdr.hdr_depth_view,
1276 depth_ops: Some(wgpu::Operations {
1277 load: wgpu::LoadOp::Load,
1278 store: wgpu::StoreOp::Discard,
1279 }),
1280 stencil_ops: None,
1281 }),
1282 timestamp_writes: None,
1283 occlusion_query_set: None,
1284 });
1285
1286 if frame.interaction.gizmo_model.is_some() && slot.gizmo_index_count > 0 {
1287 overlay_pass.set_pipeline(&self.resources.gizmo_pipeline);
1288 overlay_pass.set_bind_group(0, camera_bg, &[]);
1289 overlay_pass.set_bind_group(1, &slot.gizmo_bind_group, &[]);
1290 overlay_pass.set_vertex_buffer(0, slot.gizmo_vertex_buffer.slice(..));
1291 overlay_pass.set_index_buffer(
1292 slot.gizmo_index_buffer.slice(..),
1293 wgpu::IndexFormat::Uint32,
1294 );
1295 overlay_pass.draw_indexed(0..slot.gizmo_index_count, 0, 0..1);
1296 }
1297
1298 if !slot.constraint_line_buffers.is_empty() {
1299 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1300 overlay_pass.set_bind_group(0, camera_bg, &[]);
1301 for (vbuf, ibuf, index_count, _ubuf, bg) in &slot.constraint_line_buffers {
1302 overlay_pass.set_bind_group(1, bg, &[]);
1303 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1304 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1305 overlay_pass.draw_indexed(0..*index_count, 0, 0..1);
1306 }
1307 }
1308
1309 if !slot.clip_plane_fill_buffers.is_empty() {
1310 overlay_pass.set_pipeline(&self.resources.overlay_pipeline);
1311 overlay_pass.set_bind_group(0, camera_bg, &[]);
1312 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_fill_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..*idx_count, 0, 0..1);
1317 }
1318 }
1319
1320 if !slot.clip_plane_line_buffers.is_empty() {
1321 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1322 overlay_pass.set_bind_group(0, camera_bg, &[]);
1323 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_line_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.xray_object_buffers.is_empty() {
1332 overlay_pass.set_pipeline(&self.resources.xray_pipeline);
1333 overlay_pass.set_bind_group(0, camera_bg, &[]);
1334 for (mesh_id, _buf, bg) in &slot.xray_object_buffers {
1335 let Some(mesh) = self
1336 .resources
1337 .mesh_store
1338 .get(*mesh_id)
1339 else {
1340 continue;
1341 };
1342 overlay_pass.set_bind_group(1, bg, &[]);
1343 overlay_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
1344 overlay_pass.set_index_buffer(
1345 mesh.index_buffer.slice(..),
1346 wgpu::IndexFormat::Uint32,
1347 );
1348 overlay_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
1349 }
1350 }
1351 }
1352 }
1353
1354 if frame.viewport.show_axes_indicator {
1357 let slot = &self.viewport_slots[vp_idx];
1358 if slot.axes_vertex_count > 0 {
1359 let slot_hdr = slot.hdr.as_ref().unwrap();
1360 let mut axes_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1361 label: Some("hdr_axes_pass"),
1362 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1363 view: output_view,
1364 resolve_target: None,
1365 ops: wgpu::Operations {
1366 load: wgpu::LoadOp::Load,
1367 store: wgpu::StoreOp::Store,
1368 },
1369 depth_slice: None,
1370 })],
1371 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1372 view: &slot_hdr.hdr_depth_view,
1373 depth_ops: Some(wgpu::Operations {
1374 load: wgpu::LoadOp::Load,
1375 store: wgpu::StoreOp::Discard,
1376 }),
1377 stencil_ops: None,
1378 }),
1379 timestamp_writes: None,
1380 occlusion_query_set: None,
1381 });
1382 axes_pass.set_pipeline(&self.resources.axes_pipeline);
1383 axes_pass.set_vertex_buffer(0, slot.axes_vertex_buffer.slice(..));
1384 axes_pass.draw(0..slot.axes_vertex_count, 0..1);
1385 }
1386 }
1387
1388 if !self.screen_image_gpu_data.is_empty() {
1391 if let Some(pipeline) = &self.resources.screen_image_pipeline {
1392 let slot_hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
1393 let mut img_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1394 label: Some("screen_image_pass"),
1395 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1396 view: output_view,
1397 resolve_target: None,
1398 ops: wgpu::Operations {
1399 load: wgpu::LoadOp::Load,
1400 store: wgpu::StoreOp::Store,
1401 },
1402 depth_slice: None,
1403 })],
1404 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1405 view: &slot_hdr.hdr_depth_view,
1406 depth_ops: Some(wgpu::Operations {
1407 load: wgpu::LoadOp::Load,
1408 store: wgpu::StoreOp::Discard,
1409 }),
1410 stencil_ops: None,
1411 }),
1412 timestamp_writes: None,
1413 occlusion_query_set: None,
1414 });
1415 img_pass.set_pipeline(pipeline);
1416 for gpu in &self.screen_image_gpu_data {
1417 img_pass.set_bind_group(0, &gpu.bind_group, &[]);
1418 img_pass.draw(0..6, 0..1);
1419 }
1420 }
1421 }
1422
1423 encoder.finish()
1424 }
1425
1426 pub fn render_offscreen(
1440 &mut self,
1441 device: &wgpu::Device,
1442 queue: &wgpu::Queue,
1443 frame: &FrameData,
1444 width: u32,
1445 height: u32,
1446 ) -> Vec<u8> {
1447 let target_format = self.resources.target_format;
1449 let offscreen_texture = device.create_texture(&wgpu::TextureDescriptor {
1450 label: Some("offscreen_target"),
1451 size: wgpu::Extent3d {
1452 width: width.max(1),
1453 height: height.max(1),
1454 depth_or_array_layers: 1,
1455 },
1456 mip_level_count: 1,
1457 sample_count: 1,
1458 dimension: wgpu::TextureDimension::D2,
1459 format: target_format,
1460 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
1461 view_formats: &[],
1462 });
1463
1464 let output_view = offscreen_texture.create_view(&wgpu::TextureViewDescriptor::default());
1466
1467 let cmd_buf = self.render(device, queue, &output_view, frame);
1475 queue.submit(std::iter::once(cmd_buf));
1476
1477 let bytes_per_pixel = 4u32;
1479 let unpadded_row = width * bytes_per_pixel;
1480 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
1481 let padded_row = (unpadded_row + align - 1) & !(align - 1);
1482 let buffer_size = (padded_row * height.max(1)) as u64;
1483
1484 let staging_buf = device.create_buffer(&wgpu::BufferDescriptor {
1485 label: Some("offscreen_staging"),
1486 size: buffer_size,
1487 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1488 mapped_at_creation: false,
1489 });
1490
1491 let mut copy_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1492 label: Some("offscreen_copy_encoder"),
1493 });
1494 copy_encoder.copy_texture_to_buffer(
1495 wgpu::TexelCopyTextureInfo {
1496 texture: &offscreen_texture,
1497 mip_level: 0,
1498 origin: wgpu::Origin3d::ZERO,
1499 aspect: wgpu::TextureAspect::All,
1500 },
1501 wgpu::TexelCopyBufferInfo {
1502 buffer: &staging_buf,
1503 layout: wgpu::TexelCopyBufferLayout {
1504 offset: 0,
1505 bytes_per_row: Some(padded_row),
1506 rows_per_image: Some(height.max(1)),
1507 },
1508 },
1509 wgpu::Extent3d {
1510 width: width.max(1),
1511 height: height.max(1),
1512 depth_or_array_layers: 1,
1513 },
1514 );
1515 queue.submit(std::iter::once(copy_encoder.finish()));
1516
1517 let (tx, rx) = std::sync::mpsc::channel();
1519 staging_buf
1520 .slice(..)
1521 .map_async(wgpu::MapMode::Read, move |result| {
1522 let _ = tx.send(result);
1523 });
1524 device
1525 .poll(wgpu::PollType::Wait {
1526 submission_index: None,
1527 timeout: Some(std::time::Duration::from_secs(5)),
1528 })
1529 .unwrap();
1530 let _ = rx.recv().unwrap_or(Err(wgpu::BufferAsyncError));
1531
1532 let mut pixels: Vec<u8> = Vec::with_capacity((width * height * 4) as usize);
1533 {
1534 let mapped = staging_buf.slice(..).get_mapped_range();
1535 let data: &[u8] = &mapped;
1536 if padded_row == unpadded_row {
1537 pixels.extend_from_slice(data);
1539 } else {
1540 for row in 0..height as usize {
1542 let start = row * padded_row as usize;
1543 let end = start + unpadded_row as usize;
1544 pixels.extend_from_slice(&data[start..end]);
1545 }
1546 }
1547 }
1548 staging_buf.unmap();
1549
1550 let is_bgra = matches!(
1552 target_format,
1553 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
1554 );
1555 if is_bgra {
1556 for pixel in pixels.chunks_exact_mut(4) {
1557 pixel.swap(0, 2); }
1559 }
1560
1561 pixels
1562 }
1563}