1use oxiui_core::UiError;
37
38use crate::gpu::render_target::RenderTarget;
39
40struct LayerEntry {
43 target: RenderTarget,
44 last_frame: u64,
46 generation: u64,
48}
49
50pub struct LayerCache {
54 entries: std::collections::HashMap<u64, LayerEntry>,
55 max_idle_frames: u64,
57 current_frame: u64,
59 max_layers: usize,
61}
62
63impl LayerCache {
64 pub fn new(max_layers: usize) -> Self {
66 Self {
67 entries: std::collections::HashMap::new(),
68 max_idle_frames: 4,
69 current_frame: 0,
70 max_layers: max_layers.max(1),
71 }
72 }
73
74 pub fn set_max_idle_frames(&mut self, frames: u64) {
79 self.max_idle_frames = frames.max(1);
80 }
81
82 pub fn begin_frame(&mut self) {
88 self.current_frame += 1;
89 let max_idle = self.max_idle_frames;
91 let current = self.current_frame;
92 self.entries
93 .retain(|_, entry| current - entry.last_frame <= max_idle);
94 }
95
96 pub fn get(&mut self, layer_id: u64) -> Option<&mut RenderTarget> {
101 let current = self.current_frame;
102 let entry = self.entries.get_mut(&layer_id)?;
103 entry.last_frame = current;
104 Some(&mut entry.target)
105 }
106
107 pub fn get_or_create(
122 &mut self,
123 device: &wgpu::Device,
124 layer_id: u64,
125 width: u32,
126 height: u32,
127 sample_count: u32,
128 ) -> Result<&mut RenderTarget, UiError> {
129 let current = self.current_frame;
130
131 if !self.entries.contains_key(&layer_id) {
132 if self.entries.len() >= self.max_layers {
134 self.evict_lru();
135 }
136 let target = RenderTarget::new(device, width, height, sample_count)?;
137 self.entries.insert(
138 layer_id,
139 LayerEntry {
140 target,
141 last_frame: current,
142 generation: 0,
143 },
144 );
145 }
146
147 let entry = self.entries.get_mut(&layer_id).expect("just inserted");
148 entry.last_frame = current;
149 Ok(&mut entry.target)
150 }
151
152 pub fn invalidate(&mut self, layer_id: u64) {
155 if let Some(entry) = self.entries.get_mut(&layer_id) {
156 entry.target.mark_dirty();
157 entry.generation += 1;
158 }
159 }
160
161 pub fn invalidate_all(&mut self) {
163 for entry in self.entries.values_mut() {
164 entry.target.mark_dirty();
165 entry.generation += 1;
166 }
167 }
168
169 pub fn remove(&mut self, layer_id: u64) {
171 self.entries.remove(&layer_id);
172 }
173
174 pub fn clear(&mut self) {
176 self.entries.clear();
177 }
178
179 pub fn len(&self) -> usize {
181 self.entries.len()
182 }
183
184 pub fn is_empty(&self) -> bool {
186 self.entries.is_empty()
187 }
188
189 pub fn current_frame(&self) -> u64 {
191 self.current_frame
192 }
193
194 fn evict_lru(&mut self) {
198 let lru_key = self
200 .entries
201 .iter()
202 .min_by_key(|(_, e)| e.last_frame)
203 .map(|(&k, _)| k);
204 if let Some(k) = lru_key {
205 self.entries.remove(&k);
206 }
207 }
208}
209
210#[cfg(test)]
213mod tests {
214 use super::*;
215
216 fn try_device() -> Option<(wgpu::Device, wgpu::Queue)> {
217 let instance = wgpu::Instance::default();
218 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
219 power_preference: wgpu::PowerPreference::default(),
220 force_fallback_adapter: false,
221 compatible_surface: None,
222 }))
223 .ok()?;
224 pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
225 label: Some("layer-cache test device"),
226 required_features: wgpu::Features::empty(),
227 required_limits: wgpu::Limits::downlevel_defaults(),
228 memory_hints: wgpu::MemoryHints::Performance,
229 experimental_features: wgpu::ExperimentalFeatures::disabled(),
230 trace: wgpu::Trace::Off,
231 }))
232 .ok()
233 }
234
235 #[test]
236 fn layer_cache_empty_on_creation() {
237 let cache = LayerCache::new(8);
238 assert_eq!(cache.len(), 0);
239 assert!(cache.is_empty());
240 assert_eq!(cache.current_frame(), 0);
241 }
242
243 #[test]
244 fn layer_cache_get_or_create_and_get() {
245 let Some((device, _queue)) = try_device() else {
246 return;
247 };
248 let mut cache = LayerCache::new(4);
249 cache.begin_frame();
250
251 let target = cache
252 .get_or_create(&device, 42, 32, 32, 1)
253 .expect("create layer 42");
254 assert!(target.is_dirty(), "fresh layer must be dirty");
255 target.mark_clean();
256
257 assert_eq!(cache.len(), 1);
258 let t = cache.get(42).expect("layer 42 must exist");
259 assert!(!t.is_dirty(), "layer must be clean after mark_clean");
260 }
261
262 #[test]
263 fn layer_cache_invalidate_marks_dirty() {
264 let Some((device, _queue)) = try_device() else {
265 return;
266 };
267 let mut cache = LayerCache::new(4);
268 cache.begin_frame();
269
270 let target = cache.get_or_create(&device, 1, 16, 16, 1).expect("create");
271 target.mark_clean();
272 cache.invalidate(1);
273 let t = cache.get(1).expect("layer 1 must exist");
274 assert!(t.is_dirty(), "invalidated layer must be dirty");
275 }
276
277 #[test]
278 fn layer_cache_evicts_idle_layers() {
279 let Some((device, _queue)) = try_device() else {
280 return;
281 };
282 let mut cache = LayerCache::new(8);
283 cache.set_max_idle_frames(2);
284
285 cache.begin_frame(); cache.get_or_create(&device, 10, 16, 16, 1).expect("create");
287
288 cache.begin_frame(); cache.begin_frame(); cache.begin_frame(); assert!(
294 cache.get(10).is_none(),
295 "layer 10 must be evicted after {n} idle frames",
296 n = 3
297 );
298 }
299
300 #[test]
301 fn layer_cache_lru_eviction_at_capacity() {
302 let Some((device, _queue)) = try_device() else {
303 return;
304 };
305 let mut cache = LayerCache::new(2); cache.begin_frame();
308 cache.get_or_create(&device, 1, 8, 8, 1).expect("layer 1");
309 cache.get_or_create(&device, 2, 8, 8, 1).expect("layer 2");
310 assert_eq!(cache.len(), 2);
311
312 cache.begin_frame();
314 let _ = cache.get(2); cache.get_or_create(&device, 3, 8, 8, 1).expect("layer 3");
318 assert_eq!(cache.len(), 2, "cache should still hold 2 entries");
319 assert!(
320 cache.get(1).is_none(),
321 "layer 1 (LRU) must have been evicted"
322 );
323 assert!(cache.get(2).is_some(), "layer 2 must survive");
324 assert!(cache.get(3).is_some(), "layer 3 must be present");
325 }
326
327 #[test]
328 fn layer_cache_clear_removes_all() {
329 let Some((device, _queue)) = try_device() else {
330 return;
331 };
332 let mut cache = LayerCache::new(8);
333 cache.begin_frame();
334 cache.get_or_create(&device, 1, 8, 8, 1).expect("layer 1");
335 cache.get_or_create(&device, 2, 8, 8, 1).expect("layer 2");
336 assert_eq!(cache.len(), 2);
337 cache.clear();
338 assert!(cache.is_empty());
339 }
340
341 #[test]
342 fn layer_cache_invalid_layer_id_returns_none() {
343 let mut cache = LayerCache::new(4);
344 cache.begin_frame();
345 assert!(cache.get(9999).is_none());
346 }
347
348 #[test]
349 fn layer_cache_invalidate_all() {
350 let Some((device, _queue)) = try_device() else {
351 return;
352 };
353 let mut cache = LayerCache::new(4);
354 cache.begin_frame();
355 for id in 1u64..=3 {
356 let t = cache.get_or_create(&device, id, 8, 8, 1).expect("create");
357 t.mark_clean();
358 }
359 cache.invalidate_all();
360 for id in 1u64..=3 {
361 let t = cache.get(id).expect("layer must exist");
362 assert!(
363 t.is_dirty(),
364 "all layers must be dirty after invalidate_all"
365 );
366 }
367 }
368}