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