Skip to main content

webrender/
picture_textures.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
5use std::mem;
6use smallvec::SmallVec;
7use api::{ImageFormat, ImageBufferKind, DebugFlags, TextureCacheCategory};
8use api::units::*;
9use crate::device::TextureFilter;
10use crate::internal_types::{
11    CacheTextureId, TextureUpdateList, Swizzle, TextureCacheAllocInfo,
12    TextureSource, FrameStamp, FrameId,
13};
14use crate::profiler::{self, TransactionProfile};
15use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
16
17#[derive(Debug, PartialEq)]
18#[cfg_attr(feature = "capture", derive(Serialize))]
19#[cfg_attr(feature = "replay", derive(Deserialize))]
20pub enum PictureCacheEntryMarker {}
21
22malloc_size_of::malloc_size_of_is_0!(PictureCacheEntryMarker);
23
24pub type PictureCacheTextureHandle = WeakFreeListHandle<PictureCacheEntryMarker>;
25
26use std::cmp;
27
28// Stores information related to a single entry in the texture
29// cache. This is stored for each item whether it's in the shared
30// cache or a standalone texture.
31#[derive(Debug)]
32#[cfg_attr(feature = "capture", derive(Serialize))]
33#[cfg_attr(feature = "replay", derive(Deserialize))]
34pub struct PictureCacheEntry {
35    /// The last frame this item was requested for rendering.
36    pub last_access: FrameStamp,
37    /// The actual device texture ID this is part of.
38    pub texture_id: CacheTextureId,
39}
40
41/// The textures used to hold picture cache tiles.
42#[cfg_attr(feature = "capture", derive(Serialize))]
43#[cfg_attr(feature = "replay", derive(Deserialize))]
44struct PictureTexture {
45    texture_id: CacheTextureId,
46    size: DeviceIntSize,
47    is_allocated: bool,
48    last_frame_used: FrameId,
49}
50
51/// The textures used to hold picture cache tiles.
52#[cfg_attr(feature = "capture", derive(Serialize))]
53#[cfg_attr(feature = "replay", derive(Deserialize))]
54pub struct PictureTextures {
55    /// Current list of textures in the pool
56    textures: Vec<PictureTexture>,
57    /// Default tile size for content tiles
58    default_tile_size: DeviceIntSize,
59    /// Number of currently allocated textures in the pool
60    allocated_texture_count: usize,
61    /// Texture filter to use for picture cache textures
62    filter: TextureFilter,
63
64    debug_flags: DebugFlags,
65
66    /// Cache of picture cache entries.
67    cache_entries: FreeList<PictureCacheEntry, PictureCacheEntryMarker>,
68    /// Strong handles for the picture_cache_entries FreeList.
69    cache_handles: Vec<FreeListHandle<PictureCacheEntryMarker>>,
70
71    now: FrameStamp,
72}
73
74impl PictureTextures {
75    pub fn new(
76        default_tile_size: DeviceIntSize,
77        filter: TextureFilter,
78    ) -> Self {
79        PictureTextures {
80            textures: Vec::new(),
81            default_tile_size,
82            allocated_texture_count: 0,
83            filter,
84            debug_flags: DebugFlags::empty(),
85            cache_entries: FreeList::new(),
86            cache_handles: Vec::new(),
87            now: FrameStamp::INVALID,
88        }
89    }
90
91    pub fn begin_frame(&mut self, stamp: FrameStamp, pending_updates: &mut TextureUpdateList) {
92        self.now = stamp;
93
94        // Expire picture cache tiles that haven't been referenced in the last frame.
95        // The picture cache code manually keeps tiles alive by calling `request` on
96        // them if it wants to retain a tile that is currently not visible.
97        self.expire_old_tiles(pending_updates);
98    }
99
100    pub fn default_tile_size(&self) -> DeviceIntSize {
101        self.default_tile_size
102    }
103
104    pub fn update(
105        &mut self,
106        tile_size: DeviceIntSize,
107        handle: &mut Option<PictureCacheTextureHandle>,
108        next_texture_id: &mut CacheTextureId,
109        pending_updates: &mut TextureUpdateList,
110    ) {
111        debug_assert!(self.now.is_valid());
112        debug_assert!(tile_size.width > 0 && tile_size.height > 0);
113
114        let need_alloc = match handle {
115            None => true,
116            Some(handle) => {
117                // Check if the entry has been evicted.
118                !self.entry_exists(&handle)
119            },
120        };
121
122        if need_alloc {
123            let new_handle = self.get_or_allocate_tile(
124                tile_size,
125                next_texture_id,
126                pending_updates,
127            );
128
129            *handle = Some(new_handle);
130        }
131
132        assert!(handle.is_some(), "The handle should be valid picture cache handle now");
133    }
134
135    pub fn get_or_allocate_tile(
136        &mut self,
137        tile_size: DeviceIntSize,
138        next_texture_id: &mut CacheTextureId,
139        pending_updates: &mut TextureUpdateList,
140    ) -> PictureCacheTextureHandle {
141        let mut texture_id = None;
142        self.allocated_texture_count += 1;
143
144        for texture in &mut self.textures {
145            if texture.size == tile_size && !texture.is_allocated {
146                // Found a target that's not currently in use which matches. Update
147                // the last_frame_used for GC purposes.
148                texture.is_allocated = true;
149                texture.last_frame_used = FrameId::INVALID;
150                texture_id = Some(texture.texture_id);
151                break;
152            }
153        }
154
155        // Need to create a new render target and add it to the pool
156
157        let texture_id = texture_id.unwrap_or_else(|| {
158            let texture_id = *next_texture_id;
159            next_texture_id.0 += 1;
160
161            // Push a command to allocate device storage of the right size / format.
162            let info = TextureCacheAllocInfo {
163                target: ImageBufferKind::Texture2D,
164                width: tile_size.width,
165                height: tile_size.height,
166                format: ImageFormat::RGBA8,
167                filter: self.filter,
168                is_shared_cache: false,
169                has_depth: true,
170                category: TextureCacheCategory::PictureTile,
171            };
172
173            pending_updates.push_alloc(texture_id, info);
174
175            self.textures.push(PictureTexture {
176                texture_id,
177                is_allocated: true,
178                size: tile_size,
179                last_frame_used: FrameId::INVALID,
180            });
181
182            texture_id
183        });
184
185        let cache_entry = PictureCacheEntry {
186            last_access: self.now,
187            texture_id,
188        };
189
190        // Add the cache entry to the picture_textures.cache_entries FreeList.
191        let strong_handle = self.cache_entries.insert(cache_entry);
192        let new_handle = strong_handle.weak();
193
194        self.cache_handles.push(strong_handle);
195
196        new_handle
197    }
198
199    pub fn free_tile(
200        &mut self,
201        id: CacheTextureId,
202        current_frame_id: FrameId,
203        pending_updates: &mut TextureUpdateList,
204    ) {
205        self.allocated_texture_count -= 1;
206
207        let texture = self.textures
208            .iter_mut()
209            .find(|t| t.texture_id == id)
210            .expect("bug: invalid texture id");
211
212        assert!(texture.is_allocated);
213        texture.is_allocated = false;
214
215        assert_eq!(texture.last_frame_used, FrameId::INVALID);
216        texture.last_frame_used = current_frame_id;
217
218        if self.debug_flags.contains(
219            DebugFlags::TEXTURE_CACHE_DBG |
220            DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED)
221        {
222            pending_updates.push_debug_clear(
223                id,
224                DeviceIntPoint::zero(),
225                texture.size.width,
226                texture.size.height,
227            );
228        }
229    }
230
231    pub fn request(&mut self, handle: &PictureCacheTextureHandle) -> bool {
232        let entry = self.cache_entries.get_opt_mut(handle);
233        let now = self.now;
234        entry.map_or(true, |entry| {
235            // If an image is requested that is already in the cache,
236            // refresh the GPU cache data associated with this item.
237            entry.last_access = now;
238            false
239        })
240    }
241
242    pub fn get_texture_source(&self, handle: &PictureCacheTextureHandle) -> TextureSource {
243        let entry = self.cache_entries.get_opt(handle)
244            .expect("BUG: was dropped from cache or not updated!");
245
246        debug_assert_eq!(entry.last_access, self.now);
247
248        TextureSource::TextureCache(entry.texture_id, Swizzle::default())
249    }
250
251    /// Expire picture cache tiles that haven't been referenced in the last frame.
252    /// The picture cache code manually keeps tiles alive by calling `request` on
253    /// them if it wants to retain a tile that is currently not visible.
254    pub fn expire_old_tiles(&mut self, pending_updates: &mut TextureUpdateList) {
255        for i in (0 .. self.cache_handles.len()).rev() {
256            let evict = {
257                let entry = self.cache_entries.get(
258                    &self.cache_handles[i]
259                );
260
261                // This function is called at the beginning of the frame,
262                // so we don't yet know which picture cache tiles will be
263                // requested this frame. Therefore only evict picture cache
264                // tiles which weren't requested in the *previous* frame.
265                entry.last_access.frame_id() < self.now.frame_id() - 1
266            };
267
268            if evict {
269                let handle = self.cache_handles.swap_remove(i);
270                let entry = self.cache_entries.free(handle);
271                self.free_tile(entry.texture_id, self.now.frame_id(), pending_updates);
272            }
273        }
274    }
275
276    pub fn clear(&mut self, pending_updates: &mut TextureUpdateList) {
277        for handle in mem::take(&mut self.cache_handles) {
278            let entry = self.cache_entries.free(handle);
279            self.free_tile(entry.texture_id, self.now.frame_id(), pending_updates);
280        }
281
282        for texture in self.textures.drain(..) {
283            pending_updates.push_free(texture.texture_id);
284        }
285    }
286
287    pub fn update_profile(&self, profile: &mut TransactionProfile) {
288        profile.set(profiler::PICTURE_TILES, self.textures.len());
289    }
290
291    /// Simple garbage collect of picture cache tiles
292    pub fn gc(
293        &mut self,
294        pending_updates: &mut TextureUpdateList,
295    ) {
296        // Allow the picture cache pool to keep 25% of the current allocated tile count
297        // as free textures to be reused. This ensures the allowed tile count is appropriate
298        // based on current window size.
299        let free_texture_count = self.textures.len() - self.allocated_texture_count;
300        let allowed_retained_count = (self.allocated_texture_count as f32 * 0.25).ceil() as usize;
301        let do_gc = free_texture_count > allowed_retained_count;
302
303        if do_gc {
304            // Sort the current pool by age, so that we remove oldest textures first
305            self.textures.sort_unstable_by_key(|t| cmp::Reverse(t.last_frame_used));
306
307            // We can't just use retain() because `PictureTexture` requires manual cleanup.
308            let mut allocated_targets = SmallVec::<[PictureTexture; 32]>::new();
309            let mut retained_targets = SmallVec::<[PictureTexture; 32]>::new();
310
311            for target in self.textures.drain(..) {
312                if target.is_allocated {
313                    // Allocated targets can't be collected
314                    allocated_targets.push(target);
315                } else if retained_targets.len() < allowed_retained_count {
316                    // Retain the most recently used targets up to the allowed count
317                    retained_targets.push(target);
318                } else {
319                    // The rest of the targets get freed
320                    assert_ne!(target.last_frame_used, FrameId::INVALID);
321                    pending_updates.push_free(target.texture_id);
322                }
323            }
324
325            self.textures.extend(retained_targets);
326            self.textures.extend(allocated_targets);
327        }
328    }
329
330    pub fn entry_exists(&self, handle: &PictureCacheTextureHandle) -> bool {
331        self.cache_entries.get_opt(handle).is_some()
332    }
333
334    pub fn set_debug_flags(&mut self, flags: DebugFlags) {
335        self.debug_flags = flags;
336    }
337
338    #[cfg(feature = "replay")]
339    pub fn filter(&self) -> TextureFilter {
340        self.filter
341    }
342}