Skip to main content

radiant_rs/core/
context.rs

1use crate::core::{font, SpriteData};
2use crate::prelude::*;
3use crate::backends::backend;
4
5/// Number of texture buckets. Also requires change to renderer.rs at "let uniforms = uniform! { ... }"
6pub const NUM_BUCKETS: usize = 6;
7
8/// Initial sprite capacity. Automatically increases.
9pub const INITIAL_CAPACITY: usize = 512;
10
11/// Texture generation (increases each cleanup)
12static GENERATION: AtomicUsize = AtomicUsize::new(0);
13
14/// A thread-safe render-context.
15///
16/// Contains data relating to one or more windows and associated resources.
17#[derive(Clone)]
18pub struct Context (Arc<Mutex<ContextData>>);
19
20unsafe impl Send for Context { }
21unsafe impl Sync for Context { }
22
23impl Debug for Context {
24    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25        write!(f, "Context")
26    }
27}
28
29impl Context {
30    /// Creates a new context for use with multiple displays. For a single display use cases, simply create the display and obtain
31    /// its context via `Display::context()`
32    pub fn new() -> Context {
33        let context_data = ContextData::new();
34        Context(Arc::new(Mutex::new(context_data)))
35    }
36    /// Prunes no longer used textures. Requires all layers to be cleared before
37    /// adding new sprites or rendering the layer.
38    pub fn prune(self: &Self) {
39        self.lock().prune();
40    }
41    /// Mutex-locks the instance and returns the MutexGuard
42    pub(crate) fn lock<'a>(self: &'a Self) -> MutexGuard<'a, ContextData> {
43        self.0.lock().unwrap()
44    }
45}
46
47/// Individual Texture.
48#[derive(Clone)]
49pub struct RawFrame {
50    pub data    : Vec<u8>,
51    pub width   : u32,
52    pub height  : u32,
53    pub channels: u8,
54}
55
56/// A weak reference back to a sprite.
57struct SpriteBackRef (Weak<SpriteData>);
58
59impl SpriteBackRef {
60    /// Creates a new weak reference to SpriteData.
61    fn new(data: Weak<SpriteData>) -> Self {
62        SpriteBackRef(data)
63    }
64    /// Returns a strong reference to the SpriteData.
65    fn upgrade(self: &Self) -> Option<Arc<SpriteData>> {
66        self.0.upgrade()
67    }
68    /// Returns the texture id-range used by the referenced sprite or None, if it dropped.
69    fn range(self: &Self) -> Option<(usize, usize)> {
70        if let Some(data) = self.upgrade() {
71            Some((data.texture_id.load(Ordering::Relaxed), data.num_frames as usize * data.components as usize))
72        } else {
73            None
74        }
75    }
76}
77
78/// Texture data for a single texture array.
79pub struct RawFrameArray {
80    pub dirty   : bool,
81    pub data    : backend::Texture2dArray,
82    pub raw     : Vec<RawFrame>,
83    sprites     : Vec<SpriteBackRef>,
84}
85
86impl RawFrameArray {
87    fn new(context: &backend::Context) -> Self {
88        RawFrameArray {
89            dirty   : false,
90            data    : backend::Texture2dArray::new(context, &Vec::new()),
91            raw     : Vec::new(),
92            sprites : Vec::new(),
93        }
94    }
95    /// Store given frames to texture arrays.
96    pub fn store_frames<'a>(self: &mut Self, raw_frames: Vec<RawFrame>) -> u32 {
97        let texture_id = self.raw.len() as u32;
98        for frame in raw_frames {
99            self.raw.push(frame);
100        }
101        self.dirty = true;
102        texture_id
103    }
104    /// Stores a weak sprite reference in the context so that the sprite's texture_id can be updated after a cleanup.
105    pub fn store_sprite(self: &mut Self, sprite_data: Weak<SpriteData>) {
106        self.sprites.push(SpriteBackRef::new(sprite_data));
107    }
108    /// Updates texture array in video memory.
109    fn update(self: &mut Self, context: &backend::Context) {
110        if self.dirty {
111            self.dirty = false;
112            self.data = backend::Texture2dArray::new(context, &self.raw);
113        }
114    }
115    /// Returns a list of tuples containing current sprite texture_id and required negative offset.
116    fn create_prune_map(self: &Self) -> Option<Vec<(usize, usize)>> {
117        let mut mapping = self.sprites.iter().filter_map(|sprite| sprite.range()).collect::<Vec<(usize, usize)>>();
118        mapping.sort_by_key(|a| a.0);
119        let mut num_items = 0;
120        for i in 0..mapping.len() {
121            let items = mapping[i].1;
122            mapping[i].1 = mapping[i].0 - num_items;
123            num_items += items;
124        }
125        if mapping.len() > 0 { Some(mapping) } else { None }
126    }
127    // Shrinks raw data array using given prune-map. Returns hashmap mapping old texture index -> new texture index.
128    fn prune_raw_textures(self: &mut Self, mapping: &Vec<(usize, usize)>) -> HashMap<usize, usize> {
129        let new_size = self.raw.len() - mapping.last().unwrap().1;
130        let mut destination_map = HashMap::new();
131        for m in 0..mapping.len() {
132            destination_map.insert(mapping[m].0, mapping[m].0 - mapping[m].1);
133            let end = if m + 1 < mapping.len() { mapping[m+1].0 } else { new_size -1 };
134            for i in (mapping[m].0)..end {
135                let destination_index = i - mapping[m].1;
136                self.raw.swap(i, destination_index);
137            }
138        }
139        self.raw.truncate(new_size);
140        destination_map
141    }
142    // Runs func on all sprites still referenced, removes unreferenced sprites from list.
143    fn prune_sprites<T>(self: &mut Self, mut func: T) where T: FnMut(&Arc<SpriteData>) {
144        let mut removed = Vec::new();
145        for (i, sprite) in self.sprites.iter().enumerate() {
146            if let Some(sprite) = sprite.upgrade() {
147                func(&sprite);
148            } else {
149                removed.push(i);
150            }
151        }
152        for index in removed.iter().rev() {
153            self.sprites.swap_remove(*index);
154        }
155    }
156    /// Prunes no longer used textures from the array and update sprite texture ids and generations.
157    fn prune(self: &mut Self, context: &backend::Context, generation: usize) {
158        if let Some(mapping) = self.create_prune_map() {
159            // Remove unused textures from raw data.
160            let destination_map = self.prune_raw_textures(&mapping);
161            self.dirty = true;
162            self.update(context);
163            // Update sprite texture ids and generation.
164            self.prune_sprites(|sprite| {
165                let texture_id = sprite.texture_id.load(Ordering::Relaxed);
166                if let Some(new_texture_id) = destination_map.get(&texture_id) {
167                    sprite.texture_id.store(*new_texture_id, Ordering::Relaxed);
168                }
169                sprite.generation.store(generation, Ordering::Relaxed);
170            });
171        } else {
172            // Texure ids have not changed, simply update generation.
173            self.prune_sprites(|sprite| {
174                sprite.generation.store(generation, Ordering::Relaxed);
175            })
176        }
177    }
178}
179
180/// Internal data of a Context
181pub struct ContextData {
182    pub backend_context     : Option<backend::Context>,
183    pub tex_arrays          : Vec<RawFrameArray>,
184    pub font_cache_dimensions: u32,
185    pub font_cache          : font::FontCache,
186    pub font_texture        : Option<backend::Texture2d>,
187    generation              : usize,
188}
189
190impl ContextData {
191
192    /// Initializes the backend context. Happens lazily once the first window is created.
193    fn init_backend(self: &mut Self, display: &backend::Display) {
194
195        let backend_context = backend::Context::new(display, INITIAL_CAPACITY);
196
197        // sprite texture arrays
198
199        for _ in 0..NUM_BUCKETS {
200            self.tex_arrays.push(RawFrameArray::new(&backend_context));
201        }
202
203        // font cache texture
204
205        let data = crate::core::RawFrame {
206            width   : self.font_cache_dimensions,
207            height  : self.font_cache_dimensions,
208            data    : vec![0u8; self.font_cache_dimensions as usize * self.font_cache_dimensions as usize],
209            channels: 1,
210        };
211
212        let texture = backend::Texture2d::new(&backend_context, self.font_cache_dimensions, self.font_cache_dimensions, crate::core::TextureFormat::U8, Some(data));
213
214        self.font_texture = Some(texture);
215        self.backend_context = Some(backend_context);
216    }
217
218    /// Create a new instance
219    fn new() -> Self {
220
221        let font_cache_dimensions = 512;
222
223        ContextData {
224            backend_context     : None,
225            tex_arrays          : Vec::new(),
226            font_cache          : font::FontCache::new(font_cache_dimensions, font_cache_dimensions, 0.01, 0.01),
227            font_texture        : None,
228            font_cache_dimensions,
229            generation          : Self::create_generation(),
230        }
231    }
232
233    /// Returns whether the context has already been associated with a display as required by some backends.
234    pub fn has_primary_display(self: &Self) -> bool {
235        self.backend_context.is_some()
236    }
237
238    /// Associates the context with a display as required by some backends.
239    pub fn set_primary_display(self: &mut Self, display: &backend::Display) {
240        self.init_backend(&display);
241    }
242
243    /// Returns the context's generation.
244    pub fn generation(self: &Self) -> usize {
245        self.generation
246    }
247
248    /// Update font-texture from cache
249    pub fn update_font_cache(self: &Self) {
250        self.font_cache.update(self.font_texture.as_ref().unwrap());
251    }
252
253    /// Update texture arrays from registered textures
254    pub fn update_tex_array(self: &mut Self) {
255        for ref mut array in self.tex_arrays.iter_mut() {
256            array.update(self.backend_context.as_ref().unwrap());
257        }
258    }
259
260    /// Store given frames to texture arrays
261    pub fn store_frames(self: &mut Self, bucket_id: u32, raw_frames: Vec<RawFrame>) -> u32 {
262        self.tex_arrays[bucket_id as usize].store_frames(raw_frames)
263    }
264
265    /// Stores a weak sprite reference in the context so that the sprite's texture_id can be updated after a cleanup.
266    pub fn store_sprite(self: &mut Self, bucket_id: u32, sprite_data: Weak<SpriteData>) {
267        self.tex_arrays[bucket_id as usize].store_sprite(sprite_data);
268    }
269
270    /// Prunes no longer used textures for all texture arrays.
271    fn prune(self: &mut Self) {
272        self.generation = Self::create_generation();
273        for array in self.tex_arrays.iter_mut() {
274            array.prune(self.backend_context.as_ref().unwrap(), self.generation);
275        }
276    }
277
278    // Creates a new generation and returns it
279    fn create_generation() -> usize {
280        // needs to start at 1 as 0 has special meaning
281        GENERATION.fetch_add(1, Ordering::Relaxed) + 1
282    }
283}