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(Debug, 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
112#[cfg_attr(feature = "capture", derive(Serialize))]
113#[cfg_attr(feature = "replay", derive(Deserialize))]
114#[derive(Debug, Hash, MallocSizeOf, PartialEq, Eq)]
115pub struct Handle<I> {
116    index: u32,
117    epoch: Epoch,
118    _marker: PhantomData<I>,
119}
120
121impl<I> Clone for Handle<I> {
122    fn clone(&self) -> Self {
123        Handle {
124            index: self.index,
125            epoch: self.epoch,
126            _marker: self._marker,
127        }
128    }
129}
130
131impl<I> Copy for Handle<I> {}
132
133impl<I> Handle<I> {
134    pub fn uid(&self) -> ItemUid {
135        ItemUid {
136            // The index in the freelist + the epoch it was interned generates a stable
137            // unique id for an interned element.
138            uid: ((self.index as u64) << 32) | self.epoch.0 as u64
139        }
140    }
141
142    pub const INVALID: Self = Handle { index: !0, epoch: Epoch(!0), _marker: PhantomData };
143}
144
145pub trait InternDebug {
146    fn on_interned(&self, _uid: ItemUid) {}
147}
148
149/// The data store lives in the frame builder thread. It
150/// contains a free-list of items for fast access.
151#[cfg_attr(feature = "capture", derive(Serialize))]
152#[cfg_attr(feature = "replay", derive(Deserialize))]
153#[derive(MallocSizeOf)]
154pub struct DataStore<I: Internable> {
155    items: Vec<Option<I::StoreData>>,
156}
157
158impl<I: Internable> Default for DataStore<I> {
159    fn default() -> Self {
160        DataStore {
161            items: Vec::new(),
162        }
163    }
164}
165
166impl<I: Internable> DataStore<I> {
167    /// Apply any updates from the scene builder thread to
168    /// this data store.
169    pub fn apply_updates(
170        &mut self,
171        update_list: UpdateList<I::Key>,
172        profile: &mut TransactionProfile,
173    ) {
174        for insertion in update_list.insertions {
175            self.items
176                .entry(insertion.index)
177                .set(Some(insertion.value.into()));
178        }
179
180        for removal in update_list.removals {
181            self.items[removal.index] = None;
182        }
183
184        profile.set(I::PROFILE_COUNTER, self.items.len());
185    }
186}
187
188/// Retrieve an item from the store via handle
189impl<I: Internable> ops::Index<Handle<I>> for DataStore<I> {
190    type Output = I::StoreData;
191    fn index(&self, handle: Handle<I>) -> &I::StoreData {
192        self.items[handle.index as usize].as_ref().expect("Bad datastore lookup")
193    }
194}
195
196/// Retrieve a mutable item from the store via handle
197/// Retrieve an item from the store via handle
198impl<I: Internable> ops::IndexMut<Handle<I>> for DataStore<I> {
199    fn index_mut(&mut self, handle: Handle<I>) -> &mut I::StoreData {
200        self.items[handle.index as usize].as_mut().expect("Bad datastore lookup")
201    }
202}
203
204#[cfg_attr(feature = "capture", derive(Serialize))]
205#[cfg_attr(feature = "replay", derive(Deserialize))]
206#[derive(MallocSizeOf)]
207struct ItemDetails<I> {
208    /// Frame that this element was first interned
209    interned_epoch: Epoch,
210    /// Last frame this element was referenced (used to GC intern items)
211    last_used_epoch: Epoch,
212    /// Index into the freelist this item is located
213    index: usize,
214    /// Type marker for create_handle method
215    _marker: PhantomData<I>,
216}
217
218impl<I> ItemDetails<I> {
219    /// Construct a stable handle value from the item details
220    fn create_handle(&self) -> Handle<I> {
221        Handle {
222            index: self.index as u32,
223            epoch: self.interned_epoch,
224            _marker: PhantomData,
225        }
226    }
227}
228
229/// The main interning data structure. This lives in the
230/// scene builder thread, and handles hashing and interning
231/// unique data structures. It also manages a free-list for
232/// the items in the data store, which is synchronized via
233/// an update list of additions / removals.
234#[cfg_attr(feature = "capture", derive(Serialize))]
235#[cfg_attr(feature = "replay", derive(Deserialize))]
236#[derive(MallocSizeOf)]
237pub struct Interner<I: Internable> {
238    /// Uniquely map an interning key to a handle
239    map: FastHashMap<I::Key, ItemDetails<I>>,
240    /// List of free slots in the data store for re-use.
241    free_list: Vec<usize>,
242    /// Pending list of updates that need to be applied.
243    update_list: UpdateList<I::Key>,
244    /// The current epoch for the interner.
245    current_epoch: Epoch,
246    /// The information associated with each interned
247    /// item that can be accessed by the interner.
248    local_data: Vec<I::InternData>,
249}
250
251impl<I: Internable> Default for Interner<I> {
252    fn default() -> Self {
253        Interner {
254            map: FastHashMap::default(),
255            free_list: Vec::new(),
256            update_list: UpdateList::new(),
257            current_epoch: Epoch(1),
258            local_data: Vec::new(),
259        }
260    }
261}
262
263impl<I: Internable> Interner<I> {
264    /// Intern a data structure, and return a handle to
265    /// that data. The handle can then be stored in the
266    /// frame builder, and safely accessed via the data
267    /// store that lives in the frame builder thread.
268    /// The provided closure is invoked to build the
269    /// local data about an interned structure if the
270    /// key isn't already interned.
271    pub fn intern<F>(
272        &mut self,
273        data: &I::Key,
274        fun: F,
275    ) -> Handle<I> where F: FnOnce() -> I::InternData {
276        // Use get_mut rather than entry here to avoid
277        // cloning the (sometimes large) key in the common
278        // case, where the data already exists in the interner.
279        if let Some(details) = self.map.get_mut(data) {
280            // Update the last referenced frame for this element
281            details.last_used_epoch = self.current_epoch;
282            // Return a stable handle value for dependency checking
283            return details.create_handle();
284        }
285
286        // We need to intern a new data item. First, find out
287        // if there is a spare slot in the free-list that we
288        // can use. Otherwise, append to the end of the list.
289        let index = match self.free_list.pop() {
290            Some(index) => index,
291            None => self.local_data.len(),
292        };
293
294        // Generate a handle for access via the data store.
295        let handle = Handle {
296            index: index as u32,
297            epoch: self.current_epoch,
298            _marker: PhantomData,
299        };
300
301        let uid = handle.uid();
302
303        // Add a pending update to insert the new data.
304        self.update_list.insertions.push(Insertion {
305            index,
306            uid,
307            value: data.clone(),
308        });
309
310        #[cfg(debug_assertions)]
311        data.on_interned(uid);
312
313        // Store this handle so the next time it is
314        // interned, it gets re-used.
315        self.map.insert(data.clone(), ItemDetails {
316            interned_epoch: self.current_epoch,
317            last_used_epoch: self.current_epoch,
318            index,
319            _marker: PhantomData,
320        });
321
322        // Create the local data for this item that is
323        // being interned.
324        self.local_data.entry(index).set(fun());
325
326        handle
327    }
328
329    /// Retrieve the pending list of updates for an interner
330    /// that need to be applied to the data store. Also run
331    /// a GC step that removes old entries.
332    pub fn end_frame_and_get_pending_updates(&mut self) -> UpdateList<I::Key> {
333        let mut update_list = self.update_list.take_and_preallocate();
334
335        let free_list = &mut self.free_list;
336        let current_epoch = self.current_epoch.0;
337
338        // First, run a GC step. Walk through the handles, and
339        // if we find any that haven't been used for some time,
340        // remove them. If this ever shows up in profiles, we
341        // can make the GC step partial (scan only part of the
342        // map each frame). It also might make sense in the
343        // future to adjust how long items remain in the cache
344        // based on the current size of the list.
345        self.map.retain(|_, details| {
346            if details.last_used_epoch.0 + 10 < current_epoch {
347                // To expire an item:
348                //  - Add index to the free-list for re-use.
349                //  - Add an update to the data store to invalidate this slot.
350                //  - Remove from the hash map.
351                free_list.push(details.index);
352                update_list.removals.push(Removal {
353                    index: details.index,
354                    uid: details.create_handle().uid(),
355                });
356                return false;
357            }
358
359            true
360        });
361
362        // Begin the next epoch
363        self.current_epoch = Epoch(self.current_epoch.0 + 1);
364
365        update_list
366    }
367}
368
369/// Retrieve the local data for an item from the interner via handle
370impl<I: Internable> ops::Index<Handle<I>> for Interner<I> {
371    type Output = I::InternData;
372    fn index(&self, handle: Handle<I>) -> &I::InternData {
373        &self.local_data[handle.index as usize]
374    }
375}
376
377/// Meta-macro to enumerate the various interner identifiers and types.
378///
379/// IMPORTANT: Keep this synchronized with the list in mozilla-central located at
380/// gfx/webrender_bindings/webrender_ffi.h
381///
382/// Note that this could be a lot less verbose if concat_idents! were stable. :-(
383#[macro_export]
384macro_rules! enumerate_interners {
385    ($macro_name: ident) => {
386        $macro_name! {
387            clip: ClipIntern,
388            prim: PrimitiveKeyKind,
389            normal_border: NormalBorderPrim,
390            image_border: ImageBorder,
391            image: Image,
392            yuv_image: YuvImage,
393            line_decoration: LineDecoration,
394            linear_grad: LinearGradient,
395            radial_grad: RadialGradient,
396            conic_grad: ConicGradient,
397            picture: Picture,
398            text_run: TextRun,
399            filter_data: FilterDataIntern,
400            backdrop_capture: BackdropCapture,
401            backdrop_render: BackdropRender,
402            polygon: PolygonIntern,
403            box_shadow: BoxShadow,
404        }
405    }
406}
407
408macro_rules! declare_interning_memory_report {
409    ( $( $name:ident: $ty:ident, )+ ) => {
410        ///
411        #[repr(C)]
412        #[derive(AddAssign, Clone, Debug, Default)]
413        pub struct InternerSubReport {
414            $(
415                ///
416                pub $name: usize,
417            )+
418        }
419    }
420}
421
422enumerate_interners!(declare_interning_memory_report);
423
424/// Memory report for interning-related data structures.
425/// cbindgen:derive-eq=false
426/// cbindgen:derive-ostream=false
427#[repr(C)]
428#[derive(Clone, Debug, Default)]
429pub struct InterningMemoryReport {
430    ///
431    pub interners: InternerSubReport,
432    ///
433    pub data_stores: InternerSubReport,
434}
435
436impl ::std::ops::AddAssign for InterningMemoryReport {
437    fn add_assign(&mut self, other: InterningMemoryReport) {
438        self.interners += other.interners;
439        self.data_stores += other.data_stores;
440    }
441}
442
443// The trick to make trait bounds configurable by features.
444mod dummy {
445    #[cfg(not(feature = "capture"))]
446    pub trait Serialize {}
447    #[cfg(not(feature = "capture"))]
448    impl<T> Serialize for T {}
449    #[cfg(not(feature = "replay"))]
450    pub trait Deserialize<'a> {}
451    #[cfg(not(feature = "replay"))]
452    impl<'a, T> Deserialize<'a> for T {}
453}
454#[cfg(feature = "capture")]
455use serde::Serialize as InternSerialize;
456#[cfg(not(feature = "capture"))]
457use self::dummy::Serialize as InternSerialize;
458#[cfg(feature = "replay")]
459use serde::Deserialize as InternDeserialize;
460#[cfg(not(feature = "replay"))]
461use self::dummy::Deserialize as InternDeserialize;
462
463/// Implement `Internable` for a type that wants to participate in interning.
464pub trait Internable: MallocSizeOf {
465    type Key: Eq + Hash + Clone + Debug + MallocSizeOf + InternDebug + InternSerialize + for<'a> InternDeserialize<'a>;
466    type StoreData: From<Self::Key> + MallocSizeOf + InternSerialize + for<'a> InternDeserialize<'a>;
467    type InternData: MallocSizeOf + InternSerialize + for<'a> InternDeserialize<'a>;
468
469    // Profile counter indices, see the list in profiler.rs
470    const PROFILE_COUNTER: usize;
471}