tessera_ui/renderer/
external.rs

1//! External render textures owned by pipelines.
2//!
3//! ## Usage
4//!
5//! Keep persistent atlases and caches across frames for render pipelines.
6
7use std::{collections::HashMap, sync::Arc};
8
9use parking_lot::{RwLock, RwLockWriteGuard};
10
11use crate::{
12    PxSize,
13    render_graph::{ExternalTextureDesc, RenderTextureDesc},
14};
15
16/// Registry of persistent textures that are owned by render pipelines.
17#[derive(Clone)]
18pub struct ExternalTextureRegistry {
19    inner: Arc<RwLock<ExternalTextureRegistryInner>>,
20}
21
22impl ExternalTextureRegistry {
23    pub(crate) fn new() -> Self {
24        Self {
25            inner: Arc::new(RwLock::new(ExternalTextureRegistryInner::default())),
26        }
27    }
28
29    /// Allocate a new persistent texture and return its handle.
30    pub fn allocate(
31        &self,
32        device: &wgpu::Device,
33        desc: RenderTextureDesc,
34        sample_count: u32,
35    ) -> ExternalTextureHandle {
36        let mut inner = self.inner.write();
37        let id = inner.next_id;
38        inner.next_id = inner.next_id.wrapping_add(1);
39        let entry = ExternalTextureEntry::new(device, desc.clone(), sample_count);
40        inner.entries.insert(id, entry);
41        ExternalTextureHandle {
42            id,
43            desc,
44            sample_count,
45            registry: self.clone(),
46        }
47    }
48
49    /// Ensure the texture backing the handle matches the requested description.
50    pub fn ensure(
51        &self,
52        device: &wgpu::Device,
53        handle: &mut ExternalTextureHandle,
54        desc: RenderTextureDesc,
55        sample_count: u32,
56    ) {
57        if handle.desc == desc && handle.sample_count == sample_count {
58            return;
59        }
60        let mut inner = self.inner.write();
61        if let Some(entry) = inner.entries.get_mut(&handle.id) {
62            entry.rebuild(device, desc.clone(), sample_count);
63        } else {
64            let entry = ExternalTextureEntry::new(device, desc.clone(), sample_count);
65            inner.entries.insert(handle.id, entry);
66        }
67        handle.desc = desc;
68        handle.sample_count = sample_count;
69    }
70
71    /// Mark an external texture as used in the current frame.
72    pub fn mark_used(&self, id: u32, frame_index: u64) {
73        if let Some(entry) = self.inner.write().entries.get_mut(&id) {
74            entry.last_used_frame = frame_index;
75        }
76    }
77
78    /// Remove unused textures after a delay once all handles are dropped.
79    pub fn collect_garbage(&self, frame_index: u64, delay_frames: u64) {
80        let mut inner = self.inner.write();
81        inner.entries.retain(|_, entry| {
82            if entry.ref_count > 0 {
83                return true;
84            }
85            frame_index <= entry.last_used_frame.saturating_add(delay_frames)
86        });
87    }
88
89    pub(crate) fn slot(&self, id: u32) -> Option<ExternalTextureSlotGuard<'_>> {
90        let guard = self.inner.write();
91        guard
92            .entries
93            .contains_key(&id)
94            .then_some(ExternalTextureSlotGuard { guard, id })
95    }
96
97    fn add_ref(&self, id: u32) {
98        self.inner.write().add_ref(id);
99    }
100
101    fn release(&self, id: u32) {
102        self.inner.write().release(id);
103    }
104}
105
106/// Handle to a persistent external texture stored in the registry.
107pub struct ExternalTextureHandle {
108    id: u32,
109    desc: RenderTextureDesc,
110    sample_count: u32,
111    registry: ExternalTextureRegistry,
112}
113
114impl ExternalTextureHandle {
115    /// Return the stable id for this texture.
116    pub fn id(&self) -> u32 {
117        self.id
118    }
119
120    /// Build a render-graph descriptor for this texture.
121    pub fn desc(&self, clear_on_first_use: bool) -> ExternalTextureDesc {
122        ExternalTextureDesc {
123            handle_id: self.id,
124            size: self.desc.size,
125            format: self.desc.format,
126            sample_count: self.sample_count,
127            clear_on_first_use,
128        }
129    }
130
131    /// Ensure the registry texture matches the provided description.
132    pub fn ensure(
133        &mut self,
134        registry: &ExternalTextureRegistry,
135        device: &wgpu::Device,
136        desc: RenderTextureDesc,
137        sample_count: u32,
138    ) {
139        registry.ensure(device, self, desc, sample_count);
140    }
141}
142
143impl Clone for ExternalTextureHandle {
144    fn clone(&self) -> Self {
145        self.registry.add_ref(self.id);
146        Self {
147            id: self.id,
148            desc: self.desc.clone(),
149            sample_count: self.sample_count,
150            registry: self.registry.clone(),
151        }
152    }
153}
154
155impl Drop for ExternalTextureHandle {
156    fn drop(&mut self) {
157        self.registry.release(self.id);
158    }
159}
160
161pub(crate) struct ExternalTextureSlotGuard<'a> {
162    guard: RwLockWriteGuard<'a, ExternalTextureRegistryInner>,
163    id: u32,
164}
165
166impl ExternalTextureSlotGuard<'_> {
167    pub fn size(&self) -> PxSize {
168        self.entry().desc.size
169    }
170
171    pub fn front_view(&self) -> wgpu::TextureView {
172        self.entry().front.clone()
173    }
174
175    pub fn back_view(&self) -> wgpu::TextureView {
176        self.entry().back.clone()
177    }
178
179    pub fn msaa_view(&self) -> Option<wgpu::TextureView> {
180        self.entry().msaa_view.clone()
181    }
182
183    pub fn swap_front_back(&mut self) {
184        let entry = self.entry_mut();
185        std::mem::swap(&mut entry.front, &mut entry.back);
186    }
187
188    fn entry(&self) -> &ExternalTextureEntry {
189        self.guard
190            .entries
191            .get(&self.id)
192            .expect("missing external texture entry")
193    }
194
195    fn entry_mut(&mut self) -> &mut ExternalTextureEntry {
196        self.guard
197            .entries
198            .get_mut(&self.id)
199            .expect("missing external texture entry")
200    }
201}
202
203#[derive(Default)]
204struct ExternalTextureRegistryInner {
205    next_id: u32,
206    entries: HashMap<u32, ExternalTextureEntry>,
207}
208
209impl ExternalTextureRegistryInner {
210    fn add_ref(&mut self, id: u32) {
211        if let Some(entry) = self.entries.get_mut(&id) {
212            entry.ref_count = entry.ref_count.saturating_add(1);
213        }
214    }
215
216    fn release(&mut self, id: u32) {
217        if let Some(entry) = self.entries.get_mut(&id) {
218            entry.ref_count = entry.ref_count.saturating_sub(1);
219        }
220    }
221}
222
223struct ExternalTextureEntry {
224    desc: RenderTextureDesc,
225    sample_count: u32,
226    front: wgpu::TextureView,
227    back: wgpu::TextureView,
228    msaa_view: Option<wgpu::TextureView>,
229    last_used_frame: u64,
230    ref_count: u32,
231}
232
233impl ExternalTextureEntry {
234    fn new(device: &wgpu::Device, desc: RenderTextureDesc, sample_count: u32) -> Self {
235        let (front, back, msaa_view) = create_views(device, &desc, sample_count);
236        Self {
237            desc,
238            sample_count,
239            front,
240            back,
241            msaa_view,
242            last_used_frame: 0,
243            ref_count: 1,
244        }
245    }
246
247    fn rebuild(&mut self, device: &wgpu::Device, desc: RenderTextureDesc, sample_count: u32) {
248        let (front, back, msaa_view) = create_views(device, &desc, sample_count);
249        self.desc = desc;
250        self.sample_count = sample_count;
251        self.front = front;
252        self.back = back;
253        self.msaa_view = msaa_view;
254    }
255}
256
257fn create_views(
258    device: &wgpu::Device,
259    desc: &RenderTextureDesc,
260    sample_count: u32,
261) -> (
262    wgpu::TextureView,
263    wgpu::TextureView,
264    Option<wgpu::TextureView>,
265) {
266    let front = create_texture_view(device, desc, "External Front");
267    let back = create_texture_view(device, desc, "External Back");
268    let msaa_view = if sample_count > 1 {
269        Some(create_msaa_view(device, desc, sample_count))
270    } else {
271        None
272    };
273    (front, back, msaa_view)
274}
275
276fn create_texture_view(
277    device: &wgpu::Device,
278    desc: &RenderTextureDesc,
279    label: &str,
280) -> wgpu::TextureView {
281    let width = desc.size.width.positive().max(1);
282    let height = desc.size.height.positive().max(1);
283    let texture = device.create_texture(&wgpu::TextureDescriptor {
284        label: Some(label),
285        size: wgpu::Extent3d {
286            width,
287            height,
288            depth_or_array_layers: 1,
289        },
290        mip_level_count: 1,
291        sample_count: 1,
292        dimension: wgpu::TextureDimension::D2,
293        format: desc.format,
294        usage: wgpu::TextureUsages::RENDER_ATTACHMENT
295            | wgpu::TextureUsages::TEXTURE_BINDING
296            | wgpu::TextureUsages::STORAGE_BINDING
297            | wgpu::TextureUsages::COPY_SRC
298            | wgpu::TextureUsages::COPY_DST,
299        view_formats: &[],
300    });
301    texture.create_view(&wgpu::TextureViewDescriptor::default())
302}
303
304fn create_msaa_view(
305    device: &wgpu::Device,
306    desc: &RenderTextureDesc,
307    sample_count: u32,
308) -> wgpu::TextureView {
309    let width = desc.size.width.positive().max(1);
310    let height = desc.size.height.positive().max(1);
311    let texture = device.create_texture(&wgpu::TextureDescriptor {
312        label: Some("External MSAA"),
313        size: wgpu::Extent3d {
314            width,
315            height,
316            depth_or_array_layers: 1,
317        },
318        mip_level_count: 1,
319        sample_count,
320        dimension: wgpu::TextureDimension::D2,
321        format: desc.format,
322        usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
323        view_formats: &[],
324    });
325    texture.create_view(&wgpu::TextureViewDescriptor::default())
326}