viewport_lib/renderer/mod.rs
1//! `ViewportRenderer` — the main entry point for the viewport library.
2//!
3//! Wraps [`ViewportGpuResources`] and provides `prepare()` / `paint()` methods
4//! that take raw `wgpu` types. GUI framework adapters (e.g. the egui
5//! `CallbackTrait` impl in the application crate) delegate to these methods.
6
7#[macro_use]
8mod types;
9mod picking;
10mod prepare;
11mod render;
12pub mod shader_hashes;
13mod shadows;
14pub mod stats;
15
16pub use self::types::{
17 CacheHints, CameraFrame, ClipPlane, ClipVolume, ComputeFilterItem, ComputeFilterKind,
18 EffectsFrame, FilterMode, FrameData, GlyphItem, GlyphType, InteractionFrame, LightKind,
19 LightSource, LightingSettings, OverlayQuad, PointCloudItem, PointRenderMode, PolylineItem,
20 PostProcessSettings, RenderCamera, ScalarBar, ScalarBarAnchor, ScalarBarOrientation,
21 SceneFrame, SceneRenderItem, ShadowFilter, StreamtubeItem, SurfaceSubmission, ToneMapping,
22 ViewportFrame, VolumeItem,
23};
24
25use self::shadows::{compute_cascade_matrix, compute_cascade_splits};
26use self::types::{INSTANCING_THRESHOLD, InstancedBatch};
27use crate::resources::{
28 CameraUniform, ClipPlanesUniform, GridUniform, InstanceData, LightsUniform, ObjectUniform,
29 OutlineObjectBuffers, OutlineUniform, PickInstance, SingleLightUniform,
30 ViewportGpuResources,
31};
32
33/// High-level renderer wrapping all GPU resources and providing framework-agnostic
34/// `prepare()` and `paint()` methods.
35pub struct ViewportRenderer {
36 resources: ViewportGpuResources,
37 /// Instanced batches prepared for the current frame. Empty when using per-object path.
38 instanced_batches: Vec<InstancedBatch>,
39 /// Whether the current frame uses the instanced draw path.
40 use_instancing: bool,
41 /// Performance counters from the last frame.
42 last_stats: crate::renderer::stats::FrameStats,
43 /// Last scene generation seen during prepare(). u64::MAX forces rebuild on first frame.
44 last_scene_generation: u64,
45 /// Last selection generation seen during prepare(). u64::MAX forces rebuild on first frame.
46 last_selection_generation: u64,
47 /// Last wireframe mode seen during prepare(). Batch layout differs between solid and wireframe.
48 last_wireframe_mode: bool,
49 /// Last scene_items count seen during prepare(). usize::MAX forces rebuild on first frame.
50 /// Included in cache key so that frustum-culling changes (different visible set, different
51 /// count) correctly invalidate the instance buffer even when scene_generation is stable.
52 last_scene_items_count: usize,
53 /// Cached instance data from last rebuild (mirrors the GPU buffer contents).
54 cached_instance_data: Vec<InstanceData>,
55 /// Cached instanced batch descriptors from last rebuild.
56 cached_instanced_batches: Vec<InstancedBatch>,
57 /// Per-frame point cloud GPU data, rebuilt in prepare(), consumed in paint().
58 point_cloud_gpu_data: Vec<crate::resources::PointCloudGpuData>,
59 /// Per-frame glyph GPU data, rebuilt in prepare(), consumed in paint().
60 glyph_gpu_data: Vec<crate::resources::GlyphGpuData>,
61 /// Per-frame polyline GPU data, rebuilt in prepare(), consumed in paint().
62 polyline_gpu_data: Vec<crate::resources::PolylineGpuData>,
63 /// Per-frame volume GPU data, rebuilt in prepare(), consumed in paint().
64 volume_gpu_data: Vec<crate::resources::VolumeGpuData>,
65 /// Per-frame streamtube GPU data, rebuilt in prepare(), consumed in paint().
66 streamtube_gpu_data: Vec<crate::resources::StreamtubeGpuData>,
67 /// Per-viewport camera uniform buffers and bind groups.
68 ///
69 /// In single-viewport mode only slot 0 is used (same as the legacy
70 /// `resources.camera_bind_group`). In multi-viewport mode each sub-viewport
71 /// has its own slot so concurrent `prepare` calls don't clobber each other.
72 ///
73 /// The outer Vec is indexed by `FrameData::viewport_index`. Slots are
74 /// grown lazily in `prepare` via `ensure_viewport_camera_slot`.
75 per_viewport_cameras: Vec<(wgpu::Buffer, wgpu::BindGroup)>,
76 /// Phase G — GPU compute filter results from the last `prepare()` call.
77 ///
78 /// Each entry contains a compacted index buffer + count for one filtered mesh.
79 /// Consumed during `paint()` to override the mesh's default index buffer.
80 /// Cleared and rebuilt each frame.
81 compute_filter_results: Vec<crate::resources::ComputeFilterResult>,
82}
83
84impl ViewportRenderer {
85 /// Create a new renderer with default settings (no MSAA).
86 /// Call once at application startup.
87 pub fn new(device: &wgpu::Device, target_format: wgpu::TextureFormat) -> Self {
88 Self::with_sample_count(device, target_format, 1)
89 }
90
91 /// Create a new renderer with the specified MSAA sample count (1, 2, or 4).
92 ///
93 /// When using MSAA (sample_count > 1), the caller must create multisampled
94 /// color and depth textures and use them as render pass attachments with the
95 /// final surface texture as the resolve target.
96 pub fn with_sample_count(
97 device: &wgpu::Device,
98 target_format: wgpu::TextureFormat,
99 sample_count: u32,
100 ) -> Self {
101 Self {
102 resources: ViewportGpuResources::new(device, target_format, sample_count),
103 instanced_batches: Vec::new(),
104 use_instancing: false,
105 last_stats: crate::renderer::stats::FrameStats::default(),
106 last_scene_generation: u64::MAX,
107 last_selection_generation: u64::MAX,
108 last_wireframe_mode: false,
109 last_scene_items_count: usize::MAX,
110 cached_instance_data: Vec::new(),
111 cached_instanced_batches: Vec::new(),
112 point_cloud_gpu_data: Vec::new(),
113 glyph_gpu_data: Vec::new(),
114 polyline_gpu_data: Vec::new(),
115 volume_gpu_data: Vec::new(),
116 streamtube_gpu_data: Vec::new(),
117 per_viewport_cameras: Vec::new(),
118 compute_filter_results: Vec::new(),
119 }
120 }
121
122 /// Access the underlying GPU resources (e.g. for mesh uploads).
123 pub fn resources(&self) -> &ViewportGpuResources {
124 &self.resources
125 }
126
127 /// Performance counters from the last completed frame.
128 pub fn last_frame_stats(&self) -> crate::renderer::stats::FrameStats {
129 self.last_stats
130 }
131
132 /// Mutable access to the underlying GPU resources (e.g. for mesh uploads).
133 pub fn resources_mut(&mut self) -> &mut ViewportGpuResources {
134 &mut self.resources
135 }
136
137 /// Ensure a per-viewport camera slot exists for `viewport_index`.
138 ///
139 /// Creates a new `(Buffer, BindGroup)` pair that mirrors the layout of the
140 /// shared `resources.camera_bind_group` but with an independent camera
141 /// uniform buffer. Slots are created lazily and never destroyed (there are
142 /// at most 4 in the current UI: single, split-h top/bottom, quad × 4).
143 fn ensure_viewport_camera_slot(&mut self, device: &wgpu::Device, viewport_index: usize) {
144 while self.per_viewport_cameras.len() <= viewport_index {
145 let buf = device.create_buffer(&wgpu::BufferDescriptor {
146 label: Some("per_viewport_camera_buf"),
147 size: std::mem::size_of::<crate::resources::CameraUniform>() as u64,
148 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
149 mapped_at_creation: false,
150 });
151 // The camera bind group (group 0) binds seven resources. Only binding 0
152 // (camera uniform) differs per viewport. Bindings 1-6 (shadow map,
153 // shadow sampler, light uniform, clip planes, shadow atlas info,
154 // clip volume) are shared from the primary resources so all viewports
155 // see the same lighting, shadow, and clip state.
156 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
157 label: Some("per_viewport_camera_bg"),
158 layout: &self.resources.camera_bind_group_layout,
159 entries: &[
160 wgpu::BindGroupEntry {
161 binding: 0,
162 resource: buf.as_entire_binding(),
163 },
164 wgpu::BindGroupEntry {
165 binding: 1,
166 resource: wgpu::BindingResource::TextureView(
167 &self.resources.shadow_map_view,
168 ),
169 },
170 wgpu::BindGroupEntry {
171 binding: 2,
172 resource: wgpu::BindingResource::Sampler(&self.resources.shadow_sampler),
173 },
174 wgpu::BindGroupEntry {
175 binding: 3,
176 resource: self.resources.light_uniform_buf.as_entire_binding(),
177 },
178 wgpu::BindGroupEntry {
179 binding: 4,
180 resource: self.resources.clip_planes_uniform_buf.as_entire_binding(),
181 },
182 wgpu::BindGroupEntry {
183 binding: 5,
184 resource: self.resources.shadow_info_buf.as_entire_binding(),
185 },
186 wgpu::BindGroupEntry {
187 binding: 6,
188 resource: self.resources.clip_volume_uniform_buf.as_entire_binding(),
189 },
190 ],
191 });
192 self.per_viewport_cameras.push((buf, bg));
193 }
194 }
195
196 /// Return a reference to the camera bind group for the given viewport slot.
197 ///
198 /// Falls back to `resources.camera_bind_group` if no per-viewport slot
199 /// exists (e.g. in single-viewport mode before the first prepare call).
200 fn viewport_camera_bind_group(&self, viewport_index: usize) -> &wgpu::BindGroup {
201 self.per_viewport_cameras
202 .get(viewport_index)
203 .map(|(_, bg)| bg)
204 .unwrap_or(&self.resources.camera_bind_group)
205 }
206}