Skip to main content

webrender/
intern.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! The interning module provides a generic data structure
6//! interning container. It is similar in concept to a
7//! traditional string interning container, but it is
8//! specialized to the WR thread model.
9//!
10//! There is an Interner structure, that lives in the
11//! scene builder thread, and a DataStore structure
12//! that lives in the frame builder thread.
13//!
14//! Hashing, interning and handle creation is done by
15//! the interner structure during scene building.
16//!
17//! Delta changes for the interner are pushed during
18//! a transaction to the frame builder. The frame builder
19//! is then able to access the content of the interned
20//! handles quickly, via array indexing.
21//!
22//! Epoch tracking ensures that the garbage collection
23//! step which the interner uses to remove items is
24//! only invoked on items that the frame builder thread
25//! is no longer referencing.
26//!
27//! Items in the data store are stored in a traditional
28//! free-list structure, for content access and memory
29//! usage efficiency.
30//!
31//! The epoch is incremented each time a scene is
32//! built. The most recently used scene epoch is
33//! stored inside each handle. This is then used for
34//! cache invalidation.
35
36use crate::internal_types::FastHashMap;
37use malloc_size_of::MallocSizeOf;
38use std::fmt::Debug;
39use std::hash::Hash;
40use std::marker::PhantomData;
41use std::{ops, u64};
42use crate::util::VecHelper;
43use crate::profiler::TransactionProfile;
44use peek_poke::PeekPoke;
45
46#[cfg_attr(feature = "capture", derive(Serialize))]
47#[cfg_attr(feature = "replay", derive(Deserialize))]
48#[derive(Debug, Copy, Clone, Hash, MallocSizeOf, PartialEq, Eq)]
49struct Epoch(u32);
50
51/// A list of updates to be applied to the data store,
52/// provided by the interning structure.
53#[cfg_attr(feature = "capture", derive(Serialize))]
54#[cfg_attr(feature = "replay", derive(Deserialize))]
55#[derive(MallocSizeOf)]
56pub struct UpdateList<S> {
57    /// Items to insert.
58    pub insertions: Vec<Insertion<S>>,
59
60    /// Items to remove.
61    pub removals: Vec<Removal>,
62}
63
64#[cfg_attr(feature = "capture", derive(Serialize))]
65#[cfg_attr(feature = "replay", derive(Deserialize))]
66#[derive(MallocSizeOf)]
67pub struct Insertion<S> {
68    pub index: usize,
69    pub uid: ItemUid,
70    pub value: S,
71}
72
73#[cfg_attr(feature = "capture", derive(Serialize))]
74#[cfg_attr(feature = "replay", derive(Deserialize))]
75#[derive(MallocSizeOf)]
76pub struct Removal {
77    pub index: usize,
78    pub uid: ItemUid,
79}
80
81impl<S> UpdateList<S> {
82    fn new() -> UpdateList<S> {
83        UpdateList {
84            insertions: Vec::new(),
85            removals: Vec::new(),
86        }
87    }
88
89    fn take_and_preallocate(&mut self) -> UpdateList<S> {
90        UpdateList {
91            insertions: self.insertions.take_and_preallocate(),
92            removals: self.removals.take_and_preallocate(),
93        }
94    }
95}
96
97/// A globally, unique identifier
98#[cfg_attr(feature = "capture", derive(Serialize))]
99#[cfg_attr(feature = "replay", derive(Deserialize))]
100#[derive(Copy, Clone, Eq, Hash, MallocSizeOf, PartialEq, PeekPoke, Default)]
101pub struct ItemUid {
102    uid: u64,
103}
104
105impl ItemUid {
106    // Intended for debug usage only
107    pub fn get_uid(&self) -> u64 {
108        self.uid
109    }
110}
111
112impl std::fmt::Debug for ItemUid {
113    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
114        write!(f, "#{}", self.uid)
115    }
116}
117
118#[cfg_attr(feature = "capture", derive(Serialize))]
119#[cfg_attr(feature = "replay", derive(Deserialize))]
120#[derive(Hash, MallocSizeOf, PartialEq, Eq)]
121pub struct Handle<I> {
122    index: u32,
123    epoch: Epoch,
124    _marker: PhantomData<I>,
125}
126
127impl<I> std::fmt::Debug for Handle<I> {
128    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
129        // PartialEq requires more trait bounds
130        if self.uid() == Self::INVALID.uid() {
131            write!(f, "<invalid>")
132        } else {
133            write!(f, "#{}:{}", self.index, self.epoch.0)
134        }
135    }
136}
137
138impl<I> Clone for Handle<I> {
139    fn clone(&self) -> Self {
140        Handle {
141            index: self.index,
142            epoch: self.epoch,
143            _marker: self._marker,
144        }
145    }
146}
147
148impl<I> Copy for Handle<I> {}
149
150impl<I> Handle<I> {
151    pub fn uid(&self) -> ItemUid {
152        ItemUid {
153            // The index in the freelist + the epoch it was interned generates a stable
154            // unique id for an interned element.
155            uid: ((self.index as u64) << 32) | self.epoch.0 as u64
156        }
157    }
158
159    pub const INVALID: Self = Handle { index: !0, epoch: Epoch(!0), _marker: PhantomData };
160}
161
162pub trait InternDebug {
163    fn on_interned(&self, _uid: ItemUid) {}
164}
165
166/// The data store lives in the frame builder thread. It
167/// contains a free-list of items for fast access.
168#[cfg_attr(feature = "capture", derive(Serialize))]
169#[cfg_attr(feature = "replay", derive(Deserialize))]
170#[derive(MallocSizeOf)]
171pub struct DataStore<I: Internable> {
172    items: Vec<Option<I::StoreData>>,
173}
174
175impl<I: Internable> Default for DataStore<I> {
176    fn default() -> Self {
177        DataStore {
178            items: Vec::new(),
179        }
180    }
181}
182
183impl<I: Internable> DataStore<I> {
184    /// Apply any updates from the scene builder thread to
185    /// this data store.
186    pub fn apply_updates(
187        &mut self,
188        update_list: UpdateList<I::Key>,
189        profile: &mut TransactionProfile,
190    ) {
191        for insertion in update_list.insertions {
192            self.items
193                .entry(insertion.index)
194                .set(Some(insertion.value.into()));
195        }
196
197        for removal in update_list.removals {
198            self.items[removal.index] = None;
199        }
200
201        profile.set(I::PROFILE_COUNTER, self.items.len());
202    }
203}
204
205/// Retrieve an item from the store via handle
206impl<I: Internable> ops::Index<Handle<I>> for DataStore<I> {
207    type Output = I::StoreData;
208    fn index(&self, handle: Handle<I>) -> &I::StoreData {
209        self.items[handle.index as usize].as_ref().expect("Bad datastore lookup")
210    }
211}
212
213/// Retrieve a mutable item from the store via handle
214/// Retrieve an item from the store via handle
215impl<I: Internable> ops::IndexMut<Handle<I>> for DataStore<I> {
216    fn index_mut(&mut self, handle: Handle<I>) -> &mut I::StoreData {
217        self.items[handle.index as usize].as_mut().expect("Bad datastore lookup")
218    }
219}
220
221#[cfg_attr(feature = "capture", derive(Serialize))]
222#[cfg_attr(feature = "replay", derive(Deserialize))]
223#[derive(MallocSizeOf)]
224struct ItemDetails<I> {
225    /// Frame that this element was first interned
226    interned_epoch: Epoch,
227    /// Last frame this element was referenced (used to GC intern items)
228    last_used_epoch: Epoch,
229    /// Index into the freelist this item is located
230    index: usize,
231    /// Type marker for create_handle method
232    _marker: PhantomData<I>,
233}
234
235impl<I> ItemDetails<I> {
236    /// Construct a stable handle value from the item details
237    fn create_handle(&self) -> Handle<I> {
238        Handle {
239            index: self.index as u32,
240            epoch: self.interned_epoch,
241            _marker: PhantomData,
242        }
243    }
244}
245
246/// The main interning data structure. This lives in the
247/// scene builder thread, and handles hashing and interning
248/// unique data structures. It also manages a free-list for
249/// the items in the data store, which is synchronized via
250/// an update list of additions / removals.
251#[cfg_attr(feature = "capture", derive(Serialize))]
252#[cfg_attr(feature = "replay", derive(Deserialize))]
253#[derive(MallocSizeOf)]
254pub struct Interner<I: Internable> {
255    /// Uniquely map an interning key to a handle
256    map: FastHashMap<I::Key, ItemDetails<I>>,
257    /// List of free slots in the data store for re-use.
258    free_list: Vec<usize>,
259    /// Pending list of updates that need to be applied.
260    update_list: UpdateList<I::Key>,
261    /// The current epoch for the interner.
262    current_epoch: Epoch,
263    /// The information associated with each interned
264    /// item that can be accessed by the interner.
265    local_data: Vec<I::InternData>,
266}
267
268impl<I: Internable> Default for Interner<I> {
269    fn default() -> Self {
270        Interner {
271            map: FastHashMap::default(),
272            free_list: Vec::new(),
273            update_list: UpdateList::new(),
274            current_epoch: Epoch(1),
275            local_data: Vec::new(),
276        }
277    }
278}
279
280impl<I: Internable> Interner<I> {
281    /// Intern a data structure, and return a handle to
282    /// that data. The handle can then be stored in the
283    /// frame builder, and safely accessed via the data
284    /// store that lives in the frame builder thread.
285    /// The provided closure is invoked to build the
286    /// local data about an interned structure if the
287    /// key isn't already interned.
288    pub fn intern<F>(
289        &mut self,
290        data: &I::Key,
291        fun: F,
292    ) -> Handle<I> where F: FnOnce() -> I::InternData {
293        // Use get_mut rather than entry here to avoid
294        // cloning the (sometimes large) key in the common
295        // case, where the data already exists in the interner.
296        if let Some(details) = self.map.get_mut(data) {
297            // Update the last referenced frame for this element
298            details.last_used_epoch = self.current_epoch;
299            // Return a stable handle value for dependency checking
300            return details.create_handle();
301        }
302
303        // We need to intern a new data item. First, find out
304        // if there is a spare slot in the free-list that we
305        // can use. Otherwise, append to the end of the list.
306        let index = match self.free_list.pop() {
307            Some(index) => index,
308            None => self.local_data.len(),
309        };
310
311        // Generate a handle for access via the data store.
312        let handle = Handle {
313            index: index as u32,
314            epoch: self.current_epoch,
315            _marker: PhantomData,
316        };
317
318        let uid = handle.uid();
319
320        // Add a pending update to insert the new data.
321        self.update_list.insertions.push(Insertion {
322            index,
323            uid,
324            value: data.clone(),
325        });
326
327        #[cfg(debug_assertions)]
328        data.on_interned(uid);
329
330        // Store this handle so the next time it is
331        // interned, it gets re-used.
332        self.map.insert(data.clone(), ItemDetails {
333            interned_epoch: self.current_epoch,
334            last_used_epoch: self.current_epoch,
335            index,
336            _marker: PhantomData,
337        });
338
339        // Create the local data for this item that is
340        // being interned.
341        self.local_data.entry(index).set(fun());
342
343        handle
344    }
345
346    /// Retrieve the pending list of updates for an interner
347    /// that need to be applied to the data store. Also run
348    /// a GC step that removes old entries.
349    pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList<I::Key> {
350        let mut update_list = self.update_list.take_and_preallocate();
351
352        let free_list = &mut self.free_list;
353        let current_epoch = self.current_epoch.0;
354
355        // First, run a GC step. Walk through the handles, and
356        // if we find any that haven't been used for some time,
357        // remove them. If this ever shows up in profiles, we
358        // can make the GC step partial (scan only part of the
359        // map each frame). It also might make sense in the
360        // future to adjust how long items remain in the cache
361        // based on the current size of the list.
362        self.map.retain(|_, details| {
363            if details.last_used_epoch.0 + 10 < current_epoch {
364                // To expire an item:
365                //  - Add index to the free-list for re-use.
366                //  - Add an update to the data store to invalidate this slot.
367                //  - Remove from the hash map.
368                free_list.push(details.index);
369                update_list.removals.push(Removal {
370                    index: details.index,
371                    uid: details.create_handle().uid(),
372                });
373                return false;
374            }
375
376            true
377        });
378
379        // Begin the next epoch
380        self.current_epoch = Epoch(self.current_epoch.0 + 1);
381
382        update_list
383    }
384}
385
386/// Retrieve the local data for an item from the interner via handle
387impl<I: Internable> ops::Index<Handle<I>> for Interner<I> {
388    type Output = I::InternData;
389    fn index(&self, handle: Handle<I>) -> &I::InternData {
390        &self.local_data[handle.index as usize]
391    }
392}
393
394/// Meta-macro to enumerate the various interner identifiers and types.
395///
396/// IMPORTANT: Keep this synchronized with the list in mozilla-central located at
397/// gfx/webrender_bindings/webrender_ffi.h
398///
399/// Note that this could be a lot less verbose if concat_idents! were stable. :-(
400#[macro_export]
401macro_rules! enumerate_interners {
402    ($macro_name: ident) => {
403        $macro_name! {
404            clip: ClipIntern,
405            prim: RectanglePrim,
406            normal_border: NormalBorderPrim,
407            image_border: ImageBorder,
408            image: Image,
409            yuv_image: YuvImage,
410            line_decoration: LineDecoration,
411            linear_grad: LinearGradient,
412            radial_grad: RadialGradient,
413            conic_grad: ConicGradient,
414            picture: Picture,
415            text_run: TextRun,
416            filter_data: FilterDataIntern,
417            backdrop_capture: BackdropCapture,
418            backdrop_render: BackdropRender,
419            polygon: PolygonIntern,
420            box_shadow: BoxShadow,
421        }
422    }
423}
424
425macro_rules! declare_interning_memory_report {
426    ( $( $name:ident: $ty:ident, )+ ) => {
427        ///
428        #[repr(C)]
429        #[derive(AddAssign, Clone, Debug, Default)]
430        pub struct InternerSubReport {
431            $(
432                ///
433                pub $name: usize,
434            )+
435        }
436    }
437}
438
439enumerate_interners!(declare_interning_memory_report);
440
441/// Memory report for interning-related data structures.
442/// cbindgen:derive-eq=false
443/// cbindgen:derive-ostream=false
444#[repr(C)]
445#[derive(Clone, Debug, Default)]
446pub struct InterningMemoryReport {
447    ///
448    pub interners: InternerSubReport,
449    ///
450    pub data_stores: InternerSubReport,
451}
452
453impl ::std::ops::AddAssign for InterningMemoryReport {
454    fn add_assign(&mut self, other: InterningMemoryReport) {
455        self.interners += other.interners;
456        self.data_stores += other.data_stores;
457    }
458}
459
460// The trick to make trait bounds configurable by features.
461mod dummy {
462    #[cfg(not(feature = "capture"))]
463    pub trait Serialize {}
464    #[cfg(not(feature = "capture"))]
465    impl<T> Serialize for T {}
466    #[cfg(not(feature = "replay"))]
467    pub trait Deserialize<'a> {}
468    #[cfg(not(feature = "replay"))]
469    impl<'a, T> Deserialize<'a> for T {}
470}
471#[cfg(feature = "capture")]
472use serde::Serialize as InternSerialize;
473#[cfg(not(feature = "capture"))]
474use self::dummy::Serialize as InternSerialize;
475#[cfg(feature = "replay")]
476use serde::Deserialize as InternDeserialize;
477#[cfg(not(feature = "replay"))]
478use self::dummy::Deserialize as InternDeserialize;
479
480/// Implement `Internable` for a type that wants to participate in interning.
481pub trait Internable: MallocSizeOf {
482    type Key: Eq + Hash + Clone + Debug + MallocSizeOf + InternDebug + InternSerialize + for<'a> InternDeserialize<'a>;
483    type StoreData: From<Self::Key> + MallocSizeOf + InternSerialize + for<'a> InternDeserialize<'a>;
484    type InternData: MallocSizeOf + InternSerialize + for<'a> InternDeserialize<'a>;
485
486    // Profile counter indices, see the list in profiler.rs
487    const PROFILE_COUNTER: usize;
488}