tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

picture_textures.rs (12222B)


      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 use std::mem;
      6 use smallvec::SmallVec;
      7 use api::{ImageFormat, ImageBufferKind, DebugFlags, TextureCacheCategory};
      8 use api::units::*;
      9 use crate::device::TextureFilter;
     10 use crate::internal_types::{
     11    CacheTextureId, TextureUpdateList, Swizzle, TextureCacheAllocInfo,
     12    TextureSource, FrameStamp, FrameId,
     13 };
     14 use crate::profiler::{self, TransactionProfile};
     15 use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
     16 
     17 #[derive(Debug, PartialEq)]
     18 #[cfg_attr(feature = "capture", derive(Serialize))]
     19 #[cfg_attr(feature = "replay", derive(Deserialize))]
     20 pub enum PictureCacheEntryMarker {}
     21 
     22 malloc_size_of::malloc_size_of_is_0!(PictureCacheEntryMarker);
     23 
     24 pub type PictureCacheTextureHandle = WeakFreeListHandle<PictureCacheEntryMarker>;
     25 
     26 use 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))]
     34 pub 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))]
     44 struct 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))]
     54 pub 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 
     74 impl 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 }