diff options
Diffstat (limited to 'gfx/wr/webrender/src/picture_textures.rs')
-rw-r--r-- | gfx/wr/webrender/src/picture_textures.rs | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/picture_textures.rs b/gfx/wr/webrender/src/picture_textures.rs new file mode 100644 index 0000000000..4345951bb9 --- /dev/null +++ b/gfx/wr/webrender/src/picture_textures.rs @@ -0,0 +1,382 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::mem; +use smallvec::SmallVec; +use api::{ImageFormat, ImageBufferKind, DebugFlags}; +use api::units::*; +use crate::device::TextureFilter; +use crate::internal_types::{ + CacheTextureId, TextureUpdateList, Swizzle, TextureCacheAllocInfo, TextureCacheCategory, + TextureSource, FrameStamp, FrameId, +}; +use crate::profiler::{self, TransactionProfile}; +use crate::gpu_types::{ImageSource, UvRectKind}; +use crate::gpu_cache::{GpuCache, GpuCacheHandle}; +use crate::freelist::{FreeList, FreeListHandle, WeakFreeListHandle}; + + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub enum PictureCacheEntryMarker {} + +malloc_size_of::malloc_size_of_is_0!(PictureCacheEntryMarker); + +pub type PictureCacheTextureHandle = WeakFreeListHandle<PictureCacheEntryMarker>; + +use std::cmp; + +// Stores information related to a single entry in the texture +// cache. This is stored for each item whether it's in the shared +// cache or a standalone texture. +#[derive(Debug)] +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PictureCacheEntry { + /// Size of the requested tile. + pub size: DeviceIntSize, + /// The last frame this item was requested for rendering. + // TODO(gw): This stamp is only used for picture cache tiles, and some checks + // in the glyph cache eviction code. We could probably remove it + // entirely in future (or move to EntryDetails::Picture). + pub last_access: FrameStamp, + /// Handle to the resource rect in the GPU cache. + pub uv_rect_handle: GpuCacheHandle, + /// Image format of the data that the entry expects. + pub filter: TextureFilter, + /// The actual device texture ID this is part of. + pub texture_id: CacheTextureId, +} + +impl PictureCacheEntry { + fn update_gpu_cache(&mut self, gpu_cache: &mut GpuCache) { + if let Some(mut request) = gpu_cache.request(&mut self.uv_rect_handle) { + let origin = DeviceIntPoint::zero(); + let image_source = ImageSource { + p0: origin.to_f32(), + p1: (origin + self.size).to_f32(), + uv_rect_kind: UvRectKind::Rect, + user_data: [0.0; 4], + }; + image_source.write_gpu_blocks(&mut request); + } + } +} + +/// The textures used to hold picture cache tiles. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +struct PictureTexture { + texture_id: CacheTextureId, + size: DeviceIntSize, + is_allocated: bool, + last_frame_used: FrameId, +} + +/// The textures used to hold picture cache tiles. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct PictureTextures { + /// Current list of textures in the pool + textures: Vec<PictureTexture>, + /// Default tile size for content tiles + default_tile_size: DeviceIntSize, + /// Number of currently allocated textures in the pool + allocated_texture_count: usize, + /// Texture filter to use for picture cache textures + filter: TextureFilter, + + debug_flags: DebugFlags, + + /// Cache of picture cache entries. + cache_entries: FreeList<PictureCacheEntry, PictureCacheEntryMarker>, + /// Strong handles for the picture_cache_entries FreeList. + cache_handles: Vec<FreeListHandle<PictureCacheEntryMarker>>, + + now: FrameStamp, +} + +impl PictureTextures { + pub fn new( + default_tile_size: DeviceIntSize, + filter: TextureFilter, + ) -> Self { + PictureTextures { + textures: Vec::new(), + default_tile_size, + allocated_texture_count: 0, + filter, + debug_flags: DebugFlags::empty(), + cache_entries: FreeList::new(), + cache_handles: Vec::new(), + now: FrameStamp::INVALID, + } + } + + pub fn begin_frame(&mut self, stamp: FrameStamp, pending_updates: &mut TextureUpdateList) { + self.now = stamp; + + // Expire picture cache tiles that haven't been referenced in the last frame. + // The picture cache code manually keeps tiles alive by calling `request` on + // them if it wants to retain a tile that is currently not visible. + self.expire_old_tiles(pending_updates); + } + + pub fn default_tile_size(&self) -> DeviceIntSize { + self.default_tile_size + } + + pub fn update( + &mut self, + tile_size: DeviceIntSize, + handle: &mut Option<PictureCacheTextureHandle>, + gpu_cache: &mut GpuCache, + next_texture_id: &mut CacheTextureId, + pending_updates: &mut TextureUpdateList, + ) { + debug_assert!(self.now.is_valid()); + debug_assert!(tile_size.width > 0 && tile_size.height > 0); + + let need_alloc = match handle { + None => true, + Some(handle) => { + // Check if the entry has been evicted. + !self.entry_exists(&handle) + }, + }; + + if need_alloc { + let new_handle = self.get_or_allocate_tile( + tile_size, + next_texture_id, + pending_updates, + ); + + *handle = Some(new_handle); + } + + if let Some(handle) = handle { + // Upload the resource rect and texture array layer. + self.cache_entries + .get_opt_mut(handle) + .expect("BUG: handle must be valid now") + .update_gpu_cache(gpu_cache); + } else { + panic!("The handle should be valid picture cache handle now") + } + } + + pub fn get_or_allocate_tile( + &mut self, + tile_size: DeviceIntSize, + next_texture_id: &mut CacheTextureId, + pending_updates: &mut TextureUpdateList, + ) -> PictureCacheTextureHandle { + let mut texture_id = None; + self.allocated_texture_count += 1; + + for texture in &mut self.textures { + if texture.size == tile_size && !texture.is_allocated { + // Found a target that's not currently in use which matches. Update + // the last_frame_used for GC purposes. + texture.is_allocated = true; + texture.last_frame_used = FrameId::INVALID; + texture_id = Some(texture.texture_id); + break; + } + } + + // Need to create a new render target and add it to the pool + + let texture_id = texture_id.unwrap_or_else(|| { + let texture_id = *next_texture_id; + next_texture_id.0 += 1; + + // Push a command to allocate device storage of the right size / format. + let info = TextureCacheAllocInfo { + target: ImageBufferKind::Texture2D, + width: tile_size.width, + height: tile_size.height, + format: ImageFormat::RGBA8, + filter: self.filter, + is_shared_cache: false, + has_depth: true, + category: TextureCacheCategory::PictureTile, + }; + + pending_updates.push_alloc(texture_id, info); + + self.textures.push(PictureTexture { + texture_id, + is_allocated: true, + size: tile_size, + last_frame_used: FrameId::INVALID, + }); + + texture_id + }); + + let cache_entry = PictureCacheEntry { + size: tile_size, + last_access: self.now, + uv_rect_handle: GpuCacheHandle::new(), + filter: self.filter, + texture_id, + }; + + // Add the cache entry to the picture_textures.cache_entries FreeList. + let strong_handle = self.cache_entries.insert(cache_entry); + let new_handle = strong_handle.weak(); + + self.cache_handles.push(strong_handle); + + new_handle + } + + pub fn free_tile( + &mut self, + id: CacheTextureId, + current_frame_id: FrameId, + pending_updates: &mut TextureUpdateList, + ) { + self.allocated_texture_count -= 1; + + let texture = self.textures + .iter_mut() + .find(|t| t.texture_id == id) + .expect("bug: invalid texture id"); + + assert!(texture.is_allocated); + texture.is_allocated = false; + + assert_eq!(texture.last_frame_used, FrameId::INVALID); + texture.last_frame_used = current_frame_id; + + if self.debug_flags.contains( + DebugFlags::TEXTURE_CACHE_DBG | + DebugFlags::TEXTURE_CACHE_DBG_CLEAR_EVICTED) + { + pending_updates.push_debug_clear( + id, + DeviceIntPoint::zero(), + texture.size.width, + texture.size.height, + ); + } + } + + pub fn request(&mut self, handle: &PictureCacheTextureHandle, gpu_cache: &mut GpuCache) -> bool { + let entry = self.cache_entries.get_opt_mut(handle); + let now = self.now; + entry.map_or(true, |entry| { + // If an image is requested that is already in the cache, + // refresh the GPU cache data associated with this item. + entry.last_access = now; + entry.update_gpu_cache(gpu_cache); + false + }) + } + + pub fn get_texture_source(&self, handle: &PictureCacheTextureHandle) -> TextureSource { + let entry = self.cache_entries.get_opt(handle) + .expect("BUG: was dropped from cache or not updated!"); + + debug_assert_eq!(entry.last_access, self.now); + + TextureSource::TextureCache(entry.texture_id, Swizzle::default()) + } + + /// Expire picture cache tiles that haven't been referenced in the last frame. + /// The picture cache code manually keeps tiles alive by calling `request` on + /// them if it wants to retain a tile that is currently not visible. + pub fn expire_old_tiles(&mut self, pending_updates: &mut TextureUpdateList) { + for i in (0 .. self.cache_handles.len()).rev() { + let evict = { + let entry = self.cache_entries.get( + &self.cache_handles[i] + ); + + // This function is called at the beginning of the frame, + // so we don't yet know which picture cache tiles will be + // requested this frame. Therefore only evict picture cache + // tiles which weren't requested in the *previous* frame. + entry.last_access.frame_id() < self.now.frame_id() - 1 + }; + + if evict { + let handle = self.cache_handles.swap_remove(i); + let entry = self.cache_entries.free(handle); + self.free_tile(entry.texture_id, self.now.frame_id(), pending_updates); + } + } + } + + pub fn clear(&mut self, pending_updates: &mut TextureUpdateList) { + for handle in mem::take(&mut self.cache_handles) { + let entry = self.cache_entries.free(handle); + self.free_tile(entry.texture_id, self.now.frame_id(), pending_updates); + } + + for texture in self.textures.drain(..) { + pending_updates.push_free(texture.texture_id); + } + } + + pub fn update_profile(&self, profile: &mut TransactionProfile) { + profile.set(profiler::PICTURE_TILES, self.textures.len()); + } + + /// Simple garbage collect of picture cache tiles + pub fn gc( + &mut self, + pending_updates: &mut TextureUpdateList, + ) { + // Allow the picture cache pool to keep 25% of the current allocated tile count + // as free textures to be reused. This ensures the allowed tile count is appropriate + // based on current window size. + let free_texture_count = self.textures.len() - self.allocated_texture_count; + let allowed_retained_count = (self.allocated_texture_count as f32 * 0.25).ceil() as usize; + let do_gc = free_texture_count > allowed_retained_count; + + if do_gc { + // Sort the current pool by age, so that we remove oldest textures first + self.textures.sort_unstable_by_key(|t| cmp::Reverse(t.last_frame_used)); + + // We can't just use retain() because `PictureTexture` requires manual cleanup. + let mut allocated_targets = SmallVec::<[PictureTexture; 32]>::new(); + let mut retained_targets = SmallVec::<[PictureTexture; 32]>::new(); + + for target in self.textures.drain(..) { + if target.is_allocated { + // Allocated targets can't be collected + allocated_targets.push(target); + } else if retained_targets.len() < allowed_retained_count { + // Retain the most recently used targets up to the allowed count + retained_targets.push(target); + } else { + // The rest of the targets get freed + assert_ne!(target.last_frame_used, FrameId::INVALID); + pending_updates.push_free(target.texture_id); + } + } + + self.textures.extend(retained_targets); + self.textures.extend(allocated_targets); + } + } + + pub fn entry_exists(&self, handle: &PictureCacheTextureHandle) -> bool { + self.cache_entries.get_opt(handle).is_some() + } + + pub fn set_debug_flags(&mut self, flags: DebugFlags) { + self.debug_flags = flags; + } + + #[cfg(feature = "replay")] + pub fn filter(&self) -> TextureFilter { + self.filter + } +} |