1use super::types::{SceneEffects, ViewportEffects};
2use super::*;
3
4impl ViewportRenderer {
5 pub(super) fn prepare_scene_internal(
13 &mut self,
14 device: &wgpu::Device,
15 queue: &wgpu::Queue,
16 frame: &FrameData,
17 scene_fx: &SceneEffects<'_>,
18 ) {
19 if !scene_fx.compute_filter_items.is_empty() {
22 self.compute_filter_results =
23 self.resources
24 .run_compute_filters(device, queue, scene_fx.compute_filter_items);
25 } else {
26 self.compute_filter_results.clear();
27 }
28
29 self.resources.ensure_colormaps_initialized(device, queue);
31
32 let resources = &mut self.resources;
33 let lighting = scene_fx.lighting;
34
35 let scene_items: &[SceneRenderItem] = match &frame.scene.surfaces {
37 SurfaceSubmission::Flat(items) => items,
38 };
39
40 let (shadow_center, shadow_extent) = if let Some(extent) = lighting.shadow_extent_override {
42 (glam::Vec3::ZERO, extent)
43 } else {
44 (glam::Vec3::ZERO, 20.0)
45 };
46
47 fn compute_shadow_matrix(
49 kind: &LightKind,
50 shadow_center: glam::Vec3,
51 shadow_extent: f32,
52 ) -> glam::Mat4 {
53 match kind {
54 LightKind::Directional { direction } => {
55 let dir = glam::Vec3::from(*direction).normalize();
56 let light_up = if dir.z.abs() > 0.99 {
57 glam::Vec3::Y
58 } else {
59 glam::Vec3::Z
60 };
61 let light_pos = shadow_center + dir * shadow_extent * 2.0;
62 let light_view = glam::Mat4::look_at_rh(light_pos, shadow_center, light_up);
63 let light_proj = glam::Mat4::orthographic_rh(
64 -shadow_extent,
65 shadow_extent,
66 -shadow_extent,
67 shadow_extent,
68 0.01,
69 shadow_extent * 5.0,
70 );
71 light_proj * light_view
72 }
73 LightKind::Point { position, range } => {
74 let pos = glam::Vec3::from(*position);
75 let to_center = (shadow_center - pos).normalize();
76 let light_up = if to_center.z.abs() > 0.99 {
77 glam::Vec3::Y
78 } else {
79 glam::Vec3::Z
80 };
81 let light_view = glam::Mat4::look_at_rh(pos, shadow_center, light_up);
82 let light_proj =
83 glam::Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, *range);
84 light_proj * light_view
85 }
86 LightKind::Spot {
87 position,
88 direction,
89 range,
90 ..
91 } => {
92 let pos = glam::Vec3::from(*position);
93 let dir = glam::Vec3::from(*direction).normalize();
94 let look_target = pos + dir;
95 let up = if dir.z.abs() > 0.99 {
96 glam::Vec3::Y
97 } else {
98 glam::Vec3::Z
99 };
100 let light_view = glam::Mat4::look_at_rh(pos, look_target, up);
101 let light_proj =
102 glam::Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, *range);
103 light_proj * light_view
104 }
105 }
106 }
107
108 fn build_single_light_uniform(
110 src: &LightSource,
111 shadow_center: glam::Vec3,
112 shadow_extent: f32,
113 compute_shadow: bool,
114 ) -> SingleLightUniform {
115 let shadow_mat = if compute_shadow {
116 compute_shadow_matrix(&src.kind, shadow_center, shadow_extent)
117 } else {
118 glam::Mat4::IDENTITY
119 };
120
121 match &src.kind {
122 LightKind::Directional { direction } => SingleLightUniform {
123 light_view_proj: shadow_mat.to_cols_array_2d(),
124 pos_or_dir: *direction,
125 light_type: 0,
126 color: src.color,
127 intensity: src.intensity,
128 range: 0.0,
129 inner_angle: 0.0,
130 outer_angle: 0.0,
131 _pad_align: 0,
132 spot_direction: [0.0, -1.0, 0.0],
133 _pad: [0.0; 5],
134 },
135 LightKind::Point { position, range } => SingleLightUniform {
136 light_view_proj: shadow_mat.to_cols_array_2d(),
137 pos_or_dir: *position,
138 light_type: 1,
139 color: src.color,
140 intensity: src.intensity,
141 range: *range,
142 inner_angle: 0.0,
143 outer_angle: 0.0,
144 _pad_align: 0,
145 spot_direction: [0.0, -1.0, 0.0],
146 _pad: [0.0; 5],
147 },
148 LightKind::Spot {
149 position,
150 direction,
151 range,
152 inner_angle,
153 outer_angle,
154 } => SingleLightUniform {
155 light_view_proj: shadow_mat.to_cols_array_2d(),
156 pos_or_dir: *position,
157 light_type: 2,
158 color: src.color,
159 intensity: src.intensity,
160 range: *range,
161 inner_angle: *inner_angle,
162 outer_angle: *outer_angle,
163 _pad_align: 0,
164 spot_direction: *direction,
165 _pad: [0.0; 5],
166 },
167 }
168 }
169
170 let light_count = lighting.lights.len().min(8) as u32;
172 let mut lights_arr = [SingleLightUniform {
173 light_view_proj: glam::Mat4::IDENTITY.to_cols_array_2d(),
174 pos_or_dir: [0.0; 3],
175 light_type: 0,
176 color: [1.0; 3],
177 intensity: 1.0,
178 range: 0.0,
179 inner_angle: 0.0,
180 outer_angle: 0.0,
181 _pad_align: 0,
182 spot_direction: [0.0, -1.0, 0.0],
183 _pad: [0.0; 5],
184 }; 8];
185
186 for (i, src) in lighting.lights.iter().take(8).enumerate() {
187 lights_arr[i] = build_single_light_uniform(src, shadow_center, shadow_extent, i == 0);
188 }
189
190 let cascade_count = lighting.shadow_cascade_count.clamp(1, 4) as usize;
195 let atlas_res = lighting.shadow_atlas_resolution.max(64);
196 let tile_size = atlas_res / 2;
197
198 let cascade_splits = compute_cascade_splits(
199 frame.camera.render_camera.near.max(0.01),
200 frame.camera.render_camera.far.max(1.0),
201 cascade_count as u32,
202 lighting.cascade_split_lambda,
203 );
204
205 let light_dir_for_csm = if light_count > 0 {
206 match &lighting.lights[0].kind {
207 LightKind::Directional { direction } => glam::Vec3::from(*direction).normalize(),
208 LightKind::Point { position, .. } => {
209 (glam::Vec3::from(*position) - shadow_center).normalize()
210 }
211 LightKind::Spot {
212 position,
213 direction,
214 ..
215 } => {
216 let _ = position;
217 glam::Vec3::from(*direction).normalize()
218 }
219 }
220 } else {
221 glam::Vec3::new(0.3, 1.0, 0.5).normalize()
222 };
223
224 let mut cascade_view_projs = [glam::Mat4::IDENTITY; 4];
225 let mut cascade_split_distances = [0.0f32; 4];
227
228 let use_csm = light_count > 0
230 && matches!(lighting.lights[0].kind, LightKind::Directional { .. })
231 && frame.camera.render_camera.view != glam::Mat4::IDENTITY;
232
233 if use_csm {
234 for i in 0..cascade_count {
235 let split_near = if i == 0 {
236 frame.camera.render_camera.near.max(0.01)
237 } else {
238 cascade_splits[i - 1]
239 };
240 let split_far = cascade_splits[i];
241 cascade_view_projs[i] = compute_cascade_matrix(
242 light_dir_for_csm,
243 frame.camera.render_camera.view,
244 frame.camera.render_camera.fov,
245 frame.camera.render_camera.aspect,
246 split_near,
247 split_far,
248 tile_size as f32,
249 );
250 cascade_split_distances[i] = split_far;
251 }
252 } else {
253 let primary_shadow_mat = if light_count > 0 {
255 compute_shadow_matrix(&lighting.lights[0].kind, shadow_center, shadow_extent)
256 } else {
257 glam::Mat4::IDENTITY
258 };
259 cascade_view_projs[0] = primary_shadow_mat;
260 cascade_split_distances[0] = frame.camera.render_camera.far;
261 }
262 let effective_cascade_count = if use_csm { cascade_count } else { 1 };
263
264 let atlas_rects: [[f32; 4]; 8] = [
267 [0.0, 0.0, 0.5, 0.5], [0.5, 0.0, 1.0, 0.5], [0.0, 0.5, 0.5, 1.0], [0.5, 0.5, 1.0, 1.0], [0.0; 4],
272 [0.0; 4],
273 [0.0; 4],
274 [0.0; 4], ];
276
277 {
279 let mut vp_data = [[0.0f32; 4]; 16]; for c in 0..4 {
281 let cols = cascade_view_projs[c].to_cols_array_2d();
282 for row in 0..4 {
283 vp_data[c * 4 + row] = cols[row];
284 }
285 }
286 let shadow_atlas_uniform = ShadowAtlasUniform {
287 cascade_view_proj: vp_data,
288 cascade_splits: cascade_split_distances,
289 cascade_count: effective_cascade_count as u32,
290 atlas_size: atlas_res as f32,
291 shadow_filter: match lighting.shadow_filter {
292 ShadowFilter::Pcf => 0,
293 ShadowFilter::Pcss => 1,
294 },
295 pcss_light_radius: lighting.pcss_light_radius,
296 atlas_rects,
297 };
298 queue.write_buffer(
299 &resources.shadow_info_buf,
300 0,
301 bytemuck::cast_slice(&[shadow_atlas_uniform]),
302 );
303 for slot in &self.viewport_slots {
306 queue.write_buffer(
307 &slot.shadow_info_buf,
308 0,
309 bytemuck::cast_slice(&[shadow_atlas_uniform]),
310 );
311 }
312 }
313
314 let _primary_shadow_mat = cascade_view_projs[0];
317
318 let (ibl_enabled, ibl_intensity, ibl_rotation, show_skybox) =
321 if let Some(env) = scene_fx.environment {
322 if resources.ibl_irradiance_view.is_some() {
323 (
324 1u32,
325 env.intensity,
326 env.rotation,
327 if env.show_skybox { 1u32 } else { 0 },
328 )
329 } else {
330 (0, 0.0, 0.0, 0)
331 }
332 } else {
333 (0, 0.0, 0.0, 0)
334 };
335
336 let lights_uniform = LightsUniform {
337 count: light_count,
338 shadow_bias: lighting.shadow_bias,
339 shadows_enabled: if lighting.shadows_enabled { 1 } else { 0 },
340 _pad: 0,
341 sky_color: lighting.sky_color,
342 hemisphere_intensity: lighting.hemisphere_intensity,
343 ground_color: lighting.ground_color,
344 _pad2: 0.0,
345 lights: lights_arr,
346 ibl_enabled,
347 ibl_intensity,
348 ibl_rotation,
349 show_skybox,
350 };
351 queue.write_buffer(
352 &resources.light_uniform_buf,
353 0,
354 bytemuck::cast_slice(&[lights_uniform]),
355 );
356
357 const SHADOW_SLOT_STRIDE: u64 = 256;
361 for c in 0..4usize {
362 queue.write_buffer(
363 &resources.shadow_uniform_buf,
364 c as u64 * SHADOW_SLOT_STRIDE,
365 bytemuck::cast_slice(&cascade_view_projs[c].to_cols_array_2d()),
366 );
367 }
368
369 let visible_count = scene_items.iter().filter(|i| i.visible).count();
372 let prev_use_instancing = self.use_instancing;
373 self.use_instancing = visible_count > INSTANCING_THRESHOLD;
374
375 if self.use_instancing != prev_use_instancing {
378 self.instanced_batches.clear();
379 self.last_scene_generation = u64::MAX;
380 self.last_scene_items_count = usize::MAX;
381 }
382
383 let has_scalar_items = scene_items.iter().any(|i| i.active_attribute.is_some());
387 let has_two_sided_items = scene_items.iter().any(|i| i.two_sided);
388 if !self.use_instancing
389 || frame.viewport.wireframe_mode
390 || has_scalar_items
391 || has_two_sided_items
392 {
393 for item in scene_items {
394 if resources
395 .mesh_store
396 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
397 .is_none()
398 {
399 tracing::warn!(
400 mesh_index = item.mesh_index,
401 "scene item mesh_index invalid, skipping"
402 );
403 continue;
404 };
405 let m = &item.material;
406 let (has_attr, s_min, s_max) = if let Some(attr_ref) = &item.active_attribute {
408 let range = item
409 .scalar_range
410 .or_else(|| {
411 resources
412 .mesh_store
413 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
414 .and_then(|mesh| mesh.attribute_ranges.get(&attr_ref.name).copied())
415 })
416 .unwrap_or((0.0, 1.0));
417 (1u32, range.0, range.1)
418 } else {
419 (0u32, 0.0, 1.0)
420 };
421 let obj_uniform = ObjectUniform {
422 model: item.model,
423 color: [m.base_color[0], m.base_color[1], m.base_color[2], m.opacity],
424 selected: if item.selected { 1 } else { 0 },
425 wireframe: if frame.viewport.wireframe_mode { 1 } else { 0 },
426 ambient: m.ambient,
427 diffuse: m.diffuse,
428 specular: m.specular,
429 shininess: m.shininess,
430 has_texture: if m.texture_id.is_some() { 1 } else { 0 },
431 use_pbr: if m.use_pbr { 1 } else { 0 },
432 metallic: m.metallic,
433 roughness: m.roughness,
434 has_normal_map: if m.normal_map_id.is_some() { 1 } else { 0 },
435 has_ao_map: if m.ao_map_id.is_some() { 1 } else { 0 },
436 has_attribute: has_attr,
437 scalar_min: s_min,
438 scalar_max: s_max,
439 _pad_scalar: 0,
440 nan_color: item.nan_color.unwrap_or([0.0; 4]),
441 use_nan_color: if item.nan_color.is_some() { 1 } else { 0 },
442 _pad_nan: [0; 3],
443 };
444
445 let normal_obj_uniform = ObjectUniform {
446 model: item.model,
447 color: [1.0, 1.0, 1.0, 1.0],
448 selected: 0,
449 wireframe: 0,
450 ambient: 0.15,
451 diffuse: 0.75,
452 specular: 0.4,
453 shininess: 32.0,
454 has_texture: 0,
455 use_pbr: 0,
456 metallic: 0.0,
457 roughness: 0.5,
458 has_normal_map: 0,
459 has_ao_map: 0,
460 has_attribute: 0,
461 scalar_min: 0.0,
462 scalar_max: 1.0,
463 _pad_scalar: 0,
464 nan_color: [0.0; 4],
465 use_nan_color: 0,
466 _pad_nan: [0; 3],
467 };
468
469 {
471 let mesh = resources
472 .mesh_store
473 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
474 .unwrap();
475 queue.write_buffer(
476 &mesh.object_uniform_buf,
477 0,
478 bytemuck::cast_slice(&[obj_uniform]),
479 );
480 queue.write_buffer(
481 &mesh.normal_uniform_buf,
482 0,
483 bytemuck::cast_slice(&[normal_obj_uniform]),
484 );
485 } resources.update_mesh_texture_bind_group(
489 device,
490 item.mesh_index,
491 item.material.texture_id,
492 item.material.normal_map_id,
493 item.material.ao_map_id,
494 item.colormap_id,
495 item.active_attribute.as_ref().map(|a| a.name.as_str()),
496 );
497 }
498 }
499
500 if self.use_instancing {
501 resources.ensure_instanced_pipelines(device);
502
503 let cache_valid = frame.scene.generation == self.last_scene_generation
508 && frame.interaction.selection_generation == self.last_selection_generation
509 && scene_items.len() == self.last_scene_items_count;
510
511 if !cache_valid {
512 let mut sorted_items: Vec<&SceneRenderItem> = scene_items
514 .iter()
515 .filter(|item| {
516 item.visible
517 && item.active_attribute.is_none()
518 && !item.two_sided
519 && resources
520 .mesh_store
521 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
522 .is_some()
523 })
524 .collect();
525
526 sorted_items.sort_unstable_by_key(|item| {
527 (
528 item.mesh_index,
529 item.material.texture_id,
530 item.material.normal_map_id,
531 item.material.ao_map_id,
532 )
533 });
534
535 let mut all_instances: Vec<InstanceData> = Vec::with_capacity(sorted_items.len());
536 let mut instanced_batches: Vec<InstancedBatch> = Vec::new();
537
538 if !sorted_items.is_empty() {
539 let mut batch_start = 0usize;
540 for i in 1..=sorted_items.len() {
541 let at_end = i == sorted_items.len();
542 let key_changed = !at_end && {
543 let a = sorted_items[batch_start];
544 let b = sorted_items[i];
545 a.mesh_index != b.mesh_index
546 || a.material.texture_id != b.material.texture_id
547 || a.material.normal_map_id != b.material.normal_map_id
548 || a.material.ao_map_id != b.material.ao_map_id
549 };
550
551 if at_end || key_changed {
552 let batch_items = &sorted_items[batch_start..i];
553 let rep = batch_items[0];
554 let instance_offset = all_instances.len() as u32;
555 let is_transparent = rep.material.opacity < 1.0;
556
557 for item in batch_items {
558 let m = &item.material;
559 all_instances.push(InstanceData {
560 model: item.model,
561 color: [
562 m.base_color[0],
563 m.base_color[1],
564 m.base_color[2],
565 m.opacity,
566 ],
567 selected: if item.selected { 1 } else { 0 },
568 wireframe: 0, ambient: m.ambient,
570 diffuse: m.diffuse,
571 specular: m.specular,
572 shininess: m.shininess,
573 has_texture: if m.texture_id.is_some() { 1 } else { 0 },
574 use_pbr: if m.use_pbr { 1 } else { 0 },
575 metallic: m.metallic,
576 roughness: m.roughness,
577 has_normal_map: if m.normal_map_id.is_some() { 1 } else { 0 },
578 has_ao_map: if m.ao_map_id.is_some() { 1 } else { 0 },
579 });
580 }
581
582 instanced_batches.push(InstancedBatch {
583 mesh_index: rep.mesh_index,
584 texture_id: rep.material.texture_id,
585 normal_map_id: rep.material.normal_map_id,
586 ao_map_id: rep.material.ao_map_id,
587 instance_offset,
588 instance_count: batch_items.len() as u32,
589 is_transparent,
590 });
591
592 batch_start = i;
593 }
594 }
595 }
596
597 self.cached_instance_data = all_instances;
598 self.cached_instanced_batches = instanced_batches;
599
600 resources.upload_instance_data(device, queue, &self.cached_instance_data);
601
602 self.instanced_batches = self.cached_instanced_batches.clone();
603
604 self.last_scene_generation = frame.scene.generation;
605 self.last_selection_generation = frame.interaction.selection_generation;
606 self.last_scene_items_count = scene_items.len();
607
608 for batch in &self.instanced_batches {
609 resources.get_instance_bind_group(
610 device,
611 batch.texture_id,
612 batch.normal_map_id,
613 batch.ao_map_id,
614 );
615 }
616 } else {
617 for batch in &self.instanced_batches {
618 resources.get_instance_bind_group(
619 device,
620 batch.texture_id,
621 batch.normal_map_id,
622 batch.ao_map_id,
623 );
624 }
625 }
626 }
627
628 self.point_cloud_gpu_data.clear();
632 if !frame.scene.point_clouds.is_empty() {
633 resources.ensure_point_cloud_pipeline(device);
634 for item in &frame.scene.point_clouds {
635 if item.positions.is_empty() {
636 continue;
637 }
638 let gpu_data = resources.upload_point_cloud(device, queue, item);
639 self.point_cloud_gpu_data.push(gpu_data);
640 }
641 }
642
643 self.glyph_gpu_data.clear();
644 if !frame.scene.glyphs.is_empty() {
645 resources.ensure_glyph_pipeline(device);
646 for item in &frame.scene.glyphs {
647 if item.positions.is_empty() || item.vectors.is_empty() {
648 continue;
649 }
650 let gpu_data = resources.upload_glyph_set(device, queue, item);
651 self.glyph_gpu_data.push(gpu_data);
652 }
653 }
654
655 self.polyline_gpu_data.clear();
659 let vp_size = frame.camera.viewport_size;
660 if !frame.scene.polylines.is_empty() {
661 resources.ensure_polyline_pipeline(device);
662 for item in &frame.scene.polylines {
663 if item.positions.is_empty() {
664 continue;
665 }
666 let gpu_data = resources.upload_polyline(device, queue, item, vp_size);
667 self.polyline_gpu_data.push(gpu_data);
668 }
669 }
670
671 if !frame.scene.isolines.is_empty() {
675 resources.ensure_polyline_pipeline(device);
676 for item in &frame.scene.isolines {
677 if item.positions.is_empty() || item.indices.is_empty() || item.scalars.is_empty() {
678 continue;
679 }
680 let (positions, strip_lengths) = crate::geometry::isoline::extract_isolines(item);
681 if positions.is_empty() {
682 continue;
683 }
684 let polyline = PolylineItem {
685 positions,
686 scalars: Vec::new(),
687 strip_lengths,
688 scalar_range: None,
689 colormap_id: None,
690 default_color: item.color,
691 line_width: item.line_width,
692 id: 0,
693 };
694 let gpu_data = resources.upload_polyline(device, queue, &polyline, vp_size);
695 self.polyline_gpu_data.push(gpu_data);
696 }
697 }
698
699 self.streamtube_gpu_data.clear();
703 if !frame.scene.streamtube_items.is_empty() {
704 resources.ensure_streamtube_pipeline(device);
705 for item in &frame.scene.streamtube_items {
706 if item.positions.is_empty() || item.strip_lengths.is_empty() {
707 continue;
708 }
709 let gpu_data = resources.upload_streamtube(device, queue, item);
710 if gpu_data.index_count > 0 {
711 self.streamtube_gpu_data.push(gpu_data);
712 }
713 }
714 }
715
716 self.volume_gpu_data.clear();
722 if !frame.scene.volumes.is_empty() {
723 resources.ensure_volume_pipeline(device);
724 for item in &frame.scene.volumes {
725 let gpu =
726 resources.upload_volume_frame(device, queue, item, &frame.effects.clip_planes);
727 self.volume_gpu_data.push(gpu);
728 }
729 }
730
731 {
733 let total = scene_items.len() as u32;
734 let visible = scene_items.iter().filter(|i| i.visible).count() as u32;
735 let mut draw_calls = 0u32;
736 let mut triangles = 0u64;
737 let instanced_batch_count = if self.use_instancing {
738 self.instanced_batches.len() as u32
739 } else {
740 0
741 };
742
743 if self.use_instancing {
744 for batch in &self.instanced_batches {
745 if let Some(mesh) = resources
746 .mesh_store
747 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
748 {
749 draw_calls += 1;
750 triangles += (mesh.index_count / 3) as u64 * batch.instance_count as u64;
751 }
752 }
753 } else {
754 for item in scene_items {
755 if !item.visible {
756 continue;
757 }
758 if let Some(mesh) = resources
759 .mesh_store
760 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
761 {
762 draw_calls += 1;
763 triangles += (mesh.index_count / 3) as u64;
764 }
765 }
766 }
767
768 self.last_stats = crate::renderer::stats::FrameStats {
769 total_objects: total,
770 visible_objects: visible,
771 culled_objects: total.saturating_sub(visible),
772 draw_calls,
773 instanced_batches: instanced_batch_count,
774 triangles_submitted: triangles,
775 shadow_draw_calls: 0, };
777 }
778
779 if lighting.shadows_enabled && !scene_items.is_empty() {
783 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
784 label: Some("shadow_pass_encoder"),
785 });
786 {
787 let mut shadow_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
788 label: Some("shadow_pass"),
789 color_attachments: &[],
790 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
791 view: &resources.shadow_map_view,
792 depth_ops: Some(wgpu::Operations {
793 load: wgpu::LoadOp::Clear(1.0),
794 store: wgpu::StoreOp::Store,
795 }),
796 stencil_ops: None,
797 }),
798 timestamp_writes: None,
799 occlusion_query_set: None,
800 });
801
802 let mut shadow_draws = 0u32;
803 let tile_px = tile_size as f32;
804
805 if self.use_instancing {
806 if let (Some(pipeline), Some(instance_bg)) = (
807 &resources.shadow_instanced_pipeline,
808 self.instanced_batches.first().and_then(|b| {
809 resources.instance_bind_groups.get(&(
810 b.texture_id.unwrap_or(u64::MAX),
811 b.normal_map_id.unwrap_or(u64::MAX),
812 b.ao_map_id.unwrap_or(u64::MAX),
813 ))
814 }),
815 ) {
816 for cascade in 0..effective_cascade_count {
817 let tile_col = (cascade % 2) as f32;
818 let tile_row = (cascade / 2) as f32;
819 shadow_pass.set_viewport(
820 tile_col * tile_px,
821 tile_row * tile_px,
822 tile_px,
823 tile_px,
824 0.0,
825 1.0,
826 );
827 shadow_pass.set_scissor_rect(
828 (tile_col * tile_px) as u32,
829 (tile_row * tile_px) as u32,
830 tile_size,
831 tile_size,
832 );
833
834 shadow_pass.set_pipeline(pipeline);
835
836 queue.write_buffer(
837 resources.shadow_instanced_cascade_bufs[cascade]
838 .as_ref()
839 .expect("shadow_instanced_cascade_bufs not allocated"),
840 0,
841 bytemuck::cast_slice(
842 &cascade_view_projs[cascade].to_cols_array_2d(),
843 ),
844 );
845
846 let cascade_bg = resources.shadow_instanced_cascade_bgs[cascade]
847 .as_ref()
848 .expect("shadow_instanced_cascade_bgs not allocated");
849 shadow_pass.set_bind_group(0, cascade_bg, &[]);
850 shadow_pass.set_bind_group(1, instance_bg, &[]);
851
852 for batch in &self.instanced_batches {
853 if batch.is_transparent {
854 continue;
855 }
856 let Some(mesh) = resources
857 .mesh_store
858 .get(crate::resources::mesh_store::MeshId(batch.mesh_index))
859 else {
860 continue;
861 };
862 shadow_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
863 shadow_pass.set_index_buffer(
864 mesh.index_buffer.slice(..),
865 wgpu::IndexFormat::Uint32,
866 );
867 shadow_pass.draw_indexed(
868 0..mesh.index_count,
869 0,
870 batch.instance_offset
871 ..batch.instance_offset + batch.instance_count,
872 );
873 shadow_draws += 1;
874 }
875 }
876 }
877 } else {
878 for cascade in 0..effective_cascade_count {
879 let tile_col = (cascade % 2) as f32;
880 let tile_row = (cascade / 2) as f32;
881 shadow_pass.set_viewport(
882 tile_col * tile_px,
883 tile_row * tile_px,
884 tile_px,
885 tile_px,
886 0.0,
887 1.0,
888 );
889 shadow_pass.set_scissor_rect(
890 (tile_col * tile_px) as u32,
891 (tile_row * tile_px) as u32,
892 tile_size,
893 tile_size,
894 );
895
896 shadow_pass.set_pipeline(&resources.shadow_pipeline);
897 shadow_pass.set_bind_group(
898 0,
899 &resources.shadow_bind_group,
900 &[cascade as u32 * 256],
901 );
902
903 let cascade_frustum = crate::camera::frustum::Frustum::from_view_proj(
904 &cascade_view_projs[cascade],
905 );
906
907 for item in scene_items.iter() {
908 if !item.visible {
909 continue;
910 }
911 if item.material.opacity < 1.0 {
912 continue;
913 }
914 let Some(mesh) = resources
915 .mesh_store
916 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
917 else {
918 continue;
919 };
920
921 let world_aabb = mesh
922 .aabb
923 .transformed(&glam::Mat4::from_cols_array_2d(&item.model));
924 if cascade_frustum.cull_aabb(&world_aabb) {
925 continue;
926 }
927
928 shadow_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
929 shadow_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
930 shadow_pass.set_index_buffer(
931 mesh.index_buffer.slice(..),
932 wgpu::IndexFormat::Uint32,
933 );
934 shadow_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
935 shadow_draws += 1;
936 }
937 }
938 }
939 drop(shadow_pass);
940 self.last_stats.shadow_draw_calls = shadow_draws;
941 }
942 queue.submit(std::iter::once(encoder.finish()));
943 }
944 }
945
946 pub(super) fn prepare_viewport_internal(
951 &mut self,
952 device: &wgpu::Device,
953 queue: &wgpu::Queue,
954 frame: &FrameData,
955 viewport_fx: &ViewportEffects<'_>,
956 ) {
957 self.ensure_viewport_slot(device, frame.camera.viewport_index);
960
961 let scene_items: &[SceneRenderItem] = match &frame.scene.surfaces {
962 SurfaceSubmission::Flat(items) => items,
963 };
964
965 {
966 let resources = &mut self.resources;
967
968 {
970 let mut planes = [[0.0f32; 4]; 6];
971 let mut count = 0u32;
972 for plane in viewport_fx.clip_planes.iter().filter(|p| p.enabled).take(6) {
973 planes[count as usize] = [
974 plane.normal[0],
975 plane.normal[1],
976 plane.normal[2],
977 plane.distance,
978 ];
979 count += 1;
980 }
981 let clip_uniform = ClipPlanesUniform {
982 planes,
983 count,
984 _pad0: 0,
985 viewport_width: frame.camera.viewport_size[0].max(1.0),
986 viewport_height: frame.camera.viewport_size[1].max(1.0),
987 };
988 if let Some(slot) = self.viewport_slots.get(frame.camera.viewport_index) {
990 queue.write_buffer(
991 &slot.clip_planes_buf,
992 0,
993 bytemuck::cast_slice(&[clip_uniform]),
994 );
995 }
996 queue.write_buffer(
998 &resources.clip_planes_uniform_buf,
999 0,
1000 bytemuck::cast_slice(&[clip_uniform]),
1001 );
1002 }
1003
1004 {
1006 let clip_vol_uniform = ClipVolumeUniform::from_clip_volume(viewport_fx.clip_volume);
1007 if let Some(slot) = self.viewport_slots.get(frame.camera.viewport_index) {
1008 queue.write_buffer(
1009 &slot.clip_volume_buf,
1010 0,
1011 bytemuck::cast_slice(&[clip_vol_uniform]),
1012 );
1013 }
1014 queue.write_buffer(
1015 &resources.clip_volume_uniform_buf,
1016 0,
1017 bytemuck::cast_slice(&[clip_vol_uniform]),
1018 );
1019 }
1020
1021 let camera_uniform = frame.camera.render_camera.camera_uniform();
1023 queue.write_buffer(
1025 &resources.camera_uniform_buf,
1026 0,
1027 bytemuck::cast_slice(&[camera_uniform]),
1028 );
1029 if let Some(slot) = self.viewport_slots.get(frame.camera.viewport_index) {
1031 queue.write_buffer(&slot.camera_buf, 0, bytemuck::cast_slice(&[camera_uniform]));
1032 }
1033
1034 if frame.viewport.show_grid {
1036 let eye = glam::Vec3::from(frame.camera.render_camera.eye_position);
1037 if !eye.is_finite() {
1038 tracing::warn!(
1039 eye_x = eye.x,
1040 eye_y = eye.y,
1041 eye_z = eye.z,
1042 "grid skipped: eye_position is non-finite (camera distance overflow?)"
1043 );
1044 } else {
1045 let view_proj_mat = frame.camera.render_camera.view_proj().to_cols_array_2d();
1046
1047 let (spacing, minor_fade) = if frame.viewport.grid_cell_size > 0.0 {
1048 (frame.viewport.grid_cell_size, 1.0_f32)
1049 } else {
1050 let vertical_depth = (eye.z - frame.viewport.grid_z).abs().max(1.0);
1051 let world_per_pixel =
1052 2.0 * (frame.camera.render_camera.fov / 2.0).tan() * vertical_depth
1053 / frame.camera.viewport_size[1].max(1.0);
1054 let target = (world_per_pixel * 60.0).max(1e-9_f32);
1055 let mut s = 1.0_f32;
1056 let mut iters = 0u32;
1057 while s < target {
1058 s *= 10.0;
1059 iters += 1;
1060 }
1061 let ratio = (target / s).clamp(0.0, 1.0);
1062 let fade = if ratio < 0.5 {
1063 1.0_f32
1064 } else {
1065 let t = (ratio - 0.5) * 2.0;
1066 1.0 - t * t * (3.0 - 2.0 * t)
1067 };
1068 tracing::debug!(
1069 eye_z = eye.z,
1070 vertical_depth,
1071 world_per_pixel,
1072 target,
1073 spacing = s,
1074 lod_iters = iters,
1075 ratio,
1076 minor_fade = fade,
1077 "grid LOD"
1078 );
1079 (s, fade)
1080 };
1081
1082 let spacing_major = spacing * 10.0;
1083 let snap_x = (eye.x / spacing_major).floor() * spacing_major;
1084 let snap_y = (eye.y / spacing_major).floor() * spacing_major;
1085 tracing::debug!(
1086 spacing_minor = spacing,
1087 spacing_major,
1088 snap_x,
1089 snap_y,
1090 eye_x = eye.x,
1091 eye_y = eye.y,
1092 eye_z = eye.z,
1093 "grid snap"
1094 );
1095
1096 let orient = frame.camera.render_camera.orientation;
1097 let right = orient * glam::Vec3::X;
1098 let up = orient * glam::Vec3::Y;
1099 let back = orient * glam::Vec3::Z;
1100 let cam_to_world = [
1101 [right.x, right.y, right.z, 0.0_f32],
1102 [up.x, up.y, up.z, 0.0_f32],
1103 [back.x, back.y, back.z, 0.0_f32],
1104 ];
1105 let aspect =
1106 frame.camera.viewport_size[0] / frame.camera.viewport_size[1].max(1.0);
1107 let tan_half_fov = (frame.camera.render_camera.fov / 2.0).tan();
1108
1109 let uniform = GridUniform {
1110 view_proj: view_proj_mat,
1111 cam_to_world,
1112 tan_half_fov,
1113 aspect,
1114 _pad_ivp: [0.0; 2],
1115 eye_pos: frame.camera.render_camera.eye_position,
1116 grid_z: frame.viewport.grid_z,
1117 spacing_minor: spacing,
1118 spacing_major,
1119 snap_origin: [snap_x, snap_y],
1120 color_minor: [0.35, 0.35, 0.35, 0.4 * minor_fade],
1121 color_major: [0.40, 0.40, 0.40, 0.4 + 0.2 * minor_fade],
1122 };
1123 if let Some(slot) = self.viewport_slots.get(frame.camera.viewport_index) {
1125 queue.write_buffer(&slot.grid_buf, 0, bytemuck::cast_slice(&[uniform]));
1126 }
1127 queue.write_buffer(
1129 &resources.grid_uniform_buf,
1130 0,
1131 bytemuck::cast_slice(&[uniform]),
1132 );
1133 }
1134 }
1135 } let vp_idx = frame.camera.viewport_index;
1144
1145 let mut outline_object_buffers: Vec<OutlineObjectBuffers> = Vec::new();
1147 if frame.interaction.outline_selected {
1148 let resources = &self.resources;
1149 for item in scene_items {
1150 if !item.visible || !item.selected {
1151 continue;
1152 }
1153 let m = &item.material;
1154 let stencil_uniform = ObjectUniform {
1155 model: item.model,
1156 color: [m.base_color[0], m.base_color[1], m.base_color[2], m.opacity],
1157 selected: 1,
1158 wireframe: 0,
1159 ambient: m.ambient,
1160 diffuse: m.diffuse,
1161 specular: m.specular,
1162 shininess: m.shininess,
1163 has_texture: if m.texture_id.is_some() { 1 } else { 0 },
1164 use_pbr: if m.use_pbr { 1 } else { 0 },
1165 metallic: m.metallic,
1166 roughness: m.roughness,
1167 has_normal_map: if m.normal_map_id.is_some() { 1 } else { 0 },
1168 has_ao_map: if m.ao_map_id.is_some() { 1 } else { 0 },
1169 has_attribute: 0,
1170 scalar_min: 0.0,
1171 scalar_max: 1.0,
1172 _pad_scalar: 0,
1173 nan_color: [0.0; 4],
1174 use_nan_color: 0,
1175 _pad_nan: [0; 3],
1176 };
1177 let stencil_buf = device.create_buffer(&wgpu::BufferDescriptor {
1178 label: Some("outline_stencil_object_uniform_buf"),
1179 size: std::mem::size_of::<ObjectUniform>() as u64,
1180 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1181 mapped_at_creation: false,
1182 });
1183 queue.write_buffer(&stencil_buf, 0, bytemuck::cast_slice(&[stencil_uniform]));
1184
1185 let albedo_view = match m.texture_id {
1186 Some(id) if (id as usize) < resources.textures.len() => {
1187 &resources.textures[id as usize].view
1188 }
1189 _ => &resources.fallback_texture.view,
1190 };
1191 let normal_view = match m.normal_map_id {
1192 Some(id) if (id as usize) < resources.textures.len() => {
1193 &resources.textures[id as usize].view
1194 }
1195 _ => &resources.fallback_normal_map_view,
1196 };
1197 let ao_view = match m.ao_map_id {
1198 Some(id) if (id as usize) < resources.textures.len() => {
1199 &resources.textures[id as usize].view
1200 }
1201 _ => &resources.fallback_ao_map_view,
1202 };
1203 let stencil_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
1204 label: Some("outline_stencil_object_bg"),
1205 layout: &resources.object_bind_group_layout,
1206 entries: &[
1207 wgpu::BindGroupEntry {
1208 binding: 0,
1209 resource: stencil_buf.as_entire_binding(),
1210 },
1211 wgpu::BindGroupEntry {
1212 binding: 1,
1213 resource: wgpu::BindingResource::TextureView(albedo_view),
1214 },
1215 wgpu::BindGroupEntry {
1216 binding: 2,
1217 resource: wgpu::BindingResource::Sampler(&resources.material_sampler),
1218 },
1219 wgpu::BindGroupEntry {
1220 binding: 3,
1221 resource: wgpu::BindingResource::TextureView(normal_view),
1222 },
1223 wgpu::BindGroupEntry {
1224 binding: 4,
1225 resource: wgpu::BindingResource::TextureView(ao_view),
1226 },
1227 wgpu::BindGroupEntry {
1228 binding: 5,
1229 resource: wgpu::BindingResource::TextureView(
1230 &resources.fallback_lut_view,
1231 ),
1232 },
1233 wgpu::BindGroupEntry {
1234 binding: 6,
1235 resource: resources.fallback_scalar_buf.as_entire_binding(),
1236 },
1237 ],
1238 });
1239
1240 let uniform = OutlineUniform {
1241 model: item.model,
1242 color: frame.interaction.outline_color,
1243 pixel_offset: frame.interaction.outline_width_px,
1244 _pad: [0.0; 3],
1245 };
1246 let buf = device.create_buffer(&wgpu::BufferDescriptor {
1247 label: Some("outline_uniform_buf"),
1248 size: std::mem::size_of::<OutlineUniform>() as u64,
1249 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1250 mapped_at_creation: false,
1251 });
1252 queue.write_buffer(&buf, 0, bytemuck::cast_slice(&[uniform]));
1253 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
1254 label: Some("outline_object_bg"),
1255 layout: &resources.outline_bind_group_layout,
1256 entries: &[wgpu::BindGroupEntry {
1257 binding: 0,
1258 resource: buf.as_entire_binding(),
1259 }],
1260 });
1261 outline_object_buffers.push(OutlineObjectBuffers {
1262 mesh_index: item.mesh_index,
1263 two_sided: item.two_sided,
1264 _stencil_uniform_buf: stencil_buf,
1265 stencil_bind_group: stencil_bg,
1266 _outline_uniform_buf: buf,
1267 outline_bind_group: bg,
1268 });
1269 }
1270 }
1271
1272 let mut xray_object_buffers: Vec<(usize, wgpu::Buffer, wgpu::BindGroup)> = Vec::new();
1274 if frame.interaction.xray_selected {
1275 let resources = &self.resources;
1276 for item in scene_items {
1277 if !item.visible || !item.selected {
1278 continue;
1279 }
1280 let uniform = OutlineUniform {
1281 model: item.model,
1282 color: frame.interaction.xray_color,
1283 pixel_offset: 0.0,
1284 _pad: [0.0; 3],
1285 };
1286 let buf = device.create_buffer(&wgpu::BufferDescriptor {
1287 label: Some("xray_uniform_buf"),
1288 size: std::mem::size_of::<OutlineUniform>() as u64,
1289 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1290 mapped_at_creation: false,
1291 });
1292 queue.write_buffer(&buf, 0, bytemuck::cast_slice(&[uniform]));
1293 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
1294 label: Some("xray_object_bg"),
1295 layout: &resources.outline_bind_group_layout,
1296 entries: &[wgpu::BindGroupEntry {
1297 binding: 0,
1298 resource: buf.as_entire_binding(),
1299 }],
1300 });
1301 xray_object_buffers.push((item.mesh_index, buf, bg));
1302 }
1303 }
1304
1305 let mut constraint_line_buffers = Vec::new();
1307 for overlay in &frame.interaction.constraint_overlays {
1308 constraint_line_buffers.push(self.resources.create_constraint_overlay(device, overlay));
1309 }
1310
1311 let mut clip_plane_fill_buffers = Vec::new();
1313 let mut clip_plane_line_buffers = Vec::new();
1314 for overlay in &frame.interaction.clip_plane_overlays {
1315 clip_plane_fill_buffers.push(
1316 self.resources
1317 .create_clip_plane_fill_overlay(device, overlay),
1318 );
1319 clip_plane_line_buffers.push(
1320 self.resources
1321 .create_clip_plane_line_overlay(device, overlay),
1322 );
1323 }
1324
1325 let mut cap_buffers = Vec::new();
1327 if viewport_fx.cap_fill_enabled {
1328 let active_planes: Vec<_> = viewport_fx
1329 .clip_planes
1330 .iter()
1331 .filter(|p| p.enabled)
1332 .collect();
1333 for plane in &active_planes {
1334 let plane_n = glam::Vec3::from(plane.normal);
1335 for item in scene_items.iter().filter(|i| i.visible) {
1336 let Some(mesh) = self
1337 .resources
1338 .mesh_store
1339 .get(crate::resources::mesh_store::MeshId(item.mesh_index))
1340 else {
1341 continue;
1342 };
1343 let model = glam::Mat4::from_cols_array_2d(&item.model);
1344 let world_aabb = mesh.aabb.transformed(&model);
1345 if !world_aabb.intersects_plane(plane_n, plane.distance) {
1346 continue;
1347 }
1348 let (Some(pos), Some(idx)) = (&mesh.cpu_positions, &mesh.cpu_indices) else {
1349 continue;
1350 };
1351 if let Some(cap) = crate::geometry::cap_geometry::generate_cap_mesh(
1352 pos,
1353 idx,
1354 &model,
1355 plane_n,
1356 plane.distance,
1357 ) {
1358 let bc = item.material.base_color;
1359 let color = plane.cap_color.unwrap_or([bc[0], bc[1], bc[2], 1.0]);
1360 let buf = self.resources.upload_cap_geometry(device, &cap, color);
1361 cap_buffers.push(buf);
1362 }
1363 }
1364 }
1365 }
1366
1367 let axes_verts = if frame.viewport.show_axes_indicator
1369 && frame.camera.viewport_size[0] > 0.0
1370 && frame.camera.viewport_size[1] > 0.0
1371 {
1372 let verts = crate::widgets::axes_indicator::build_axes_geometry(
1373 frame.camera.viewport_size[0],
1374 frame.camera.viewport_size[1],
1375 frame.camera.render_camera.orientation,
1376 );
1377 if verts.is_empty() { None } else { Some(verts) }
1378 } else {
1379 None
1380 };
1381
1382 let gizmo_update = frame.interaction.gizmo_model.map(|model| {
1384 let (verts, indices) = crate::interaction::gizmo::build_gizmo_mesh(
1385 frame.interaction.gizmo_mode,
1386 frame.interaction.gizmo_hovered,
1387 frame.interaction.gizmo_space_orientation,
1388 );
1389 (verts, indices, model)
1390 });
1391
1392 {
1396 let slot = &mut self.viewport_slots[vp_idx];
1397 slot.outline_object_buffers = outline_object_buffers;
1398 slot.xray_object_buffers = xray_object_buffers;
1399 slot.constraint_line_buffers = constraint_line_buffers;
1400 slot.clip_plane_fill_buffers = clip_plane_fill_buffers;
1401 slot.clip_plane_line_buffers = clip_plane_line_buffers;
1402 slot.cap_buffers = cap_buffers;
1403
1404 if let Some(verts) = axes_verts {
1406 let byte_size = std::mem::size_of_val(verts.as_slice()) as u64;
1407 if byte_size > slot.axes_vertex_buffer.size() {
1408 slot.axes_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1409 label: Some("vp_axes_vertex_buf"),
1410 size: byte_size,
1411 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1412 mapped_at_creation: false,
1413 });
1414 }
1415 queue.write_buffer(&slot.axes_vertex_buffer, 0, bytemuck::cast_slice(&verts));
1416 slot.axes_vertex_count = verts.len() as u32;
1417 } else {
1418 slot.axes_vertex_count = 0;
1419 }
1420
1421 if let Some((verts, indices, model)) = gizmo_update {
1423 let vert_bytes: &[u8] = bytemuck::cast_slice(&verts);
1424 let idx_bytes: &[u8] = bytemuck::cast_slice(&indices);
1425 if vert_bytes.len() as u64 > slot.gizmo_vertex_buffer.size() {
1426 slot.gizmo_vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1427 label: Some("vp_gizmo_vertex_buf"),
1428 size: vert_bytes.len() as u64,
1429 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1430 mapped_at_creation: false,
1431 });
1432 }
1433 if idx_bytes.len() as u64 > slot.gizmo_index_buffer.size() {
1434 slot.gizmo_index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
1435 label: Some("vp_gizmo_index_buf"),
1436 size: idx_bytes.len() as u64,
1437 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
1438 mapped_at_creation: false,
1439 });
1440 }
1441 queue.write_buffer(&slot.gizmo_vertex_buffer, 0, vert_bytes);
1442 queue.write_buffer(&slot.gizmo_index_buffer, 0, idx_bytes);
1443 slot.gizmo_index_count = indices.len() as u32;
1444 let uniform = crate::interaction::gizmo::GizmoUniform {
1445 model: model.to_cols_array_2d(),
1446 };
1447 queue.write_buffer(&slot.gizmo_uniform_buf, 0, bytemuck::cast_slice(&[uniform]));
1448 }
1449 }
1450
1451 if frame.interaction.outline_selected
1458 && !self.viewport_slots[vp_idx]
1459 .outline_object_buffers
1460 .is_empty()
1461 {
1462 let w = frame.camera.viewport_size[0] as u32;
1463 let h = frame.camera.viewport_size[1] as u32;
1464
1465 self.ensure_viewport_hdr(device, queue, vp_idx, w.max(1), h.max(1));
1467
1468 let slot_ref = &self.viewport_slots[vp_idx];
1472 let outlines_ptr = &slot_ref.outline_object_buffers as *const Vec<OutlineObjectBuffers>;
1473 let camera_bg_ptr = &slot_ref.camera_bind_group as *const wgpu::BindGroup;
1474 let slot_hdr = slot_ref.hdr.as_ref().unwrap();
1475 let color_view_ptr = &slot_hdr.outline_color_view as *const wgpu::TextureView;
1476 let depth_view_ptr = &slot_hdr.outline_depth_view as *const wgpu::TextureView;
1477 let (outlines, camera_bg, color_view, depth_view) = unsafe {
1480 (
1481 &*outlines_ptr,
1482 &*camera_bg_ptr,
1483 &*color_view_ptr,
1484 &*depth_view_ptr,
1485 )
1486 };
1487
1488 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1489 label: Some("outline_offscreen_encoder"),
1490 });
1491 {
1492 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1493 label: Some("outline_offscreen_pass"),
1494 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1495 view: color_view,
1496 resolve_target: None,
1497 ops: wgpu::Operations {
1498 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
1499 store: wgpu::StoreOp::Store,
1500 },
1501 depth_slice: None,
1502 })],
1503 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1504 view: depth_view,
1505 depth_ops: Some(wgpu::Operations {
1506 load: wgpu::LoadOp::Clear(1.0),
1507 store: wgpu::StoreOp::Discard,
1508 }),
1509 stencil_ops: Some(wgpu::Operations {
1510 load: wgpu::LoadOp::Clear(0),
1511 store: wgpu::StoreOp::Discard,
1512 }),
1513 }),
1514 timestamp_writes: None,
1515 occlusion_query_set: None,
1516 });
1517
1518 pass.set_stencil_reference(1);
1520 pass.set_bind_group(0, camera_bg, &[]);
1521 for outlined in outlines {
1522 let Some(mesh) = self
1523 .resources
1524 .mesh_store
1525 .get(crate::resources::mesh_store::MeshId(outlined.mesh_index))
1526 else {
1527 continue;
1528 };
1529 let pipeline = if outlined.two_sided {
1530 &self.resources.stencil_write_two_sided_pipeline
1531 } else {
1532 &self.resources.stencil_write_pipeline
1533 };
1534 pass.set_pipeline(pipeline);
1535 pass.set_bind_group(1, &outlined.stencil_bind_group, &[]);
1536 pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
1537 pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1538 pass.draw_indexed(0..mesh.index_count, 0, 0..1);
1539 }
1540
1541 pass.set_pipeline(&self.resources.outline_pipeline);
1543 pass.set_stencil_reference(1);
1544 for outlined in outlines {
1545 let Some(mesh) = self
1546 .resources
1547 .mesh_store
1548 .get(crate::resources::mesh_store::MeshId(outlined.mesh_index))
1549 else {
1550 continue;
1551 };
1552 pass.set_bind_group(1, &outlined.outline_bind_group, &[]);
1553 pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
1554 pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1555 pass.draw_indexed(0..mesh.index_count, 0, 0..1);
1556 }
1557 }
1558 queue.submit(std::iter::once(encoder.finish()));
1559 }
1560 }
1561
1562 pub fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, frame: &FrameData) {
1565 let (scene_fx, viewport_fx) = frame.effects.split();
1566 self.prepare_scene_internal(device, queue, frame, &scene_fx);
1567 self.prepare_viewport_internal(device, queue, frame, &viewport_fx);
1568 }
1569}