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 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 }
36
37 pub fn paint_to<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>, frame: &FrameData) {
43 let vp_idx = frame.camera.viewport_index;
44 let camera_bg = self.viewport_camera_bind_group(vp_idx);
45 let grid_bg = self.viewport_grid_bind_group(vp_idx);
46 let vp_slot = self.viewport_slots.get(vp_idx);
47 emit_draw_calls!(
48 &self.resources,
49 &mut *render_pass,
50 frame,
51 self.use_instancing,
52 &self.instanced_batches,
53 camera_bg,
54 grid_bg,
55 &self.compute_filter_results,
56 vp_slot
57 );
58 emit_scivis_draw_calls!(
59 &self.resources,
60 render_pass,
61 &self.point_cloud_gpu_data,
62 &self.glyph_gpu_data,
63 &self.polyline_gpu_data,
64 &self.volume_gpu_data,
65 &self.streamtube_gpu_data,
66 camera_bg
67 );
68 }
69
70 pub fn render_viewport(
84 &mut self,
85 device: &wgpu::Device,
86 queue: &wgpu::Queue,
87 output_view: &wgpu::TextureView,
88 id: ViewportId,
89 frame: &FrameData,
90 ) -> wgpu::CommandBuffer {
91 self.render_frame_internal(device, queue, output_view, id.0, frame)
92 }
93
94 pub fn render(
102 &mut self,
103 device: &wgpu::Device,
104 queue: &wgpu::Queue,
105 output_view: &wgpu::TextureView,
106 frame: &FrameData,
107 ) -> wgpu::CommandBuffer {
108 self.prepare(device, queue, frame);
110 self.render_frame_internal(
111 device,
112 queue,
113 output_view,
114 frame.camera.viewport_index,
115 frame,
116 )
117 }
118
119 fn render_frame_internal(
124 &mut self,
125 device: &wgpu::Device,
126 queue: &wgpu::Queue,
127 output_view: &wgpu::TextureView,
128 vp_idx: usize,
129 frame: &FrameData,
130 ) -> wgpu::CommandBuffer {
131 let scene_items: &[SceneRenderItem] = match &frame.scene.surfaces {
133 SurfaceSubmission::Flat(items) => items,
134 };
135
136 let bg_color = frame.viewport.background_color.unwrap_or([
137 65.0 / 255.0,
138 65.0 / 255.0,
139 65.0 / 255.0,
140 1.0,
141 ]);
142 let w = frame.camera.viewport_size[0] as u32;
143 let h = frame.camera.viewport_size[1] as u32;
144
145 self.ensure_viewport_hdr(device, queue, vp_idx, w.max(1), h.max(1));
147
148 if !frame.effects.post_process.enabled {
149 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
151 label: Some("ldr_encoder"),
152 });
153 {
154 let slot = &self.viewport_slots[vp_idx];
155 let slot_hdr = slot.hdr.as_ref().unwrap();
156 let camera_bg = &slot.camera_bind_group;
157 let grid_bg = &slot.grid_bind_group;
158 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
159 label: Some("ldr_render_pass"),
160 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
161 view: output_view,
162 resolve_target: None,
163 ops: wgpu::Operations {
164 load: wgpu::LoadOp::Clear(wgpu::Color {
165 r: bg_color[0] as f64,
166 g: bg_color[1] as f64,
167 b: bg_color[2] as f64,
168 a: bg_color[3] as f64,
169 }),
170 store: wgpu::StoreOp::Store,
171 },
172 depth_slice: None,
173 })],
174 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
175 view: &slot_hdr.outline_depth_view,
176 depth_ops: Some(wgpu::Operations {
177 load: wgpu::LoadOp::Clear(1.0),
178 store: wgpu::StoreOp::Discard,
179 }),
180 stencil_ops: None,
181 }),
182 timestamp_writes: None,
183 occlusion_query_set: None,
184 });
185 emit_draw_calls!(
186 &self.resources,
187 &mut render_pass,
188 frame,
189 self.use_instancing,
190 &self.instanced_batches,
191 camera_bg,
192 grid_bg,
193 &self.compute_filter_results,
194 Some(slot)
195 );
196 emit_scivis_draw_calls!(
197 &self.resources,
198 &mut render_pass,
199 &self.point_cloud_gpu_data,
200 &self.glyph_gpu_data,
201 &self.polyline_gpu_data,
202 &self.volume_gpu_data,
203 &self.streamtube_gpu_data,
204 camera_bg
205 );
206 }
207 return encoder.finish();
208 }
209
210 let pp = &frame.effects.post_process;
212
213 let hdr_clear_rgb = [
214 bg_color[0].powf(2.2),
215 bg_color[1].powf(2.2),
216 bg_color[2].powf(2.2),
217 ];
218
219 let mode = match pp.tone_mapping {
221 crate::renderer::ToneMapping::Reinhard => 0u32,
222 crate::renderer::ToneMapping::Aces => 1u32,
223 crate::renderer::ToneMapping::KhronosNeutral => 2u32,
224 };
225 let tm_uniform = crate::resources::ToneMapUniform {
226 exposure: pp.exposure,
227 mode,
228 bloom_enabled: if pp.bloom { 1 } else { 0 },
229 ssao_enabled: if pp.ssao { 1 } else { 0 },
230 contact_shadows_enabled: if pp.contact_shadows { 1 } else { 0 },
231 _pad_tm: [0; 3],
232 background_color: bg_color,
233 };
234 {
235 let hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
236 queue.write_buffer(
237 &hdr.tone_map_uniform_buf,
238 0,
239 bytemuck::cast_slice(&[tm_uniform]),
240 );
241
242 if pp.ssao {
244 let proj = frame.camera.render_camera.projection;
245 let inv_proj = proj.inverse();
246 let ssao_uniform = crate::resources::SsaoUniform {
247 inv_proj: inv_proj.to_cols_array_2d(),
248 proj: proj.to_cols_array_2d(),
249 radius: 0.5,
250 bias: 0.025,
251 _pad: [0.0; 2],
252 };
253 queue.write_buffer(
254 &hdr.ssao_uniform_buf,
255 0,
256 bytemuck::cast_slice(&[ssao_uniform]),
257 );
258 }
259
260 if pp.contact_shadows {
262 let proj = frame.camera.render_camera.projection;
263 let inv_proj = proj.inverse();
264 let light_dir_world: glam::Vec3 =
265 if let Some(l) = frame.effects.lighting.lights.first() {
266 match l.kind {
267 LightKind::Directional { direction } => {
268 glam::Vec3::from(direction).normalize()
269 }
270 LightKind::Spot { direction, .. } => {
271 glam::Vec3::from(direction).normalize()
272 }
273 _ => glam::Vec3::new(0.0, -1.0, 0.0),
274 }
275 } else {
276 glam::Vec3::new(0.0, -1.0, 0.0)
277 };
278 let view = frame.camera.render_camera.view;
279 let light_dir_view = view.transform_vector3(light_dir_world).normalize();
280 let world_up_view = view.transform_vector3(glam::Vec3::Z).normalize();
281 let cs_uniform = crate::resources::ContactShadowUniform {
282 inv_proj: inv_proj.to_cols_array_2d(),
283 proj: proj.to_cols_array_2d(),
284 light_dir_view: [light_dir_view.x, light_dir_view.y, light_dir_view.z, 0.0],
285 world_up_view: [world_up_view.x, world_up_view.y, world_up_view.z, 0.0],
286 params: [
287 pp.contact_shadow_max_distance,
288 pp.contact_shadow_steps as f32,
289 pp.contact_shadow_thickness,
290 0.0,
291 ],
292 };
293 queue.write_buffer(
294 &hdr.contact_shadow_uniform_buf,
295 0,
296 bytemuck::cast_slice(&[cs_uniform]),
297 );
298 }
299
300 if pp.bloom {
302 let bloom_u = crate::resources::BloomUniform {
303 threshold: pp.bloom_threshold,
304 intensity: pp.bloom_intensity,
305 horizontal: 0,
306 _pad: 0,
307 };
308 queue.write_buffer(&hdr.bloom_uniform_buf, 0, bytemuck::cast_slice(&[bloom_u]));
309 }
310 }
311
312 {
314 let hdr = self.viewport_slots[vp_idx].hdr.as_mut().unwrap();
315 self.resources.rebuild_tone_map_bind_group(
316 device,
317 hdr,
318 pp.bloom,
319 pp.ssao,
320 pp.contact_shadows,
321 );
322 }
323
324 {
329 let needs_oit = if self.use_instancing && !self.instanced_batches.is_empty() {
330 self.instanced_batches.iter().any(|b| b.is_transparent)
331 } else {
332 scene_items
333 .iter()
334 .any(|i| i.visible && i.material.opacity < 1.0)
335 };
336 if needs_oit {
337 let hdr = self.viewport_slots[vp_idx].hdr.as_mut().unwrap();
338 self.resources
339 .ensure_viewport_oit(device, hdr, w.max(1), h.max(1));
340 }
341 }
342
343 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
347 label: Some("hdr_encoder"),
348 });
349
350 let slot = &self.viewport_slots[vp_idx];
352 let camera_bg = &slot.camera_bind_group;
353 let slot_hdr = slot.hdr.as_ref().unwrap();
354
355 {
359 let hdr_view = &slot_hdr.hdr_view;
360 let hdr_depth_view = &slot_hdr.hdr_depth_view;
361
362 let clear_wgpu = wgpu::Color {
363 r: hdr_clear_rgb[0] as f64,
364 g: hdr_clear_rgb[1] as f64,
365 b: hdr_clear_rgb[2] as f64,
366 a: bg_color[3] as f64,
367 };
368
369 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
370 label: Some("hdr_scene_pass"),
371 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
372 view: hdr_view,
373 resolve_target: None,
374 ops: wgpu::Operations {
375 load: wgpu::LoadOp::Clear(clear_wgpu),
376 store: wgpu::StoreOp::Store,
377 },
378 depth_slice: None,
379 })],
380 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
381 view: hdr_depth_view,
382 depth_ops: Some(wgpu::Operations {
383 load: wgpu::LoadOp::Clear(1.0),
384 store: wgpu::StoreOp::Store,
385 }),
386 stencil_ops: Some(wgpu::Operations {
387 load: wgpu::LoadOp::Clear(0),
388 store: wgpu::StoreOp::Store,
389 }),
390 }),
391 timestamp_writes: None,
392 occlusion_query_set: None,
393 });
394
395 let resources = &self.resources;
396 render_pass.set_bind_group(0, camera_bg, &[]);
397
398 let show_skybox = frame
400 .effects
401 .environment
402 .as_ref()
403 .is_some_and(|e| e.show_skybox)
404 && resources.ibl_skybox_view.is_some();
405
406 let use_instancing = self.use_instancing;
407 let batches = &self.instanced_batches;
408
409 if !scene_items.is_empty() {
410 if use_instancing && !batches.is_empty() {
411 let excluded_items: Vec<&SceneRenderItem> = scene_items
412 .iter()
413 .filter(|item| {
414 item.visible
415 && (item.active_attribute.is_some() || item.two_sided)
416 && resources
417 .mesh_store
418 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
419 .is_some()
420 })
421 .collect();
422
423 let mut opaque_batches: Vec<&InstancedBatch> = Vec::new();
425 let mut transparent_batches: Vec<&InstancedBatch> = Vec::new();
426 for batch in batches {
427 if batch.is_transparent {
428 transparent_batches.push(batch);
429 } else {
430 opaque_batches.push(batch);
431 }
432 }
433
434 if !opaque_batches.is_empty() && !frame.viewport.wireframe_mode {
435 if let Some(ref pipeline) = resources.hdr_solid_instanced_pipeline {
436 render_pass.set_pipeline(pipeline);
437 for batch in &opaque_batches {
438 let Some(mesh) = resources
439 .mesh_store
440 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
441 else {
442 continue;
443 };
444 let mat_key = (
445 batch.texture_id.unwrap_or(u64::MAX),
446 batch.normal_map_id.unwrap_or(u64::MAX),
447 batch.ao_map_id.unwrap_or(u64::MAX),
448 );
449 let Some(inst_tex_bg) =
450 resources.instance_bind_groups.get(&mat_key)
451 else {
452 continue;
453 };
454 render_pass.set_bind_group(1, inst_tex_bg, &[]);
455 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
456 render_pass.set_index_buffer(
457 mesh.index_buffer.slice(..),
458 wgpu::IndexFormat::Uint32,
459 );
460 render_pass.draw_indexed(
461 0..mesh.index_count,
462 0,
463 batch.instance_offset
464 ..batch.instance_offset + batch.instance_count,
465 );
466 }
467 }
468 }
469
470 let _ = &transparent_batches; if frame.viewport.wireframe_mode {
475 if let Some(ref hdr_wf) = resources.hdr_wireframe_pipeline {
476 render_pass.set_pipeline(hdr_wf);
477 for item in scene_items {
478 if !item.visible {
479 continue;
480 }
481 let Some(mesh) = resources
482 .mesh_store
483 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
484 else {
485 continue;
486 };
487 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
488 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
489 render_pass.set_index_buffer(
490 mesh.edge_index_buffer.slice(..),
491 wgpu::IndexFormat::Uint32,
492 );
493 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
494 }
495 }
496 } else if let (Some(hdr_solid), Some(hdr_solid_two_sided)) = (
497 &resources.hdr_solid_pipeline,
498 &resources.hdr_solid_two_sided_pipeline,
499 ) {
500 for item in excluded_items
501 .into_iter()
502 .filter(|item| item.material.opacity >= 1.0)
503 {
504 let Some(mesh) = resources
505 .mesh_store
506 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
507 else {
508 continue;
509 };
510 let pipeline = if item.two_sided {
511 hdr_solid_two_sided
512 } else {
513 hdr_solid
514 };
515 render_pass.set_pipeline(pipeline);
516 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
517 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
518 render_pass.set_index_buffer(
519 mesh.index_buffer.slice(..),
520 wgpu::IndexFormat::Uint32,
521 );
522 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
523 }
524 }
525 } else {
526 let eye = glam::Vec3::from(frame.camera.render_camera.eye_position);
528 let dist_from_eye = |item: &&SceneRenderItem| -> f32 {
529 let pos =
530 glam::Vec3::new(item.model[3][0], item.model[3][1], item.model[3][2]);
531 (pos - eye).length()
532 };
533
534 let mut opaque: Vec<&SceneRenderItem> = Vec::new();
535 let mut transparent: Vec<&SceneRenderItem> = Vec::new();
536 for item in scene_items {
537 if !item.visible
538 || resources
539 .mesh_store
540 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
541 .is_none()
542 {
543 continue;
544 }
545 if item.material.opacity < 1.0 {
546 transparent.push(item);
547 } else {
548 opaque.push(item);
549 }
550 }
551 opaque.sort_by(|a, b| {
552 dist_from_eye(a)
553 .partial_cmp(&dist_from_eye(b))
554 .unwrap_or(std::cmp::Ordering::Equal)
555 });
556 transparent.sort_by(|a, b| {
557 dist_from_eye(b)
558 .partial_cmp(&dist_from_eye(a))
559 .unwrap_or(std::cmp::Ordering::Equal)
560 });
561
562 let draw_item_hdr =
563 |render_pass: &mut wgpu::RenderPass<'_>,
564 item: &SceneRenderItem,
565 solid_pl: &wgpu::RenderPipeline,
566 trans_pl: &wgpu::RenderPipeline,
567 wf_pl: &wgpu::RenderPipeline| {
568 let mesh = resources
569 .mesh_store
570 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
571 .unwrap();
572 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
575 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
576 if frame.viewport.wireframe_mode {
577 render_pass.set_pipeline(wf_pl);
578 render_pass.set_index_buffer(
579 mesh.edge_index_buffer.slice(..),
580 wgpu::IndexFormat::Uint32,
581 );
582 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
583 } else if item.material.opacity < 1.0 {
584 render_pass.set_pipeline(trans_pl);
585 render_pass.set_index_buffer(
586 mesh.index_buffer.slice(..),
587 wgpu::IndexFormat::Uint32,
588 );
589 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
590 } else {
591 render_pass.set_pipeline(solid_pl);
592 render_pass.set_index_buffer(
593 mesh.index_buffer.slice(..),
594 wgpu::IndexFormat::Uint32,
595 );
596 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
597 }
598 };
599
600 let _ = &transparent; if let (
604 Some(hdr_solid),
605 Some(hdr_solid_two_sided),
606 Some(hdr_trans),
607 Some(hdr_wf),
608 ) = (
609 &resources.hdr_solid_pipeline,
610 &resources.hdr_solid_two_sided_pipeline,
611 &resources.hdr_transparent_pipeline,
612 &resources.hdr_wireframe_pipeline,
613 ) {
614 for item in &opaque {
615 let solid_pl = if item.two_sided {
616 hdr_solid_two_sided
617 } else {
618 hdr_solid
619 };
620 draw_item_hdr(&mut render_pass, item, solid_pl, hdr_trans, hdr_wf);
621 }
622 }
623 }
624 }
625
626 if !slot.cap_buffers.is_empty() {
628 if let Some(ref hdr_overlay) = resources.hdr_overlay_pipeline {
629 render_pass.set_pipeline(hdr_overlay);
630 render_pass.set_bind_group(0, camera_bg, &[]);
631 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.cap_buffers {
632 render_pass.set_bind_group(1, bg, &[]);
633 render_pass.set_vertex_buffer(0, vbuf.slice(..));
634 render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
635 render_pass.draw_indexed(0..*idx_count, 0, 0..1);
636 }
637 }
638 }
639
640 emit_scivis_draw_calls!(
642 &self.resources,
643 &mut render_pass,
644 &self.point_cloud_gpu_data,
645 &self.glyph_gpu_data,
646 &self.polyline_gpu_data,
647 &self.volume_gpu_data,
648 &self.streamtube_gpu_data,
649 camera_bg
650 );
651
652 if show_skybox {
654 render_pass.set_bind_group(0, camera_bg, &[]);
655 render_pass.set_pipeline(&resources.skybox_pipeline);
656 render_pass.draw(0..3, 0..1);
657 }
658 }
659
660 let has_transparent = if self.use_instancing && !self.instanced_batches.is_empty() {
665 self.instanced_batches.iter().any(|b| b.is_transparent)
666 } else {
667 scene_items
668 .iter()
669 .any(|i| i.visible && i.material.opacity < 1.0)
670 };
671
672 if has_transparent {
673 if let (Some(accum_view), Some(reveal_view)) = (
675 slot_hdr.oit_accum_view.as_ref(),
676 slot_hdr.oit_reveal_view.as_ref(),
677 ) {
678 let hdr_depth_view = &slot_hdr.hdr_depth_view;
679 let mut oit_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
681 label: Some("oit_pass"),
682 color_attachments: &[
683 Some(wgpu::RenderPassColorAttachment {
684 view: accum_view,
685 resolve_target: None,
686 ops: wgpu::Operations {
687 load: wgpu::LoadOp::Clear(wgpu::Color {
688 r: 0.0,
689 g: 0.0,
690 b: 0.0,
691 a: 0.0,
692 }),
693 store: wgpu::StoreOp::Store,
694 },
695 depth_slice: None,
696 }),
697 Some(wgpu::RenderPassColorAttachment {
698 view: reveal_view,
699 resolve_target: None,
700 ops: wgpu::Operations {
701 load: wgpu::LoadOp::Clear(wgpu::Color {
702 r: 1.0,
703 g: 1.0,
704 b: 1.0,
705 a: 1.0,
706 }),
707 store: wgpu::StoreOp::Store,
708 },
709 depth_slice: None,
710 }),
711 ],
712 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
713 view: hdr_depth_view,
714 depth_ops: Some(wgpu::Operations {
715 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
717 }),
718 stencil_ops: None,
719 }),
720 timestamp_writes: None,
721 occlusion_query_set: None,
722 });
723
724 oit_pass.set_bind_group(0, camera_bg, &[]);
725
726 if self.use_instancing && !self.instanced_batches.is_empty() {
727 if let Some(ref pipeline) = self.resources.oit_instanced_pipeline {
728 oit_pass.set_pipeline(pipeline);
729 for batch in &self.instanced_batches {
730 if !batch.is_transparent {
731 continue;
732 }
733 let Some(mesh) = self
734 .resources
735 .mesh_store
736 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
737 else {
738 continue;
739 };
740 let mat_key = (
741 batch.texture_id.unwrap_or(u64::MAX),
742 batch.normal_map_id.unwrap_or(u64::MAX),
743 batch.ao_map_id.unwrap_or(u64::MAX),
744 );
745 let Some(inst_tex_bg) =
746 self.resources.instance_bind_groups.get(&mat_key)
747 else {
748 continue;
749 };
750 oit_pass.set_bind_group(1, inst_tex_bg, &[]);
751 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
752 oit_pass.set_index_buffer(
753 mesh.index_buffer.slice(..),
754 wgpu::IndexFormat::Uint32,
755 );
756 oit_pass.draw_indexed(
757 0..mesh.index_count,
758 0,
759 batch.instance_offset..batch.instance_offset + batch.instance_count,
760 );
761 }
762 }
763 } else if let Some(ref pipeline) = self.resources.oit_pipeline {
764 oit_pass.set_pipeline(pipeline);
765 for item in scene_items {
766 if !item.visible || item.material.opacity >= 1.0 {
767 continue;
768 }
769 let Some(mesh) = self
770 .resources
771 .mesh_store
772 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
773 else {
774 continue;
775 };
776 oit_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
777 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
778 oit_pass.set_index_buffer(
779 mesh.index_buffer.slice(..),
780 wgpu::IndexFormat::Uint32,
781 );
782 oit_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
783 }
784 }
785 }
786 }
787
788 if has_transparent {
793 if let (Some(pipeline), Some(bg)) = (
794 self.resources.oit_composite_pipeline.as_ref(),
795 slot_hdr.oit_composite_bind_group.as_ref(),
796 ) {
797 let hdr_view = &slot_hdr.hdr_view;
798 let mut composite_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
799 label: Some("oit_composite_pass"),
800 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
801 view: hdr_view,
802 resolve_target: None,
803 ops: wgpu::Operations {
804 load: wgpu::LoadOp::Load,
805 store: wgpu::StoreOp::Store,
806 },
807 depth_slice: None,
808 })],
809 depth_stencil_attachment: None,
810 timestamp_writes: None,
811 occlusion_query_set: None,
812 });
813 composite_pass.set_pipeline(pipeline);
814 composite_pass.set_bind_group(0, bg, &[]);
815 composite_pass.draw(0..3, 0..1);
816 }
817 }
818
819 if !slot.outline_object_buffers.is_empty() {
825 let hdr_pipeline = self
827 .resources
828 .outline_composite_pipeline_hdr
829 .as_ref()
830 .or(self.resources.outline_composite_pipeline_single.as_ref());
831 if let Some(pipeline) = hdr_pipeline {
832 let bg = &slot_hdr.outline_composite_bind_group;
833 let hdr_view = &slot_hdr.hdr_view;
834 let hdr_depth_view = &slot_hdr.hdr_depth_view;
835 let mut outline_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
836 label: Some("hdr_outline_composite_pass"),
837 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
838 view: hdr_view,
839 resolve_target: None,
840 ops: wgpu::Operations {
841 load: wgpu::LoadOp::Load,
842 store: wgpu::StoreOp::Store,
843 },
844 depth_slice: None,
845 })],
846 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
847 view: hdr_depth_view,
848 depth_ops: Some(wgpu::Operations {
849 load: wgpu::LoadOp::Load,
850 store: wgpu::StoreOp::Store,
851 }),
852 stencil_ops: None,
853 }),
854 timestamp_writes: None,
855 occlusion_query_set: None,
856 });
857 outline_pass.set_pipeline(pipeline);
858 outline_pass.set_bind_group(0, bg, &[]);
859 outline_pass.draw(0..3, 0..1);
860 }
861 }
862
863 if pp.ssao {
867 if let Some(ssao_pipeline) = &self.resources.ssao_pipeline {
868 {
869 let mut ssao_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
870 label: Some("ssao_pass"),
871 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
872 view: &slot_hdr.ssao_view,
873 resolve_target: None,
874 ops: wgpu::Operations {
875 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
876 store: wgpu::StoreOp::Store,
877 },
878 depth_slice: None,
879 })],
880 depth_stencil_attachment: None,
881 timestamp_writes: None,
882 occlusion_query_set: None,
883 });
884 ssao_pass.set_pipeline(ssao_pipeline);
885 ssao_pass.set_bind_group(0, &slot_hdr.ssao_bg, &[]);
886 ssao_pass.draw(0..3, 0..1);
887 }
888
889 if let Some(ssao_blur_pipeline) = &self.resources.ssao_blur_pipeline {
891 let mut ssao_blur_pass =
892 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
893 label: Some("ssao_blur_pass"),
894 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
895 view: &slot_hdr.ssao_blur_view,
896 resolve_target: None,
897 ops: wgpu::Operations {
898 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
899 store: wgpu::StoreOp::Store,
900 },
901 depth_slice: None,
902 })],
903 depth_stencil_attachment: None,
904 timestamp_writes: None,
905 occlusion_query_set: None,
906 });
907 ssao_blur_pass.set_pipeline(ssao_blur_pipeline);
908 ssao_blur_pass.set_bind_group(0, &slot_hdr.ssao_blur_bg, &[]);
909 ssao_blur_pass.draw(0..3, 0..1);
910 }
911 }
912 }
913
914 if pp.contact_shadows {
918 if let Some(cs_pipeline) = &self.resources.contact_shadow_pipeline {
919 let mut cs_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
920 label: Some("contact_shadow_pass"),
921 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
922 view: &slot_hdr.contact_shadow_view,
923 resolve_target: None,
924 depth_slice: None,
925 ops: wgpu::Operations {
926 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
927 store: wgpu::StoreOp::Store,
928 },
929 })],
930 depth_stencil_attachment: None,
931 timestamp_writes: None,
932 occlusion_query_set: None,
933 });
934 cs_pass.set_pipeline(cs_pipeline);
935 cs_pass.set_bind_group(0, &slot_hdr.contact_shadow_bg, &[]);
936 cs_pass.draw(0..3, 0..1);
937 }
938 }
939
940 if pp.bloom {
944 if let Some(bloom_threshold_pipeline) = &self.resources.bloom_threshold_pipeline {
946 {
947 let mut threshold_pass =
948 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
949 label: Some("bloom_threshold_pass"),
950 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
951 view: &slot_hdr.bloom_threshold_view,
952 resolve_target: None,
953 ops: wgpu::Operations {
954 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
955 store: wgpu::StoreOp::Store,
956 },
957 depth_slice: None,
958 })],
959 depth_stencil_attachment: None,
960 timestamp_writes: None,
961 occlusion_query_set: None,
962 });
963 threshold_pass.set_pipeline(bloom_threshold_pipeline);
964 threshold_pass.set_bind_group(0, &slot_hdr.bloom_threshold_bg, &[]);
965 threshold_pass.draw(0..3, 0..1);
966 }
967
968 if let Some(blur_pipeline) = &self.resources.bloom_blur_pipeline {
971 let blur_h_bg = &slot_hdr.bloom_blur_h_bg;
972 let blur_h_pong_bg = &slot_hdr.bloom_blur_h_pong_bg;
973 let blur_v_bg = &slot_hdr.bloom_blur_v_bg;
974 let bloom_ping_view = &slot_hdr.bloom_ping_view;
975 let bloom_pong_view = &slot_hdr.bloom_pong_view;
976 const BLUR_ITERATIONS: usize = 4;
977 for i in 0..BLUR_ITERATIONS {
978 let h_bg = if i == 0 { blur_h_bg } else { blur_h_pong_bg };
980 {
981 let mut h_pass =
982 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
983 label: Some("bloom_blur_h_pass"),
984 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
985 view: bloom_ping_view,
986 resolve_target: None,
987 ops: wgpu::Operations {
988 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
989 store: wgpu::StoreOp::Store,
990 },
991 depth_slice: None,
992 })],
993 depth_stencil_attachment: None,
994 timestamp_writes: None,
995 occlusion_query_set: None,
996 });
997 h_pass.set_pipeline(blur_pipeline);
998 h_pass.set_bind_group(0, h_bg, &[]);
999 h_pass.draw(0..3, 0..1);
1000 }
1001 {
1003 let mut v_pass =
1004 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1005 label: Some("bloom_blur_v_pass"),
1006 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1007 view: bloom_pong_view,
1008 resolve_target: None,
1009 ops: wgpu::Operations {
1010 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1011 store: wgpu::StoreOp::Store,
1012 },
1013 depth_slice: None,
1014 })],
1015 depth_stencil_attachment: None,
1016 timestamp_writes: None,
1017 occlusion_query_set: None,
1018 });
1019 v_pass.set_pipeline(blur_pipeline);
1020 v_pass.set_bind_group(0, blur_v_bg, &[]);
1021 v_pass.draw(0..3, 0..1);
1022 }
1023 }
1024 }
1025 }
1026 }
1027
1028 let use_fxaa = pp.fxaa;
1032 if let Some(tone_map_pipeline) = &self.resources.tone_map_pipeline {
1033 let tone_target: &wgpu::TextureView = if use_fxaa {
1034 &slot_hdr.fxaa_view
1035 } else {
1036 output_view
1037 };
1038 let mut tone_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1039 label: Some("tone_map_pass"),
1040 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1041 view: tone_target,
1042 resolve_target: None,
1043 ops: wgpu::Operations {
1044 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1045 store: wgpu::StoreOp::Store,
1046 },
1047 depth_slice: None,
1048 })],
1049 depth_stencil_attachment: None,
1050 timestamp_writes: None,
1051 occlusion_query_set: None,
1052 });
1053 tone_pass.set_pipeline(tone_map_pipeline);
1054 tone_pass.set_bind_group(0, &slot_hdr.tone_map_bind_group, &[]);
1055 tone_pass.draw(0..3, 0..1);
1056 }
1057
1058 if use_fxaa {
1062 if let Some(fxaa_pipeline) = &self.resources.fxaa_pipeline {
1063 let mut fxaa_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1064 label: Some("fxaa_pass"),
1065 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1066 view: output_view,
1067 resolve_target: None,
1068 ops: wgpu::Operations {
1069 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1070 store: wgpu::StoreOp::Store,
1071 },
1072 depth_slice: None,
1073 })],
1074 depth_stencil_attachment: None,
1075 timestamp_writes: None,
1076 occlusion_query_set: None,
1077 });
1078 fxaa_pass.set_pipeline(fxaa_pipeline);
1079 fxaa_pass.set_bind_group(0, &slot_hdr.fxaa_bind_group, &[]);
1080 fxaa_pass.draw(0..3, 0..1);
1081 }
1082 }
1083
1084 if frame.viewport.show_grid {
1088 let slot = &self.viewport_slots[vp_idx];
1089 let slot_hdr = slot.hdr.as_ref().unwrap();
1090 let grid_bg = &slot.grid_bind_group;
1091 let mut grid_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1092 label: Some("hdr_grid_pass"),
1093 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1094 view: output_view,
1095 resolve_target: None,
1096 ops: wgpu::Operations {
1097 load: wgpu::LoadOp::Load,
1098 store: wgpu::StoreOp::Store,
1099 },
1100 depth_slice: None,
1101 })],
1102 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1103 view: &slot_hdr.hdr_depth_view,
1104 depth_ops: Some(wgpu::Operations {
1105 load: wgpu::LoadOp::Load,
1106 store: wgpu::StoreOp::Store,
1107 }),
1108 stencil_ops: None,
1109 }),
1110 timestamp_writes: None,
1111 occlusion_query_set: None,
1112 });
1113 grid_pass.set_pipeline(&self.resources.grid_pipeline);
1114 grid_pass.set_bind_group(0, grid_bg, &[]);
1115 grid_pass.draw(0..3, 0..1);
1116 }
1117
1118 {
1122 let slot = &self.viewport_slots[vp_idx];
1123 let slot_hdr = slot.hdr.as_ref().unwrap();
1124 let has_editor_overlays =
1125 (frame.interaction.gizmo_model.is_some() && slot.gizmo_index_count > 0)
1126 || !slot.constraint_line_buffers.is_empty()
1127 || !slot.clip_plane_fill_buffers.is_empty()
1128 || !slot.clip_plane_line_buffers.is_empty()
1129 || !slot.xray_object_buffers.is_empty();
1130 if has_editor_overlays {
1131 let camera_bg = &slot.camera_bind_group;
1132 let mut overlay_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1133 label: Some("hdr_editor_overlay_pass"),
1134 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1135 view: output_view,
1136 resolve_target: None,
1137 ops: wgpu::Operations {
1138 load: wgpu::LoadOp::Load,
1139 store: wgpu::StoreOp::Store,
1140 },
1141 depth_slice: None,
1142 })],
1143 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1144 view: &slot_hdr.hdr_depth_view,
1145 depth_ops: Some(wgpu::Operations {
1146 load: wgpu::LoadOp::Load,
1147 store: wgpu::StoreOp::Discard,
1148 }),
1149 stencil_ops: None,
1150 }),
1151 timestamp_writes: None,
1152 occlusion_query_set: None,
1153 });
1154
1155 if frame.interaction.gizmo_model.is_some() && slot.gizmo_index_count > 0 {
1156 overlay_pass.set_pipeline(&self.resources.gizmo_pipeline);
1157 overlay_pass.set_bind_group(0, camera_bg, &[]);
1158 overlay_pass.set_bind_group(1, &slot.gizmo_bind_group, &[]);
1159 overlay_pass.set_vertex_buffer(0, slot.gizmo_vertex_buffer.slice(..));
1160 overlay_pass.set_index_buffer(
1161 slot.gizmo_index_buffer.slice(..),
1162 wgpu::IndexFormat::Uint32,
1163 );
1164 overlay_pass.draw_indexed(0..slot.gizmo_index_count, 0, 0..1);
1165 }
1166
1167 if !slot.constraint_line_buffers.is_empty() {
1168 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1169 overlay_pass.set_bind_group(0, camera_bg, &[]);
1170 for (vbuf, ibuf, index_count, _ubuf, bg) in &slot.constraint_line_buffers {
1171 overlay_pass.set_bind_group(1, bg, &[]);
1172 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1173 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1174 overlay_pass.draw_indexed(0..*index_count, 0, 0..1);
1175 }
1176 }
1177
1178 if !slot.clip_plane_fill_buffers.is_empty() {
1179 overlay_pass.set_pipeline(&self.resources.overlay_pipeline);
1180 overlay_pass.set_bind_group(0, camera_bg, &[]);
1181 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_fill_buffers {
1182 overlay_pass.set_bind_group(1, bg, &[]);
1183 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1184 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1185 overlay_pass.draw_indexed(0..*idx_count, 0, 0..1);
1186 }
1187 }
1188
1189 if !slot.clip_plane_line_buffers.is_empty() {
1190 overlay_pass.set_pipeline(&self.resources.overlay_line_pipeline);
1191 overlay_pass.set_bind_group(0, camera_bg, &[]);
1192 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_line_buffers {
1193 overlay_pass.set_bind_group(1, bg, &[]);
1194 overlay_pass.set_vertex_buffer(0, vbuf.slice(..));
1195 overlay_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
1196 overlay_pass.draw_indexed(0..*idx_count, 0, 0..1);
1197 }
1198 }
1199
1200 if !slot.xray_object_buffers.is_empty() {
1201 overlay_pass.set_pipeline(&self.resources.xray_pipeline);
1202 overlay_pass.set_bind_group(0, camera_bg, &[]);
1203 for (mesh_idx, _buf, bg) in &slot.xray_object_buffers {
1204 let Some(mesh) = self
1205 .resources
1206 .mesh_store
1207 .get(crate::resources::mesh_store::MeshId(*mesh_idx))
1208 else {
1209 continue;
1210 };
1211 overlay_pass.set_bind_group(1, bg, &[]);
1212 overlay_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
1213 overlay_pass.set_index_buffer(
1214 mesh.index_buffer.slice(..),
1215 wgpu::IndexFormat::Uint32,
1216 );
1217 overlay_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
1218 }
1219 }
1220 }
1221 }
1222
1223 if frame.viewport.show_axes_indicator {
1226 let slot = &self.viewport_slots[vp_idx];
1227 if slot.axes_vertex_count > 0 {
1228 let slot_hdr = slot.hdr.as_ref().unwrap();
1229 let mut axes_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1230 label: Some("hdr_axes_pass"),
1231 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1232 view: output_view,
1233 resolve_target: None,
1234 ops: wgpu::Operations {
1235 load: wgpu::LoadOp::Load,
1236 store: wgpu::StoreOp::Store,
1237 },
1238 depth_slice: None,
1239 })],
1240 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1241 view: &slot_hdr.hdr_depth_view,
1242 depth_ops: Some(wgpu::Operations {
1243 load: wgpu::LoadOp::Load,
1244 store: wgpu::StoreOp::Discard,
1245 }),
1246 stencil_ops: None,
1247 }),
1248 timestamp_writes: None,
1249 occlusion_query_set: None,
1250 });
1251 axes_pass.set_pipeline(&self.resources.axes_pipeline);
1252 axes_pass.set_vertex_buffer(0, slot.axes_vertex_buffer.slice(..));
1253 axes_pass.draw(0..slot.axes_vertex_count, 0..1);
1254 }
1255 }
1256
1257 encoder.finish()
1258 }
1259
1260 pub fn render_offscreen(
1274 &mut self,
1275 device: &wgpu::Device,
1276 queue: &wgpu::Queue,
1277 frame: &FrameData,
1278 width: u32,
1279 height: u32,
1280 ) -> Vec<u8> {
1281 let target_format = self.resources.target_format;
1283 let offscreen_texture = device.create_texture(&wgpu::TextureDescriptor {
1284 label: Some("offscreen_target"),
1285 size: wgpu::Extent3d {
1286 width: width.max(1),
1287 height: height.max(1),
1288 depth_or_array_layers: 1,
1289 },
1290 mip_level_count: 1,
1291 sample_count: 1,
1292 dimension: wgpu::TextureDimension::D2,
1293 format: target_format,
1294 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
1295 view_formats: &[],
1296 });
1297
1298 let output_view = offscreen_texture.create_view(&wgpu::TextureViewDescriptor::default());
1300
1301 let cmd_buf = self.render(device, queue, &output_view, frame);
1309 queue.submit(std::iter::once(cmd_buf));
1310
1311 let bytes_per_pixel = 4u32;
1313 let unpadded_row = width * bytes_per_pixel;
1314 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
1315 let padded_row = (unpadded_row + align - 1) & !(align - 1);
1316 let buffer_size = (padded_row * height.max(1)) as u64;
1317
1318 let staging_buf = device.create_buffer(&wgpu::BufferDescriptor {
1319 label: Some("offscreen_staging"),
1320 size: buffer_size,
1321 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1322 mapped_at_creation: false,
1323 });
1324
1325 let mut copy_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1326 label: Some("offscreen_copy_encoder"),
1327 });
1328 copy_encoder.copy_texture_to_buffer(
1329 wgpu::TexelCopyTextureInfo {
1330 texture: &offscreen_texture,
1331 mip_level: 0,
1332 origin: wgpu::Origin3d::ZERO,
1333 aspect: wgpu::TextureAspect::All,
1334 },
1335 wgpu::TexelCopyBufferInfo {
1336 buffer: &staging_buf,
1337 layout: wgpu::TexelCopyBufferLayout {
1338 offset: 0,
1339 bytes_per_row: Some(padded_row),
1340 rows_per_image: Some(height.max(1)),
1341 },
1342 },
1343 wgpu::Extent3d {
1344 width: width.max(1),
1345 height: height.max(1),
1346 depth_or_array_layers: 1,
1347 },
1348 );
1349 queue.submit(std::iter::once(copy_encoder.finish()));
1350
1351 let (tx, rx) = std::sync::mpsc::channel();
1353 staging_buf
1354 .slice(..)
1355 .map_async(wgpu::MapMode::Read, move |result| {
1356 let _ = tx.send(result);
1357 });
1358 device
1359 .poll(wgpu::PollType::Wait {
1360 submission_index: None,
1361 timeout: Some(std::time::Duration::from_secs(5)),
1362 })
1363 .unwrap();
1364 let _ = rx.recv().unwrap_or(Err(wgpu::BufferAsyncError));
1365
1366 let mut pixels: Vec<u8> = Vec::with_capacity((width * height * 4) as usize);
1367 {
1368 let mapped = staging_buf.slice(..).get_mapped_range();
1369 let data: &[u8] = &mapped;
1370 if padded_row == unpadded_row {
1371 pixels.extend_from_slice(data);
1373 } else {
1374 for row in 0..height as usize {
1376 let start = row * padded_row as usize;
1377 let end = start + unpadded_row as usize;
1378 pixels.extend_from_slice(&data[start..end]);
1379 }
1380 }
1381 }
1382 staging_buf.unmap();
1383
1384 let is_bgra = matches!(
1386 target_format,
1387 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
1388 );
1389 if is_bgra {
1390 for pixel in pixels.chunks_exact_mut(4) {
1391 pixel.swap(0, 2); }
1393 }
1394
1395 pixels
1396 }
1397}