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 mode = match pp.tone_mapping {
215 crate::renderer::ToneMapping::Reinhard => 0u32,
216 crate::renderer::ToneMapping::Aces => 1u32,
217 crate::renderer::ToneMapping::KhronosNeutral => 2u32,
218 };
219 let tm_uniform = crate::resources::ToneMapUniform {
220 exposure: pp.exposure,
221 mode,
222 bloom_enabled: if pp.bloom { 1 } else { 0 },
223 ssao_enabled: if pp.ssao { 1 } else { 0 },
224 contact_shadows_enabled: if pp.contact_shadows { 1 } else { 0 },
225 _pad_tm: [0; 3],
226 };
227 {
228 let hdr = self.viewport_slots[vp_idx].hdr.as_ref().unwrap();
229 queue.write_buffer(
230 &hdr.tone_map_uniform_buf,
231 0,
232 bytemuck::cast_slice(&[tm_uniform]),
233 );
234
235 if pp.ssao {
237 let proj = frame.camera.render_camera.projection;
238 let inv_proj = proj.inverse();
239 let ssao_uniform = crate::resources::SsaoUniform {
240 inv_proj: inv_proj.to_cols_array_2d(),
241 proj: proj.to_cols_array_2d(),
242 radius: 0.5,
243 bias: 0.025,
244 _pad: [0.0; 2],
245 };
246 queue.write_buffer(
247 &hdr.ssao_uniform_buf,
248 0,
249 bytemuck::cast_slice(&[ssao_uniform]),
250 );
251 }
252
253 if pp.contact_shadows {
255 let proj = frame.camera.render_camera.projection;
256 let inv_proj = proj.inverse();
257 let light_dir_world: glam::Vec3 =
258 if let Some(l) = frame.effects.lighting.lights.first() {
259 match l.kind {
260 LightKind::Directional { direction } => {
261 glam::Vec3::from(direction).normalize()
262 }
263 LightKind::Spot { direction, .. } => {
264 glam::Vec3::from(direction).normalize()
265 }
266 _ => glam::Vec3::new(0.0, -1.0, 0.0),
267 }
268 } else {
269 glam::Vec3::new(0.0, -1.0, 0.0)
270 };
271 let view = frame.camera.render_camera.view;
272 let light_dir_view = view.transform_vector3(light_dir_world).normalize();
273 let world_up_view = view.transform_vector3(glam::Vec3::Z).normalize();
274 let cs_uniform = crate::resources::ContactShadowUniform {
275 inv_proj: inv_proj.to_cols_array_2d(),
276 proj: proj.to_cols_array_2d(),
277 light_dir_view: [light_dir_view.x, light_dir_view.y, light_dir_view.z, 0.0],
278 world_up_view: [world_up_view.x, world_up_view.y, world_up_view.z, 0.0],
279 params: [
280 pp.contact_shadow_max_distance,
281 pp.contact_shadow_steps as f32,
282 pp.contact_shadow_thickness,
283 0.0,
284 ],
285 };
286 queue.write_buffer(
287 &hdr.contact_shadow_uniform_buf,
288 0,
289 bytemuck::cast_slice(&[cs_uniform]),
290 );
291 }
292
293 if pp.bloom {
295 let bloom_u = crate::resources::BloomUniform {
296 threshold: pp.bloom_threshold,
297 intensity: pp.bloom_intensity,
298 horizontal: 0,
299 _pad: 0,
300 };
301 queue.write_buffer(&hdr.bloom_uniform_buf, 0, bytemuck::cast_slice(&[bloom_u]));
302 }
303 }
304
305 {
307 let hdr = self.viewport_slots[vp_idx].hdr.as_mut().unwrap();
308 self.resources.rebuild_tone_map_bind_group(
309 device,
310 hdr,
311 pp.bloom,
312 pp.ssao,
313 pp.contact_shadows,
314 );
315 }
316
317 {
322 let needs_oit = if self.use_instancing && !self.instanced_batches.is_empty() {
323 self.instanced_batches.iter().any(|b| b.is_transparent)
324 } else {
325 scene_items
326 .iter()
327 .any(|i| i.visible && i.material.opacity < 1.0)
328 };
329 if needs_oit {
330 let hdr = self.viewport_slots[vp_idx].hdr.as_mut().unwrap();
331 self.resources
332 .ensure_viewport_oit(device, hdr, w.max(1), h.max(1));
333 }
334 }
335
336 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
340 label: Some("hdr_encoder"),
341 });
342
343 let slot = &self.viewport_slots[vp_idx];
345 let camera_bg = &slot.camera_bind_group;
346 let slot_hdr = slot.hdr.as_ref().unwrap();
347
348 {
352 let hdr_view = &slot_hdr.hdr_view;
353 let hdr_depth_view = &slot_hdr.hdr_depth_view;
354
355 let clear_wgpu = wgpu::Color {
356 r: bg_color[0] as f64,
357 g: bg_color[1] as f64,
358 b: bg_color[2] as f64,
359 a: bg_color[3] as f64,
360 };
361
362 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
363 label: Some("hdr_scene_pass"),
364 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
365 view: hdr_view,
366 resolve_target: None,
367 ops: wgpu::Operations {
368 load: wgpu::LoadOp::Clear(clear_wgpu),
369 store: wgpu::StoreOp::Store,
370 },
371 depth_slice: None,
372 })],
373 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
374 view: hdr_depth_view,
375 depth_ops: Some(wgpu::Operations {
376 load: wgpu::LoadOp::Clear(1.0),
377 store: wgpu::StoreOp::Store,
378 }),
379 stencil_ops: Some(wgpu::Operations {
380 load: wgpu::LoadOp::Clear(0),
381 store: wgpu::StoreOp::Store,
382 }),
383 }),
384 timestamp_writes: None,
385 occlusion_query_set: None,
386 });
387
388 let resources = &self.resources;
389 render_pass.set_bind_group(0, camera_bg, &[]);
390
391 let show_skybox = frame
393 .effects
394 .environment
395 .as_ref()
396 .is_some_and(|e| e.show_skybox)
397 && resources.ibl_skybox_view.is_some();
398
399 let use_instancing = self.use_instancing;
400 let batches = &self.instanced_batches;
401
402 if !scene_items.is_empty() {
403 if use_instancing && !batches.is_empty() {
404 let excluded_items: Vec<&SceneRenderItem> = scene_items
405 .iter()
406 .filter(|item| {
407 item.visible
408 && (item.active_attribute.is_some() || item.two_sided)
409 && resources
410 .mesh_store
411 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
412 .is_some()
413 })
414 .collect();
415
416 let mut opaque_batches: Vec<&InstancedBatch> = Vec::new();
418 let mut transparent_batches: Vec<&InstancedBatch> = Vec::new();
419 for batch in batches {
420 if batch.is_transparent {
421 transparent_batches.push(batch);
422 } else {
423 opaque_batches.push(batch);
424 }
425 }
426
427 if !opaque_batches.is_empty() && !frame.viewport.wireframe_mode {
428 if let Some(ref pipeline) = resources.hdr_solid_instanced_pipeline {
429 render_pass.set_pipeline(pipeline);
430 for batch in &opaque_batches {
431 let Some(mesh) = resources
432 .mesh_store
433 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
434 else {
435 continue;
436 };
437 let mat_key = (
438 batch.texture_id.unwrap_or(u64::MAX),
439 batch.normal_map_id.unwrap_or(u64::MAX),
440 batch.ao_map_id.unwrap_or(u64::MAX),
441 );
442 let Some(inst_tex_bg) =
443 resources.instance_bind_groups.get(&mat_key)
444 else {
445 continue;
446 };
447 render_pass.set_bind_group(1, inst_tex_bg, &[]);
448 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
449 render_pass.set_index_buffer(
450 mesh.index_buffer.slice(..),
451 wgpu::IndexFormat::Uint32,
452 );
453 render_pass.draw_indexed(
454 0..mesh.index_count,
455 0,
456 batch.instance_offset
457 ..batch.instance_offset + batch.instance_count,
458 );
459 }
460 }
461 }
462
463 let _ = &transparent_batches; if frame.viewport.wireframe_mode {
468 if let Some(ref hdr_wf) = resources.hdr_wireframe_pipeline {
469 render_pass.set_pipeline(hdr_wf);
470 for item in scene_items {
471 if !item.visible {
472 continue;
473 }
474 let Some(mesh) = resources
475 .mesh_store
476 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
477 else {
478 continue;
479 };
480 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
481 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
482 render_pass.set_index_buffer(
483 mesh.edge_index_buffer.slice(..),
484 wgpu::IndexFormat::Uint32,
485 );
486 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
487 }
488 }
489 } else if let (Some(hdr_solid), Some(hdr_solid_two_sided)) = (
490 &resources.hdr_solid_pipeline,
491 &resources.hdr_solid_two_sided_pipeline,
492 ) {
493 for item in excluded_items
494 .into_iter()
495 .filter(|item| item.material.opacity >= 1.0)
496 {
497 let Some(mesh) = resources
498 .mesh_store
499 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
500 else {
501 continue;
502 };
503 let pipeline = if item.two_sided {
504 hdr_solid_two_sided
505 } else {
506 hdr_solid
507 };
508 render_pass.set_pipeline(pipeline);
509 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
510 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
511 render_pass.set_index_buffer(
512 mesh.index_buffer.slice(..),
513 wgpu::IndexFormat::Uint32,
514 );
515 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
516 }
517 }
518 } else {
519 let eye = glam::Vec3::from(frame.camera.render_camera.eye_position);
521 let dist_from_eye = |item: &&SceneRenderItem| -> f32 {
522 let pos =
523 glam::Vec3::new(item.model[3][0], item.model[3][1], item.model[3][2]);
524 (pos - eye).length()
525 };
526
527 let mut opaque: Vec<&SceneRenderItem> = Vec::new();
528 let mut transparent: Vec<&SceneRenderItem> = Vec::new();
529 for item in scene_items {
530 if !item.visible
531 || resources
532 .mesh_store
533 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
534 .is_none()
535 {
536 continue;
537 }
538 if item.material.opacity < 1.0 {
539 transparent.push(item);
540 } else {
541 opaque.push(item);
542 }
543 }
544 opaque.sort_by(|a, b| {
545 dist_from_eye(a)
546 .partial_cmp(&dist_from_eye(b))
547 .unwrap_or(std::cmp::Ordering::Equal)
548 });
549 transparent.sort_by(|a, b| {
550 dist_from_eye(b)
551 .partial_cmp(&dist_from_eye(a))
552 .unwrap_or(std::cmp::Ordering::Equal)
553 });
554
555 let draw_item_hdr =
556 |render_pass: &mut wgpu::RenderPass<'_>,
557 item: &SceneRenderItem,
558 solid_pl: &wgpu::RenderPipeline,
559 trans_pl: &wgpu::RenderPipeline,
560 wf_pl: &wgpu::RenderPipeline| {
561 let mesh = resources
562 .mesh_store
563 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
564 .unwrap();
565 render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
568 render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
569 if frame.viewport.wireframe_mode {
570 render_pass.set_pipeline(wf_pl);
571 render_pass.set_index_buffer(
572 mesh.edge_index_buffer.slice(..),
573 wgpu::IndexFormat::Uint32,
574 );
575 render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
576 } else if item.material.opacity < 1.0 {
577 render_pass.set_pipeline(trans_pl);
578 render_pass.set_index_buffer(
579 mesh.index_buffer.slice(..),
580 wgpu::IndexFormat::Uint32,
581 );
582 render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
583 } else {
584 render_pass.set_pipeline(solid_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 }
591 };
592
593 let _ = &transparent; if let (
597 Some(hdr_solid),
598 Some(hdr_solid_two_sided),
599 Some(hdr_trans),
600 Some(hdr_wf),
601 ) = (
602 &resources.hdr_solid_pipeline,
603 &resources.hdr_solid_two_sided_pipeline,
604 &resources.hdr_transparent_pipeline,
605 &resources.hdr_wireframe_pipeline,
606 ) {
607 for item in &opaque {
608 let solid_pl = if item.two_sided {
609 hdr_solid_two_sided
610 } else {
611 hdr_solid
612 };
613 draw_item_hdr(&mut render_pass, item, solid_pl, hdr_trans, hdr_wf);
614 }
615 }
616 }
617 }
618
619 if !slot.cap_buffers.is_empty() {
621 if let Some(ref hdr_overlay) = resources.hdr_overlay_pipeline {
622 render_pass.set_pipeline(hdr_overlay);
623 render_pass.set_bind_group(0, camera_bg, &[]);
624 for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.cap_buffers {
625 render_pass.set_bind_group(1, bg, &[]);
626 render_pass.set_vertex_buffer(0, vbuf.slice(..));
627 render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
628 render_pass.draw_indexed(0..*idx_count, 0, 0..1);
629 }
630 }
631 }
632
633 emit_scivis_draw_calls!(
635 &self.resources,
636 &mut render_pass,
637 &self.point_cloud_gpu_data,
638 &self.glyph_gpu_data,
639 &self.polyline_gpu_data,
640 &self.volume_gpu_data,
641 &self.streamtube_gpu_data,
642 camera_bg
643 );
644
645 if show_skybox {
647 render_pass.set_bind_group(0, camera_bg, &[]);
648 render_pass.set_pipeline(&resources.skybox_pipeline);
649 render_pass.draw(0..3, 0..1);
650 }
651 }
652
653 let has_transparent = if self.use_instancing && !self.instanced_batches.is_empty() {
658 self.instanced_batches.iter().any(|b| b.is_transparent)
659 } else {
660 scene_items
661 .iter()
662 .any(|i| i.visible && i.material.opacity < 1.0)
663 };
664
665 if has_transparent {
666 if let (Some(accum_view), Some(reveal_view)) = (
668 slot_hdr.oit_accum_view.as_ref(),
669 slot_hdr.oit_reveal_view.as_ref(),
670 ) {
671 let hdr_depth_view = &slot_hdr.hdr_depth_view;
672 let mut oit_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
674 label: Some("oit_pass"),
675 color_attachments: &[
676 Some(wgpu::RenderPassColorAttachment {
677 view: accum_view,
678 resolve_target: None,
679 ops: wgpu::Operations {
680 load: wgpu::LoadOp::Clear(wgpu::Color {
681 r: 0.0,
682 g: 0.0,
683 b: 0.0,
684 a: 0.0,
685 }),
686 store: wgpu::StoreOp::Store,
687 },
688 depth_slice: None,
689 }),
690 Some(wgpu::RenderPassColorAttachment {
691 view: reveal_view,
692 resolve_target: None,
693 ops: wgpu::Operations {
694 load: wgpu::LoadOp::Clear(wgpu::Color {
695 r: 1.0,
696 g: 1.0,
697 b: 1.0,
698 a: 1.0,
699 }),
700 store: wgpu::StoreOp::Store,
701 },
702 depth_slice: None,
703 }),
704 ],
705 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
706 view: hdr_depth_view,
707 depth_ops: Some(wgpu::Operations {
708 load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store,
710 }),
711 stencil_ops: None,
712 }),
713 timestamp_writes: None,
714 occlusion_query_set: None,
715 });
716
717 oit_pass.set_bind_group(0, camera_bg, &[]);
718
719 if self.use_instancing && !self.instanced_batches.is_empty() {
720 if let Some(ref pipeline) = self.resources.oit_instanced_pipeline {
721 oit_pass.set_pipeline(pipeline);
722 for batch in &self.instanced_batches {
723 if !batch.is_transparent {
724 continue;
725 }
726 let Some(mesh) = self
727 .resources
728 .mesh_store
729 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
730 else {
731 continue;
732 };
733 let mat_key = (
734 batch.texture_id.unwrap_or(u64::MAX),
735 batch.normal_map_id.unwrap_or(u64::MAX),
736 batch.ao_map_id.unwrap_or(u64::MAX),
737 );
738 let Some(inst_tex_bg) =
739 self.resources.instance_bind_groups.get(&mat_key)
740 else {
741 continue;
742 };
743 oit_pass.set_bind_group(1, inst_tex_bg, &[]);
744 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
745 oit_pass.set_index_buffer(
746 mesh.index_buffer.slice(..),
747 wgpu::IndexFormat::Uint32,
748 );
749 oit_pass.draw_indexed(
750 0..mesh.index_count,
751 0,
752 batch.instance_offset..batch.instance_offset + batch.instance_count,
753 );
754 }
755 }
756 } else if let Some(ref pipeline) = self.resources.oit_pipeline {
757 oit_pass.set_pipeline(pipeline);
758 for item in scene_items {
759 if !item.visible || item.material.opacity >= 1.0 {
760 continue;
761 }
762 let Some(mesh) = self
763 .resources
764 .mesh_store
765 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
766 else {
767 continue;
768 };
769 oit_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
770 oit_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
771 oit_pass.set_index_buffer(
772 mesh.index_buffer.slice(..),
773 wgpu::IndexFormat::Uint32,
774 );
775 oit_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
776 }
777 }
778 }
779 }
780
781 if has_transparent {
786 if let (Some(pipeline), Some(bg)) = (
787 self.resources.oit_composite_pipeline.as_ref(),
788 slot_hdr.oit_composite_bind_group.as_ref(),
789 ) {
790 let hdr_view = &slot_hdr.hdr_view;
791 let mut composite_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
792 label: Some("oit_composite_pass"),
793 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
794 view: hdr_view,
795 resolve_target: None,
796 ops: wgpu::Operations {
797 load: wgpu::LoadOp::Load,
798 store: wgpu::StoreOp::Store,
799 },
800 depth_slice: None,
801 })],
802 depth_stencil_attachment: None,
803 timestamp_writes: None,
804 occlusion_query_set: None,
805 });
806 composite_pass.set_pipeline(pipeline);
807 composite_pass.set_bind_group(0, bg, &[]);
808 composite_pass.draw(0..3, 0..1);
809 }
810 }
811
812 if !slot.outline_object_buffers.is_empty() {
818 let hdr_pipeline = self
820 .resources
821 .outline_composite_pipeline_hdr
822 .as_ref()
823 .or(self.resources.outline_composite_pipeline_single.as_ref());
824 if let Some(pipeline) = hdr_pipeline {
825 let bg = &slot_hdr.outline_composite_bind_group;
826 let hdr_view = &slot_hdr.hdr_view;
827 let hdr_depth_view = &slot_hdr.hdr_depth_view;
828 let mut outline_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
829 label: Some("hdr_outline_composite_pass"),
830 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
831 view: hdr_view,
832 resolve_target: None,
833 ops: wgpu::Operations {
834 load: wgpu::LoadOp::Load,
835 store: wgpu::StoreOp::Store,
836 },
837 depth_slice: None,
838 })],
839 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
840 view: hdr_depth_view,
841 depth_ops: Some(wgpu::Operations {
842 load: wgpu::LoadOp::Load,
843 store: wgpu::StoreOp::Discard,
844 }),
845 stencil_ops: None,
846 }),
847 timestamp_writes: None,
848 occlusion_query_set: None,
849 });
850 outline_pass.set_pipeline(pipeline);
851 outline_pass.set_bind_group(0, bg, &[]);
852 outline_pass.draw(0..3, 0..1);
853 }
854 }
855
856 if pp.ssao {
860 if let Some(ssao_pipeline) = &self.resources.ssao_pipeline {
861 {
862 let mut ssao_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
863 label: Some("ssao_pass"),
864 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
865 view: &slot_hdr.ssao_view,
866 resolve_target: None,
867 ops: wgpu::Operations {
868 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
869 store: wgpu::StoreOp::Store,
870 },
871 depth_slice: None,
872 })],
873 depth_stencil_attachment: None,
874 timestamp_writes: None,
875 occlusion_query_set: None,
876 });
877 ssao_pass.set_pipeline(ssao_pipeline);
878 ssao_pass.set_bind_group(0, &slot_hdr.ssao_bg, &[]);
879 ssao_pass.draw(0..3, 0..1);
880 }
881
882 if let Some(ssao_blur_pipeline) = &self.resources.ssao_blur_pipeline {
884 let mut ssao_blur_pass =
885 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
886 label: Some("ssao_blur_pass"),
887 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
888 view: &slot_hdr.ssao_blur_view,
889 resolve_target: None,
890 ops: wgpu::Operations {
891 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
892 store: wgpu::StoreOp::Store,
893 },
894 depth_slice: None,
895 })],
896 depth_stencil_attachment: None,
897 timestamp_writes: None,
898 occlusion_query_set: None,
899 });
900 ssao_blur_pass.set_pipeline(ssao_blur_pipeline);
901 ssao_blur_pass.set_bind_group(0, &slot_hdr.ssao_blur_bg, &[]);
902 ssao_blur_pass.draw(0..3, 0..1);
903 }
904 }
905 }
906
907 if pp.contact_shadows {
911 if let Some(cs_pipeline) = &self.resources.contact_shadow_pipeline {
912 let mut cs_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
913 label: Some("contact_shadow_pass"),
914 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
915 view: &slot_hdr.contact_shadow_view,
916 resolve_target: None,
917 depth_slice: None,
918 ops: wgpu::Operations {
919 load: wgpu::LoadOp::Clear(wgpu::Color::WHITE),
920 store: wgpu::StoreOp::Store,
921 },
922 })],
923 depth_stencil_attachment: None,
924 timestamp_writes: None,
925 occlusion_query_set: None,
926 });
927 cs_pass.set_pipeline(cs_pipeline);
928 cs_pass.set_bind_group(0, &slot_hdr.contact_shadow_bg, &[]);
929 cs_pass.draw(0..3, 0..1);
930 }
931 }
932
933 if pp.bloom {
937 if let Some(bloom_threshold_pipeline) = &self.resources.bloom_threshold_pipeline {
939 {
940 let mut threshold_pass =
941 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
942 label: Some("bloom_threshold_pass"),
943 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
944 view: &slot_hdr.bloom_threshold_view,
945 resolve_target: None,
946 ops: wgpu::Operations {
947 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
948 store: wgpu::StoreOp::Store,
949 },
950 depth_slice: None,
951 })],
952 depth_stencil_attachment: None,
953 timestamp_writes: None,
954 occlusion_query_set: None,
955 });
956 threshold_pass.set_pipeline(bloom_threshold_pipeline);
957 threshold_pass.set_bind_group(0, &slot_hdr.bloom_threshold_bg, &[]);
958 threshold_pass.draw(0..3, 0..1);
959 }
960
961 if let Some(blur_pipeline) = &self.resources.bloom_blur_pipeline {
964 let blur_h_bg = &slot_hdr.bloom_blur_h_bg;
965 let blur_h_pong_bg = &slot_hdr.bloom_blur_h_pong_bg;
966 let blur_v_bg = &slot_hdr.bloom_blur_v_bg;
967 let bloom_ping_view = &slot_hdr.bloom_ping_view;
968 let bloom_pong_view = &slot_hdr.bloom_pong_view;
969 const BLUR_ITERATIONS: usize = 4;
970 for i in 0..BLUR_ITERATIONS {
971 let h_bg = if i == 0 { blur_h_bg } else { blur_h_pong_bg };
973 {
974 let mut h_pass =
975 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
976 label: Some("bloom_blur_h_pass"),
977 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
978 view: bloom_ping_view,
979 resolve_target: None,
980 ops: wgpu::Operations {
981 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
982 store: wgpu::StoreOp::Store,
983 },
984 depth_slice: None,
985 })],
986 depth_stencil_attachment: None,
987 timestamp_writes: None,
988 occlusion_query_set: None,
989 });
990 h_pass.set_pipeline(blur_pipeline);
991 h_pass.set_bind_group(0, h_bg, &[]);
992 h_pass.draw(0..3, 0..1);
993 }
994 {
996 let mut v_pass =
997 encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
998 label: Some("bloom_blur_v_pass"),
999 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1000 view: bloom_pong_view,
1001 resolve_target: None,
1002 ops: wgpu::Operations {
1003 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1004 store: wgpu::StoreOp::Store,
1005 },
1006 depth_slice: None,
1007 })],
1008 depth_stencil_attachment: None,
1009 timestamp_writes: None,
1010 occlusion_query_set: None,
1011 });
1012 v_pass.set_pipeline(blur_pipeline);
1013 v_pass.set_bind_group(0, blur_v_bg, &[]);
1014 v_pass.draw(0..3, 0..1);
1015 }
1016 }
1017 }
1018 }
1019 }
1020
1021 let use_fxaa = pp.fxaa;
1025 if let Some(tone_map_pipeline) = &self.resources.tone_map_pipeline {
1026 let tone_target: &wgpu::TextureView = if use_fxaa {
1027 &slot_hdr.fxaa_view
1028 } else {
1029 output_view
1030 };
1031 let mut tone_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1032 label: Some("tone_map_pass"),
1033 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1034 view: tone_target,
1035 resolve_target: None,
1036 ops: wgpu::Operations {
1037 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1038 store: wgpu::StoreOp::Store,
1039 },
1040 depth_slice: None,
1041 })],
1042 depth_stencil_attachment: None,
1043 timestamp_writes: None,
1044 occlusion_query_set: None,
1045 });
1046 tone_pass.set_pipeline(tone_map_pipeline);
1047 tone_pass.set_bind_group(0, &slot_hdr.tone_map_bind_group, &[]);
1048 tone_pass.draw(0..3, 0..1);
1049 }
1050
1051 if use_fxaa {
1055 if let Some(fxaa_pipeline) = &self.resources.fxaa_pipeline {
1056 let mut fxaa_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1057 label: Some("fxaa_pass"),
1058 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1059 view: output_view,
1060 resolve_target: None,
1061 ops: wgpu::Operations {
1062 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
1063 store: wgpu::StoreOp::Store,
1064 },
1065 depth_slice: None,
1066 })],
1067 depth_stencil_attachment: None,
1068 timestamp_writes: None,
1069 occlusion_query_set: None,
1070 });
1071 fxaa_pass.set_pipeline(fxaa_pipeline);
1072 fxaa_pass.set_bind_group(0, &slot_hdr.fxaa_bind_group, &[]);
1073 fxaa_pass.draw(0..3, 0..1);
1074 }
1075 }
1076
1077 encoder.finish()
1078 }
1079
1080 pub fn render_offscreen(
1094 &mut self,
1095 device: &wgpu::Device,
1096 queue: &wgpu::Queue,
1097 frame: &FrameData,
1098 width: u32,
1099 height: u32,
1100 ) -> Vec<u8> {
1101 let target_format = self.resources.target_format;
1103 let offscreen_texture = device.create_texture(&wgpu::TextureDescriptor {
1104 label: Some("offscreen_target"),
1105 size: wgpu::Extent3d {
1106 width: width.max(1),
1107 height: height.max(1),
1108 depth_or_array_layers: 1,
1109 },
1110 mip_level_count: 1,
1111 sample_count: 1,
1112 dimension: wgpu::TextureDimension::D2,
1113 format: target_format,
1114 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
1115 view_formats: &[],
1116 });
1117
1118 let output_view = offscreen_texture.create_view(&wgpu::TextureViewDescriptor::default());
1120
1121 let cmd_buf = self.render(device, queue, &output_view, frame);
1129 queue.submit(std::iter::once(cmd_buf));
1130
1131 let bytes_per_pixel = 4u32;
1133 let unpadded_row = width * bytes_per_pixel;
1134 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
1135 let padded_row = (unpadded_row + align - 1) & !(align - 1);
1136 let buffer_size = (padded_row * height.max(1)) as u64;
1137
1138 let staging_buf = device.create_buffer(&wgpu::BufferDescriptor {
1139 label: Some("offscreen_staging"),
1140 size: buffer_size,
1141 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
1142 mapped_at_creation: false,
1143 });
1144
1145 let mut copy_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1146 label: Some("offscreen_copy_encoder"),
1147 });
1148 copy_encoder.copy_texture_to_buffer(
1149 wgpu::TexelCopyTextureInfo {
1150 texture: &offscreen_texture,
1151 mip_level: 0,
1152 origin: wgpu::Origin3d::ZERO,
1153 aspect: wgpu::TextureAspect::All,
1154 },
1155 wgpu::TexelCopyBufferInfo {
1156 buffer: &staging_buf,
1157 layout: wgpu::TexelCopyBufferLayout {
1158 offset: 0,
1159 bytes_per_row: Some(padded_row),
1160 rows_per_image: Some(height.max(1)),
1161 },
1162 },
1163 wgpu::Extent3d {
1164 width: width.max(1),
1165 height: height.max(1),
1166 depth_or_array_layers: 1,
1167 },
1168 );
1169 queue.submit(std::iter::once(copy_encoder.finish()));
1170
1171 let (tx, rx) = std::sync::mpsc::channel();
1173 staging_buf
1174 .slice(..)
1175 .map_async(wgpu::MapMode::Read, move |result| {
1176 let _ = tx.send(result);
1177 });
1178 device
1179 .poll(wgpu::PollType::Wait {
1180 submission_index: None,
1181 timeout: Some(std::time::Duration::from_secs(5)),
1182 })
1183 .unwrap();
1184 let _ = rx.recv().unwrap_or(Err(wgpu::BufferAsyncError));
1185
1186 let mut pixels: Vec<u8> = Vec::with_capacity((width * height * 4) as usize);
1187 {
1188 let mapped = staging_buf.slice(..).get_mapped_range();
1189 let data: &[u8] = &mapped;
1190 if padded_row == unpadded_row {
1191 pixels.extend_from_slice(data);
1193 } else {
1194 for row in 0..height as usize {
1196 let start = row * padded_row as usize;
1197 let end = start + unpadded_row as usize;
1198 pixels.extend_from_slice(&data[start..end]);
1199 }
1200 }
1201 }
1202 staging_buf.unmap();
1203
1204 let is_bgra = matches!(
1206 target_format,
1207 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb
1208 );
1209 if is_bgra {
1210 for pixel in pixels.chunks_exact_mut(4) {
1211 pixel.swap(0, 2); }
1213 }
1214
1215 pixels
1216 }
1217}