Skip to main content

viewport_lib/resources/scivis/
curve_store.rs

1//! Slot-based storage for pre-uploaded curve GPU data.
2//!
3//! Each store holds GPU data produced by the per-frame upload helpers, keyed
4//! by a typed handle. Per-frame ref items (`PolylineRefItem` and friends) name
5//! a store entry by handle and supply per-frame overrides (model matrix, etc.)
6//! instead of resubmitting the geometry every frame.
7
8use crate::resources::{
9    GlyphGpuData, PointCloudGpuData, PolylineGpuData, SpriteGpuData, StreamtubeGpuData,
10    TensorGlyphGpuData,
11};
12
13/// Resident GPU bytes for one uploaded curve entry, summed by
14/// [`CurveStore::allocated_bytes`] and surfaced through
15/// [`DeviceResources::resident_bytes`](crate::resources::DeviceResources::resident_bytes).
16///
17/// Counts the buffers this entry owns. Base meshes shared across glyph and
18/// tensor-glyph batches are borrowed (`&'static`) from a single cached copy, so
19/// they belong to that cache, not to each entry, and are not counted here.
20pub(crate) trait GpuByteSize {
21    fn gpu_bytes(&self) -> u64;
22}
23
24impl GpuByteSize for PolylineGpuData {
25    fn gpu_bytes(&self) -> u64 {
26        self.vertex_buffer.size() + self._uniform_buf.size()
27    }
28}
29
30impl GpuByteSize for StreamtubeGpuData {
31    fn gpu_bytes(&self) -> u64 {
32        self.vertex_buffer.size()
33            + self.index_buffer.size()
34            + self.edge_index_buffer.size()
35            + self._uniform_buf.size()
36    }
37}
38
39impl GpuByteSize for PointCloudGpuData {
40    fn gpu_bytes(&self) -> u64 {
41        self.vertex_buffer.size()
42            + self._uniform_buf.size()
43            + self._scalar_buf.size()
44            + self._colour_buf.size()
45            + self._radius_buf.size()
46            + self._transparency_buf.size()
47    }
48}
49
50impl GpuByteSize for GlyphGpuData {
51    fn gpu_bytes(&self) -> u64 {
52        self._uniform_buf.size() + self._instance_buf.size()
53    }
54}
55
56impl GpuByteSize for TensorGlyphGpuData {
57    fn gpu_bytes(&self) -> u64 {
58        self._uniform_buf.size() + self._instance_buf.size()
59    }
60}
61
62impl GpuByteSize for SpriteGpuData {
63    fn gpu_bytes(&self) -> u64 {
64        self.vertex_buffer.size() + self._uniform_buf.size() + self._instance_buf.size()
65    }
66}
67
68/// One store slot: the data (when occupied) and the slot's current generation.
69struct CurveSlot<T> {
70    data: Option<T>,
71    generation: u32,
72}
73
74macro_rules! curve_store {
75    ($(#[$id_doc:meta])* $id:ident, $store:ident, $data:ty) => {
76        crate::resources::handle::slot_handle! {
77            $(#[$id_doc])*
78            pub struct $id;
79        }
80
81        pub(crate) struct $store {
82            slots: Vec<CurveSlot<$data>>,
83            free_list: Vec<usize>,
84        }
85
86        impl $store {
87            pub fn new() -> Self {
88                Self { slots: Vec::new(), free_list: Vec::new() }
89            }
90
91            pub fn insert(&mut self, data: $data) -> $id {
92                if let Some(idx) = self.free_list.pop() {
93                    let slot = &mut self.slots[idx];
94                    slot.data = Some(data);
95                    $id::new(idx as u32, slot.generation)
96                } else {
97                    let idx = self.slots.len();
98                    self.slots.push(CurveSlot { data: Some(data), generation: 0 });
99                    $id::new(idx as u32, 0)
100                }
101            }
102
103            pub fn get(&self, id: $id) -> Option<&$data> {
104                let slot = self.slots.get(id.index())?;
105                if slot.generation != id.generation {
106                    return None;
107                }
108                slot.data.as_ref()
109            }
110
111            pub fn replace(&mut self, id: $id, data: $data) -> bool {
112                match self.slots.get_mut(id.index()) {
113                    Some(slot) if slot.generation == id.generation && slot.data.is_some() => {
114                        slot.data = Some(data);
115                        true
116                    }
117                    _ => false,
118                }
119            }
120
121            pub fn remove(&mut self, id: $id) -> bool {
122                if let Some(slot) = self.slots.get_mut(id.index()) {
123                    if slot.generation == id.generation && slot.data.is_some() {
124                        slot.data = None;
125                        slot.generation = slot.generation.wrapping_add(1);
126                        self.free_list.push(id.index());
127                        return true;
128                    }
129                }
130                false
131            }
132
133            pub fn contains(&self, id: $id) -> bool {
134                self.get(id).is_some()
135            }
136
137            #[allow(dead_code)]
138            pub fn len(&self) -> usize {
139                self.slots.iter().filter(|s| s.data.is_some()).count()
140            }
141
142            /// Total resident GPU bytes across every live entry in this store.
143            pub fn allocated_bytes(&self) -> u64 {
144                self.slots
145                    .iter()
146                    .filter_map(|s| s.data.as_ref())
147                    .map(GpuByteSize::gpu_bytes)
148                    .sum()
149            }
150        }
151    };
152}
153
154curve_store!(
155    /// Handle to a pre-uploaded polyline produced by
156    /// [`DeviceResources::upload_polyline`](crate::resources::DeviceResources::upload_polyline).
157    PolylineId,
158    PolylineStore,
159    PolylineGpuData
160);
161
162curve_store!(
163    /// Handle to a pre-uploaded streamtube produced by
164    /// [`DeviceResources::upload_streamtube`](crate::resources::DeviceResources::upload_streamtube).
165    StreamtubeId,
166    StreamtubeStore,
167    StreamtubeGpuData
168);
169
170curve_store!(
171    /// Handle to a pre-uploaded tube produced by
172    /// [`DeviceResources::upload_tube`](crate::resources::DeviceResources::upload_tube).
173    TubeId,
174    TubeStore,
175    StreamtubeGpuData
176);
177
178curve_store!(
179    /// Handle to a pre-uploaded ribbon produced by
180    /// [`DeviceResources::upload_ribbon`](crate::resources::DeviceResources::upload_ribbon).
181    RibbonId,
182    RibbonStore,
183    StreamtubeGpuData
184);
185
186curve_store!(
187    /// Handle to a pre-uploaded point cloud produced by
188    /// [`DeviceResources::upload_point_cloud`](crate::resources::DeviceResources::upload_point_cloud).
189    PointCloudId,
190    PointCloudStore,
191    PointCloudGpuData
192);
193
194curve_store!(
195    /// Handle to a pre-uploaded glyph set produced by
196    /// [`DeviceResources::upload_glyph_set`](crate::resources::DeviceResources::upload_glyph_set).
197    GlyphSetId,
198    GlyphSetStore,
199    GlyphGpuData
200);
201
202curve_store!(
203    /// Handle to a pre-uploaded tensor glyph set produced by
204    /// [`DeviceResources::upload_tensor_glyph_set`](crate::resources::DeviceResources::upload_tensor_glyph_set).
205    TensorGlyphSetId,
206    TensorGlyphSetStore,
207    TensorGlyphGpuData
208);
209
210curve_store!(
211    /// Handle to a pre-uploaded sprite set produced by
212    /// [`DeviceResources::upload_sprite_set`](crate::resources::DeviceResources::upload_sprite_set).
213    /// Backs static billboards such as foliage, signage, and light flares.
214    SpriteSetId,
215    SpriteSetStore,
216    SpriteGpuData
217);
218
219curve_store!(
220    /// Handle to a pre-uploaded sprite instance set produced by
221    /// [`DeviceResources::upload_sprite_instance_set`](crate::resources::DeviceResources::upload_sprite_instance_set).
222    /// Backs entity sprites such as NPCs, item drops, and damage numbers.
223    SpriteInstanceSetId,
224    SpriteInstanceSetStore,
225    SpriteGpuData
226);