summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/resource_cache.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/resource_cache.rs')
-rw-r--r--gfx/wr/webrender/src/resource_cache.rs2308
1 files changed, 2308 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/resource_cache.rs b/gfx/wr/webrender/src/resource_cache.rs
new file mode 100644
index 0000000000..9459bf86da
--- /dev/null
+++ b/gfx/wr/webrender/src/resource_cache.rs
@@ -0,0 +1,2308 @@
+/* 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 api::{BlobImageRequest, RasterizedBlobImage, ImageFormat, ImageDescriptorFlags};
+use api::{DebugFlags, FontInstanceKey, FontKey, FontTemplate, GlyphIndex};
+use api::{ExternalImageData, ExternalImageType, ExternalImageId, BlobImageResult};
+use api::{DirtyRect, GlyphDimensions, IdNamespace, DEFAULT_TILE_SIZE};
+use api::{ColorF, ImageData, ImageDescriptor, ImageKey, ImageRendering, TileSize};
+use api::{BlobImageHandler, BlobImageKey, VoidPtrToSizeFn};
+use api::units::*;
+use euclid::size2;
+use crate::{render_api::{ClearCache, AddFont, ResourceUpdate, MemoryReport}, util::WeakTable};
+use crate::image_tiling::{compute_tile_size, compute_tile_range};
+#[cfg(feature = "capture")]
+use crate::capture::ExternalCaptureImage;
+#[cfg(feature = "replay")]
+use crate::capture::PlainExternalImage;
+#[cfg(any(feature = "replay", feature = "png", feature="capture"))]
+use crate::capture::CaptureConfig;
+use crate::composite::{NativeSurfaceId, NativeSurfaceOperation, NativeTileId, NativeSurfaceOperationDetails};
+use crate::device::TextureFilter;
+use crate::glyph_cache::{GlyphCache, CachedGlyphInfo};
+use crate::glyph_cache::GlyphCacheEntry;
+use glyph_rasterizer::{GLYPH_FLASHING, FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer, GlyphRasterJob};
+use glyph_rasterizer::{SharedFontResources, BaseFontInstance};
+use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
+use crate::gpu_types::UvRectKind;
+use crate::internal_types::{
+ CacheTextureId, FastHashMap, FastHashSet, TextureSource, ResourceUpdateList,
+ FrameId, FrameStamp,
+};
+use crate::profiler::{self, TransactionProfile, bytes_to_mb};
+use crate::render_task_graph::{RenderTaskId, RenderTaskGraphBuilder};
+use crate::render_task_cache::{RenderTaskCache, RenderTaskCacheKey, RenderTaskParent};
+use crate::render_task_cache::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle};
+use crate::renderer::GpuBufferBuilder;
+use crate::surface::SurfaceBuilder;
+use euclid::point2;
+use smallvec::SmallVec;
+use std::collections::hash_map::Entry::{self, Occupied, Vacant};
+use std::collections::hash_map::{Iter, IterMut};
+use std::collections::VecDeque;
+use std::{cmp, mem};
+use std::fmt::Debug;
+use std::hash::Hash;
+use std::os::raw::c_void;
+#[cfg(any(feature = "capture", feature = "replay"))]
+use std::path::PathBuf;
+use std::sync::Arc;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::u32;
+use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction, TargetShader};
+use crate::picture_textures::PictureTextures;
+use peek_poke::PeekPoke;
+
+// Counter for generating unique native surface ids
+static NEXT_NATIVE_SURFACE_ID: AtomicUsize = AtomicUsize::new(0);
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct GlyphFetchResult {
+ pub index_in_text_run: i32,
+ pub uv_rect_address: GpuCacheAddress,
+ pub offset: DevicePoint,
+ pub size: DeviceIntSize,
+ pub scale: f32,
+}
+
+// These coordinates are always in texels.
+// They are converted to normalized ST
+// values in the vertex shader. The reason
+// for this is that the texture may change
+// dimensions (e.g. the pages in a texture
+// atlas can grow). When this happens, by
+// storing the coordinates as texel values
+// we don't need to go through and update
+// various CPU-side structures.
+#[derive(Debug, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct CacheItem {
+ pub texture_id: TextureSource,
+ pub uv_rect_handle: GpuCacheHandle,
+ pub uv_rect: DeviceIntRect,
+ pub user_data: [f32; 4],
+}
+
+impl CacheItem {
+ pub fn invalid() -> Self {
+ CacheItem {
+ texture_id: TextureSource::Invalid,
+ uv_rect_handle: GpuCacheHandle::new(),
+ uv_rect: DeviceIntRect::zero(),
+ user_data: [0.0; 4],
+ }
+ }
+
+ pub fn is_valid(&self) -> bool {
+ self.texture_id != TextureSource::Invalid
+ }
+}
+
+/// Represents the backing store of an image in the cache.
+/// This storage can take several forms.
+#[derive(Clone, Debug)]
+pub enum CachedImageData {
+ /// A simple series of bytes, provided by the embedding and owned by WebRender.
+ /// The format is stored out-of-band, currently in ImageDescriptor.
+ Raw(Arc<Vec<u8>>),
+ /// An series of commands that can be rasterized into an image via an
+ /// embedding-provided callback.
+ ///
+ /// The commands are stored elsewhere and this variant is used as a placeholder.
+ Blob,
+ /// An image owned by the embedding, and referenced by WebRender. This may
+ /// take the form of a texture or a heap-allocated buffer.
+ External(ExternalImageData),
+}
+
+impl From<ImageData> for CachedImageData {
+ fn from(img_data: ImageData) -> Self {
+ match img_data {
+ ImageData::Raw(data) => CachedImageData::Raw(data),
+ ImageData::External(data) => CachedImageData::External(data),
+ }
+ }
+}
+
+impl CachedImageData {
+ /// Returns true if this represents a blob.
+ #[inline]
+ pub fn is_blob(&self) -> bool {
+ match *self {
+ CachedImageData::Blob => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this variant of CachedImageData should go through the texture
+ /// cache.
+ #[inline]
+ pub fn uses_texture_cache(&self) -> bool {
+ match *self {
+ CachedImageData::External(ref ext_data) => match ext_data.image_type {
+ ExternalImageType::TextureHandle(_) => false,
+ ExternalImageType::Buffer => true,
+ },
+ CachedImageData::Blob => true,
+ CachedImageData::Raw(_) => true,
+ }
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ImageProperties {
+ pub descriptor: ImageDescriptor,
+ pub external_image: Option<ExternalImageData>,
+ pub tiling: Option<TileSize>,
+ // Potentially a subset of the image's total rectangle. This rectangle is what
+ // we map to the (layout space) display item bounds.
+ pub visible_rect: DeviceIntRect,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+enum State {
+ Idle,
+ AddResources,
+ QueryResources,
+}
+
+/// Post scene building state.
+type RasterizedBlob = FastHashMap<TileOffset, RasterizedBlobImage>;
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Copy, Clone, PartialEq, PeekPoke, Default)]
+pub struct ImageGeneration(pub u32);
+
+impl ImageGeneration {
+ pub const INVALID: ImageGeneration = ImageGeneration(u32::MAX);
+}
+
+struct ImageResource {
+ data: CachedImageData,
+ descriptor: ImageDescriptor,
+ tiling: Option<TileSize>,
+ /// This is used to express images that are virtually very large
+ /// but with only a visible sub-set that is valid at a given time.
+ visible_rect: DeviceIntRect,
+ generation: ImageGeneration,
+}
+
+#[derive(Clone, Debug)]
+pub struct ImageTiling {
+ pub image_size: DeviceIntSize,
+ pub tile_size: TileSize,
+}
+
+#[derive(Default)]
+struct ImageTemplates {
+ images: FastHashMap<ImageKey, ImageResource>,
+}
+
+impl ImageTemplates {
+ fn insert(&mut self, key: ImageKey, resource: ImageResource) {
+ self.images.insert(key, resource);
+ }
+
+ fn remove(&mut self, key: ImageKey) -> Option<ImageResource> {
+ self.images.remove(&key)
+ }
+
+ fn get(&self, key: ImageKey) -> Option<&ImageResource> {
+ self.images.get(&key)
+ }
+
+ fn get_mut(&mut self, key: ImageKey) -> Option<&mut ImageResource> {
+ self.images.get_mut(&key)
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+struct CachedImageInfo {
+ texture_cache_handle: TextureCacheHandle,
+ dirty_rect: ImageDirtyRect,
+ manual_eviction: bool,
+}
+
+impl CachedImageInfo {
+ fn mark_unused(&mut self, texture_cache: &mut TextureCache) {
+ texture_cache.evict_handle(&self.texture_cache_handle);
+ self.manual_eviction = false;
+ }
+}
+
+#[cfg(debug_assertions)]
+impl Drop for CachedImageInfo {
+ fn drop(&mut self) {
+ debug_assert!(!self.manual_eviction, "Manual eviction requires cleanup");
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ResourceClassCache<K: Hash + Eq, V, U: Default> {
+ resources: FastHashMap<K, V>,
+ pub user_data: U,
+}
+
+impl<K, V, U> ResourceClassCache<K, V, U>
+where
+ K: Clone + Hash + Eq + Debug,
+ U: Default,
+{
+ pub fn new() -> Self {
+ ResourceClassCache {
+ resources: FastHashMap::default(),
+ user_data: Default::default(),
+ }
+ }
+
+ pub fn get(&self, key: &K) -> &V {
+ self.resources.get(key)
+ .expect("Didn't find a cached resource with that ID!")
+ }
+
+ pub fn try_get(&self, key: &K) -> Option<&V> {
+ self.resources.get(key)
+ }
+
+ pub fn insert(&mut self, key: K, value: V) {
+ self.resources.insert(key, value);
+ }
+
+ pub fn remove(&mut self, key: &K) -> Option<V> {
+ self.resources.remove(key)
+ }
+
+ pub fn get_mut(&mut self, key: &K) -> &mut V {
+ self.resources.get_mut(key)
+ .expect("Didn't find a cached resource with that ID!")
+ }
+
+ pub fn try_get_mut(&mut self, key: &K) -> Option<&mut V> {
+ self.resources.get_mut(key)
+ }
+
+ pub fn entry(&mut self, key: K) -> Entry<K, V> {
+ self.resources.entry(key)
+ }
+
+ pub fn iter(&self) -> Iter<K, V> {
+ self.resources.iter()
+ }
+
+ pub fn iter_mut(&mut self) -> IterMut<K, V> {
+ self.resources.iter_mut()
+ }
+
+ pub fn is_empty(&mut self) -> bool {
+ self.resources.is_empty()
+ }
+
+ pub fn clear(&mut self) {
+ self.resources.clear();
+ }
+
+ pub fn retain<F>(&mut self, f: F)
+ where
+ F: FnMut(&K, &mut V) -> bool,
+ {
+ self.resources.retain(f);
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+struct CachedImageKey {
+ pub rendering: ImageRendering,
+ pub tile: Option<TileOffset>,
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ImageRequest {
+ pub key: ImageKey,
+ pub rendering: ImageRendering,
+ pub tile: Option<TileOffset>,
+}
+
+impl ImageRequest {
+ pub fn with_tile(&self, offset: TileOffset) -> Self {
+ ImageRequest {
+ key: self.key,
+ rendering: self.rendering,
+ tile: Some(offset),
+ }
+ }
+
+ pub fn is_untiled_auto(&self) -> bool {
+ self.tile.is_none() && self.rendering == ImageRendering::Auto
+ }
+}
+
+impl Into<BlobImageRequest> for ImageRequest {
+ fn into(self) -> BlobImageRequest {
+ BlobImageRequest {
+ key: BlobImageKey(self.key),
+ tile: self.tile.unwrap(),
+ }
+ }
+}
+
+impl Into<CachedImageKey> for ImageRequest {
+ fn into(self) -> CachedImageKey {
+ CachedImageKey {
+ rendering: self.rendering,
+ tile: self.tile,
+ }
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum ImageCacheError {
+ OverLimitSize,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+enum ImageResult {
+ UntiledAuto(CachedImageInfo),
+ Multi(ResourceClassCache<CachedImageKey, CachedImageInfo, ()>),
+ Err(ImageCacheError),
+}
+
+impl ImageResult {
+ /// Releases any texture cache entries held alive by this ImageResult.
+ fn drop_from_cache(&mut self, texture_cache: &mut TextureCache) {
+ match *self {
+ ImageResult::UntiledAuto(ref mut entry) => {
+ entry.mark_unused(texture_cache);
+ },
+ ImageResult::Multi(ref mut entries) => {
+ for entry in entries.resources.values_mut() {
+ entry.mark_unused(texture_cache);
+ }
+ },
+ ImageResult::Err(_) => {},
+ }
+ }
+}
+
+type ImageCache = ResourceClassCache<ImageKey, ImageResult, ()>;
+
+struct Resources {
+ fonts: SharedFontResources,
+ image_templates: ImageTemplates,
+ // We keep a set of Weak references to the fonts so that we're able to include them in memory
+ // reports even if only the OS is holding on to the Vec<u8>. PtrWeakHashSet will periodically
+ // drop any references that have gone dead.
+ weak_fonts: WeakTable
+}
+
+// We only use this to report glyph dimensions to the user of the API, so using
+// the font instance key should be enough. If we start using it to cache dimensions
+// for internal font instances we should change the hash key accordingly.
+pub type GlyphDimensionsCache = FastHashMap<(FontInstanceKey, GlyphIndex), Option<GlyphDimensions>>;
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct BlobImageRasterizerEpoch(usize);
+
+/// Internal information about allocated render targets in the pool
+struct RenderTarget {
+ size: DeviceIntSize,
+ format: ImageFormat,
+ texture_id: CacheTextureId,
+ /// If true, this is currently leant out, and not available to other passes
+ is_active: bool,
+ last_frame_used: FrameId,
+}
+
+impl RenderTarget {
+ fn size_in_bytes(&self) -> usize {
+ let bpp = self.format.bytes_per_pixel() as usize;
+ (self.size.width * self.size.height) as usize * bpp
+ }
+
+ /// Returns true if this texture was used within `threshold` frames of
+ /// the current frame.
+ pub fn used_recently(&self, current_frame_id: FrameId, threshold: u64) -> bool {
+ self.last_frame_used + threshold >= current_frame_id
+ }
+}
+
+/// High-level container for resources managed by the `RenderBackend`.
+///
+/// This includes a variety of things, including images, fonts, and glyphs,
+/// which may be stored as memory buffers, GPU textures, or handles to resources
+/// managed by the OS or other parts of WebRender.
+pub struct ResourceCache {
+ cached_glyphs: GlyphCache,
+ cached_images: ImageCache,
+ cached_render_tasks: RenderTaskCache,
+
+ resources: Resources,
+ state: State,
+ current_frame_id: FrameId,
+
+ #[cfg(feature = "capture")]
+ /// Used for capture sequences. If the resource cache is updated, then we
+ /// mark it as dirty. When the next frame is captured in the sequence, we
+ /// dump the state of the resource cache.
+ capture_dirty: bool,
+
+ pub texture_cache: TextureCache,
+ pub picture_textures: PictureTextures,
+
+ /// TODO(gw): We should expire (parts of) this cache semi-regularly!
+ cached_glyph_dimensions: GlyphDimensionsCache,
+ glyph_rasterizer: GlyphRasterizer,
+
+ /// The set of images that aren't present or valid in the texture cache,
+ /// and need to be rasterized and/or uploaded this frame. This includes
+ /// both blobs and regular images.
+ pending_image_requests: FastHashSet<ImageRequest>,
+
+ rasterized_blob_images: FastHashMap<BlobImageKey, RasterizedBlob>,
+
+ /// A log of the last three frames worth of deleted image keys kept
+ /// for debugging purposes.
+ deleted_blob_keys: VecDeque<Vec<BlobImageKey>>,
+
+ /// We keep one around to be able to call clear_namespace
+ /// after the api object is deleted. For most purposes the
+ /// api object's blob handler should be used instead.
+ blob_image_handler: Option<Box<dyn BlobImageHandler>>,
+
+ /// A list of queued compositor surface updates to apply next frame.
+ pending_native_surface_updates: Vec<NativeSurfaceOperation>,
+
+ image_templates_memory: usize,
+ font_templates_memory: usize,
+
+ /// A pool of render targets for use by the render task graph
+ render_target_pool: Vec<RenderTarget>,
+}
+
+impl ResourceCache {
+ pub fn new(
+ texture_cache: TextureCache,
+ picture_textures: PictureTextures,
+ glyph_rasterizer: GlyphRasterizer,
+ cached_glyphs: GlyphCache,
+ fonts: SharedFontResources,
+ blob_image_handler: Option<Box<dyn BlobImageHandler>>,
+ ) -> Self {
+ ResourceCache {
+ cached_glyphs,
+ cached_images: ResourceClassCache::new(),
+ cached_render_tasks: RenderTaskCache::new(),
+ resources: Resources {
+ fonts,
+ image_templates: ImageTemplates::default(),
+ weak_fonts: WeakTable::new(),
+ },
+ cached_glyph_dimensions: FastHashMap::default(),
+ texture_cache,
+ picture_textures,
+ state: State::Idle,
+ current_frame_id: FrameId::INVALID,
+ pending_image_requests: FastHashSet::default(),
+ glyph_rasterizer,
+ rasterized_blob_images: FastHashMap::default(),
+ // We want to keep three frames worth of delete blob keys
+ deleted_blob_keys: vec![Vec::new(), Vec::new(), Vec::new()].into(),
+ blob_image_handler,
+ pending_native_surface_updates: Vec::new(),
+ #[cfg(feature = "capture")]
+ capture_dirty: true,
+ image_templates_memory: 0,
+ font_templates_memory: 0,
+ render_target_pool: Vec::new(),
+ }
+ }
+
+ /// Construct a resource cache for use in unit tests.
+ #[cfg(test)]
+ pub fn new_for_testing() -> Self {
+ use rayon::ThreadPoolBuilder;
+
+ let texture_cache = TextureCache::new_for_testing(
+ 4096,
+ ImageFormat::RGBA8,
+ );
+ let workers = Arc::new(ThreadPoolBuilder::new().build().unwrap());
+ let glyph_rasterizer = GlyphRasterizer::new(workers, true);
+ let cached_glyphs = GlyphCache::new();
+ let fonts = SharedFontResources::new(IdNamespace(0));
+ let picture_textures = PictureTextures::new(
+ crate::picture::TILE_SIZE_DEFAULT,
+ TextureFilter::Nearest,
+ );
+
+ ResourceCache::new(
+ texture_cache,
+ picture_textures,
+ glyph_rasterizer,
+ cached_glyphs,
+ fonts,
+ None,
+ )
+ }
+
+ pub fn max_texture_size(&self) -> i32 {
+ self.texture_cache.max_texture_size()
+ }
+
+ /// Maximum texture size before we consider it preferrable to break the texture
+ /// into tiles.
+ pub fn tiling_threshold(&self) -> i32 {
+ self.texture_cache.tiling_threshold()
+ }
+
+ pub fn enable_multithreading(&mut self, enable: bool) {
+ self.glyph_rasterizer.enable_multithreading(enable);
+ }
+
+ fn should_tile(limit: i32, descriptor: &ImageDescriptor, data: &CachedImageData) -> bool {
+ let size_check = descriptor.size.width > limit || descriptor.size.height > limit;
+ match *data {
+ CachedImageData::Raw(_) | CachedImageData::Blob => size_check,
+ CachedImageData::External(info) => {
+ // External handles already represent existing textures so it does
+ // not make sense to tile them into smaller ones.
+ info.image_type == ExternalImageType::Buffer && size_check
+ }
+ }
+ }
+
+ // Request the texture cache item for a cacheable render
+ // task. If the item is already cached, the texture cache
+ // handle will be returned. Otherwise, the user supplied
+ // closure will be invoked to generate the render task
+ // chain that is required to draw this task.
+ pub fn request_render_task<F>(
+ &mut self,
+ key: RenderTaskCacheKey,
+ gpu_cache: &mut GpuCache,
+ gpu_buffer_builder: &mut GpuBufferBuilder,
+ rg_builder: &mut RenderTaskGraphBuilder,
+ user_data: Option<[f32; 4]>,
+ is_opaque: bool,
+ parent: RenderTaskParent,
+ surface_builder: &mut SurfaceBuilder,
+ f: F,
+ ) -> RenderTaskId
+ where
+ F: FnOnce(&mut RenderTaskGraphBuilder, &mut GpuBufferBuilder) -> RenderTaskId,
+ {
+ self.cached_render_tasks.request_render_task(
+ key,
+ &mut self.texture_cache,
+ gpu_cache,
+ gpu_buffer_builder,
+ rg_builder,
+ user_data,
+ is_opaque,
+ parent,
+ surface_builder,
+ |render_graph, gpu_buffer_builder| Ok(f(render_graph, gpu_buffer_builder))
+ ).expect("Failed to request a render task from the resource cache!")
+ }
+
+ pub fn post_scene_building_update(
+ &mut self,
+ updates: Vec<ResourceUpdate>,
+ profile: &mut TransactionProfile,
+ ) {
+ // TODO, there is potential for optimization here, by processing updates in
+ // bulk rather than one by one (for example by sorting allocations by size or
+ // in a way that reduces fragmentation in the atlas).
+ #[cfg(feature = "capture")]
+ match updates.is_empty() {
+ false => self.capture_dirty = true,
+ _ => {},
+ }
+
+ for update in updates {
+ match update {
+ ResourceUpdate::AddImage(img) => {
+ if let ImageData::Raw(ref bytes) = img.data {
+ self.image_templates_memory += bytes.len();
+ profile.set(profiler::IMAGE_TEMPLATES_MEM, bytes_to_mb(self.image_templates_memory));
+ }
+ self.add_image_template(
+ img.key,
+ img.descriptor,
+ img.data.into(),
+ &img.descriptor.size.into(),
+ img.tiling,
+ );
+ profile.set(profiler::IMAGE_TEMPLATES, self.resources.image_templates.images.len());
+ }
+ ResourceUpdate::UpdateImage(img) => {
+ self.update_image_template(img.key, img.descriptor, img.data.into(), &img.dirty_rect);
+ }
+ ResourceUpdate::AddBlobImage(img) => {
+ self.add_image_template(
+ img.key.as_image(),
+ img.descriptor,
+ CachedImageData::Blob,
+ &img.visible_rect,
+ Some(img.tile_size),
+ );
+ }
+ ResourceUpdate::UpdateBlobImage(img) => {
+ self.update_image_template(
+ img.key.as_image(),
+ img.descriptor,
+ CachedImageData::Blob,
+ &to_image_dirty_rect(
+ &img.dirty_rect
+ ),
+ );
+ self.discard_tiles_outside_visible_area(img.key, &img.visible_rect); // TODO: remove?
+ self.set_image_visible_rect(img.key.as_image(), &img.visible_rect);
+ }
+ ResourceUpdate::DeleteImage(img) => {
+ self.delete_image_template(img);
+ profile.set(profiler::IMAGE_TEMPLATES, self.resources.image_templates.images.len());
+ profile.set(profiler::IMAGE_TEMPLATES_MEM, bytes_to_mb(self.image_templates_memory));
+ }
+ ResourceUpdate::DeleteBlobImage(img) => {
+ self.delete_image_template(img.as_image());
+ }
+ ResourceUpdate::DeleteFont(font) => {
+ if let Some(shared_key) = self.resources.fonts.font_keys.delete_key(&font) {
+ self.delete_font_template(shared_key);
+ if let Some(ref mut handler) = &mut self.blob_image_handler {
+ handler.delete_font(shared_key);
+ }
+ profile.set(profiler::FONT_TEMPLATES, self.resources.fonts.templates.len());
+ profile.set(profiler::FONT_TEMPLATES_MEM, bytes_to_mb(self.font_templates_memory));
+ }
+ }
+ ResourceUpdate::DeleteFontInstance(font) => {
+ if let Some(shared_key) = self.resources.fonts.instance_keys.delete_key(&font) {
+ self.delete_font_instance(shared_key);
+ }
+ if let Some(ref mut handler) = &mut self.blob_image_handler {
+ handler.delete_font_instance(font);
+ }
+ }
+ ResourceUpdate::SetBlobImageVisibleArea(key, area) => {
+ self.discard_tiles_outside_visible_area(key, &area);
+ self.set_image_visible_rect(key.as_image(), &area);
+ }
+ ResourceUpdate::AddFont(font) => {
+ // The shared key was already added in ApiResources, but the first time it is
+ // seen on the backend we still need to do some extra initialization here.
+ let (key, template) = match font {
+ AddFont::Raw(key, bytes, index) => {
+ (key, FontTemplate::Raw(bytes, index))
+ }
+ AddFont::Native(key, native_font_handle) => {
+ (key, FontTemplate::Native(native_font_handle))
+ }
+ };
+ let shared_key = self.resources.fonts.font_keys.map_key(&key);
+ if !self.glyph_rasterizer.has_font(shared_key) {
+ self.add_font_template(shared_key, template);
+ profile.set(profiler::FONT_TEMPLATES, self.resources.fonts.templates.len());
+ profile.set(profiler::FONT_TEMPLATES_MEM, bytes_to_mb(self.font_templates_memory));
+ }
+ }
+ ResourceUpdate::AddFontInstance(..) => {
+ // Already added in ApiResources.
+ }
+ }
+ }
+ }
+
+ pub fn add_rasterized_blob_images(
+ &mut self,
+ images: Vec<(BlobImageRequest, BlobImageResult)>,
+ profile: &mut TransactionProfile,
+ ) {
+ for (request, result) in images {
+ let data = match result {
+ Ok(data) => data,
+ Err(..) => {
+ warn!("Failed to rasterize a blob image");
+ continue;
+ }
+ };
+
+ profile.add(profiler::RASTERIZED_BLOBS_PX, data.rasterized_rect.area());
+
+ // First make sure we have an entry for this key (using a placeholder
+ // if need be).
+ let tiles = self.rasterized_blob_images.entry(request.key).or_insert_with(
+ || { RasterizedBlob::default() }
+ );
+
+ tiles.insert(request.tile, data);
+
+ match self.cached_images.try_get_mut(&request.key.as_image()) {
+ Some(&mut ImageResult::Multi(ref mut entries)) => {
+ let cached_key = CachedImageKey {
+ rendering: ImageRendering::Auto, // TODO(nical)
+ tile: Some(request.tile),
+ };
+ if let Some(entry) = entries.try_get_mut(&cached_key) {
+ entry.dirty_rect = DirtyRect::All;
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
+ pub fn add_font_template(&mut self, font_key: FontKey, template: FontTemplate) {
+ // Push the new font to the font renderer, and also store
+ // it locally for glyph metric requests.
+ if let FontTemplate::Raw(ref data, _) = template {
+ self.resources.weak_fonts.insert(Arc::downgrade(data));
+ self.font_templates_memory += data.len();
+ }
+ self.glyph_rasterizer.add_font(font_key, template.clone());
+ self.resources.fonts.templates.add_font(font_key, template);
+ }
+
+ pub fn delete_font_template(&mut self, font_key: FontKey) {
+ self.glyph_rasterizer.delete_font(font_key);
+ if let Some(FontTemplate::Raw(data, _)) = self.resources.fonts.templates.delete_font(&font_key) {
+ self.font_templates_memory -= data.len();
+ }
+ self.cached_glyphs.delete_fonts(&[font_key]);
+ }
+
+ pub fn delete_font_instance(&mut self, instance_key: FontInstanceKey) {
+ self.resources.fonts.instances.delete_font_instance(instance_key);
+ }
+
+ pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option<Arc<BaseFontInstance>> {
+ self.resources.fonts.instances.get_font_instance(instance_key)
+ }
+
+ pub fn get_fonts(&self) -> SharedFontResources {
+ self.resources.fonts.clone()
+ }
+
+ pub fn add_image_template(
+ &mut self,
+ image_key: ImageKey,
+ descriptor: ImageDescriptor,
+ data: CachedImageData,
+ visible_rect: &DeviceIntRect,
+ mut tiling: Option<TileSize>,
+ ) {
+ if let Some(ref mut tile_size) = tiling {
+ // Sanitize the value since it can be set by a pref.
+ *tile_size = (*tile_size).max(16).min(2048);
+ }
+
+ if tiling.is_none() && Self::should_tile(self.tiling_threshold(), &descriptor, &data) {
+ // We aren't going to be able to upload a texture this big, so tile it, even
+ // if tiling was not requested.
+ tiling = Some(DEFAULT_TILE_SIZE);
+ }
+
+ let resource = ImageResource {
+ descriptor,
+ data,
+ tiling,
+ visible_rect: *visible_rect,
+ generation: ImageGeneration(0),
+ };
+
+ self.resources.image_templates.insert(image_key, resource);
+ }
+
+ pub fn update_image_template(
+ &mut self,
+ image_key: ImageKey,
+ descriptor: ImageDescriptor,
+ data: CachedImageData,
+ dirty_rect: &ImageDirtyRect,
+ ) {
+ let tiling_threshold = self.tiling_threshold();
+ let image = match self.resources.image_templates.get_mut(image_key) {
+ Some(res) => res,
+ None => panic!("Attempt to update non-existent image"),
+ };
+
+ let mut tiling = image.tiling;
+ if tiling.is_none() && Self::should_tile(tiling_threshold, &descriptor, &data) {
+ tiling = Some(DEFAULT_TILE_SIZE);
+ }
+
+ // Each cache entry stores its own copy of the image's dirty rect. This allows them to be
+ // updated independently.
+ match self.cached_images.try_get_mut(&image_key) {
+ Some(&mut ImageResult::UntiledAuto(ref mut entry)) => {
+ entry.dirty_rect = entry.dirty_rect.union(dirty_rect);
+ }
+ Some(&mut ImageResult::Multi(ref mut entries)) => {
+ for (key, entry) in entries.iter_mut() {
+ // We want the dirty rect relative to the tile and not the whole image.
+ let local_dirty_rect = match (tiling, key.tile) {
+ (Some(tile_size), Some(tile)) => {
+ dirty_rect.map(|mut rect|{
+ let tile_offset = DeviceIntPoint::new(
+ tile.x as i32,
+ tile.y as i32,
+ ) * tile_size as i32;
+ rect = rect.translate(-tile_offset.to_vector());
+
+ let tile_rect = compute_tile_size(
+ &descriptor.size.into(),
+ tile_size,
+ tile,
+ ).into();
+
+ rect.intersection(&tile_rect).unwrap_or_else(DeviceIntRect::zero)
+ })
+ }
+ (None, Some(..)) => DirtyRect::All,
+ _ => *dirty_rect,
+ };
+ entry.dirty_rect = entry.dirty_rect.union(&local_dirty_rect);
+ }
+ }
+ _ => {}
+ }
+
+ if image.descriptor.format != descriptor.format {
+ // could be a stronger warning/error?
+ trace!("Format change {:?} -> {:?}", image.descriptor.format, descriptor.format);
+ }
+ *image = ImageResource {
+ descriptor,
+ data,
+ tiling,
+ visible_rect: descriptor.size.into(),
+ generation: ImageGeneration(image.generation.0 + 1),
+ };
+ }
+
+ pub fn delete_image_template(&mut self, image_key: ImageKey) {
+ // Remove the template.
+ let value = self.resources.image_templates.remove(image_key);
+
+ // Release the corresponding texture cache entry, if any.
+ if let Some(mut cached) = self.cached_images.remove(&image_key) {
+ cached.drop_from_cache(&mut self.texture_cache);
+ }
+
+ match value {
+ Some(image) => if image.data.is_blob() {
+ if let CachedImageData::Raw(data) = image.data {
+ self.image_templates_memory -= data.len();
+ }
+
+ let blob_key = BlobImageKey(image_key);
+ self.deleted_blob_keys.back_mut().unwrap().push(blob_key);
+ self.rasterized_blob_images.remove(&blob_key);
+ },
+ None => {
+ warn!("Delete the non-exist key");
+ debug!("key={:?}", image_key);
+ }
+ }
+ }
+
+ /// Return the current generation of an image template
+ pub fn get_image_generation(&self, key: ImageKey) -> ImageGeneration {
+ self.resources
+ .image_templates
+ .get(key)
+ .map_or(ImageGeneration::INVALID, |template| template.generation)
+ }
+
+ /// Requests an image to ensure that it will be in the texture cache this frame.
+ ///
+ /// returns the size in device pixel of the image or tile.
+ pub fn request_image(
+ &mut self,
+ request: ImageRequest,
+ gpu_cache: &mut GpuCache,
+ ) -> DeviceIntSize {
+ debug_assert_eq!(self.state, State::AddResources);
+
+ let template = match self.resources.image_templates.get(request.key) {
+ Some(template) => template,
+ None => {
+ warn!("ERROR: Trying to render deleted / non-existent key");
+ debug!("key={:?}", request.key);
+ return DeviceIntSize::zero();
+ }
+ };
+
+ let size = match request.tile {
+ Some(tile) => compute_tile_size(&template.visible_rect, template.tiling.unwrap(), tile),
+ None => template.descriptor.size,
+ };
+
+ // Images that don't use the texture cache can early out.
+ if !template.data.uses_texture_cache() {
+ return size;
+ }
+
+ let side_size =
+ template.tiling.map_or(cmp::max(template.descriptor.size.width, template.descriptor.size.height),
+ |tile_size| tile_size as i32);
+ if side_size > self.texture_cache.max_texture_size() {
+ // The image or tiling size is too big for hardware texture size.
+ warn!("Dropping image, image:(w:{},h:{}, tile:{}) is too big for hardware!",
+ template.descriptor.size.width, template.descriptor.size.height, template.tiling.unwrap_or(0));
+ self.cached_images.insert(request.key, ImageResult::Err(ImageCacheError::OverLimitSize));
+ return DeviceIntSize::zero();
+ }
+
+ let storage = match self.cached_images.entry(request.key) {
+ Occupied(e) => {
+ // We might have an existing untiled entry, and need to insert
+ // a second entry. In such cases we need to move the old entry
+ // out first, replacing it with a dummy entry, and then creating
+ // the tiled/multi-entry variant.
+ let entry = e.into_mut();
+ if !request.is_untiled_auto() {
+ let untiled_entry = match entry {
+ &mut ImageResult::UntiledAuto(ref mut entry) => {
+ Some(mem::replace(entry, CachedImageInfo {
+ texture_cache_handle: TextureCacheHandle::invalid(),
+ dirty_rect: DirtyRect::All,
+ manual_eviction: false,
+ }))
+ }
+ _ => None
+ };
+
+ if let Some(untiled_entry) = untiled_entry {
+ let mut entries = ResourceClassCache::new();
+ let untiled_key = CachedImageKey {
+ rendering: ImageRendering::Auto,
+ tile: None,
+ };
+ entries.insert(untiled_key, untiled_entry);
+ *entry = ImageResult::Multi(entries);
+ }
+ }
+ entry
+ }
+ Vacant(entry) => {
+ entry.insert(if request.is_untiled_auto() {
+ ImageResult::UntiledAuto(CachedImageInfo {
+ texture_cache_handle: TextureCacheHandle::invalid(),
+ dirty_rect: DirtyRect::All,
+ manual_eviction: false,
+ })
+ } else {
+ ImageResult::Multi(ResourceClassCache::new())
+ })
+ }
+ };
+
+ // If this image exists in the texture cache, *and* the dirty rect
+ // in the cache is empty, then it is valid to use as-is.
+ let entry = match *storage {
+ ImageResult::UntiledAuto(ref mut entry) => entry,
+ ImageResult::Multi(ref mut entries) => {
+ entries.entry(request.into())
+ .or_insert(CachedImageInfo {
+ texture_cache_handle: TextureCacheHandle::invalid(),
+ dirty_rect: DirtyRect::All,
+ manual_eviction: false,
+ })
+ },
+ ImageResult::Err(_) => panic!("Errors should already have been handled"),
+ };
+
+ let needs_upload = self.texture_cache.request(&entry.texture_cache_handle, gpu_cache);
+
+ if !needs_upload && entry.dirty_rect.is_empty() {
+ return size;
+ }
+
+ if !self.pending_image_requests.insert(request) {
+ return size;
+ }
+
+ if template.data.is_blob() {
+ let request: BlobImageRequest = request.into();
+ let missing = match self.rasterized_blob_images.get(&request.key) {
+ Some(tiles) => !tiles.contains_key(&request.tile),
+ _ => true,
+ };
+
+ assert!(!missing);
+ }
+
+ size
+ }
+
+ fn discard_tiles_outside_visible_area(
+ &mut self,
+ key: BlobImageKey,
+ area: &DeviceIntRect
+ ) {
+ let tile_size = match self.resources.image_templates.get(key.as_image()) {
+ Some(template) => template.tiling.unwrap(),
+ None => {
+ //debug!("Missing image template (key={:?})!", key);
+ return;
+ }
+ };
+
+ let tiles = match self.rasterized_blob_images.get_mut(&key) {
+ Some(tiles) => tiles,
+ _ => { return; }
+ };
+
+ let tile_range = compute_tile_range(
+ &area,
+ tile_size,
+ );
+
+ tiles.retain(|tile, _| { tile_range.contains(*tile) });
+
+ let texture_cache = &mut self.texture_cache;
+ match self.cached_images.try_get_mut(&key.as_image()) {
+ Some(&mut ImageResult::Multi(ref mut entries)) => {
+ entries.retain(|key, entry| {
+ if key.tile.is_none() || tile_range.contains(key.tile.unwrap()) {
+ return true;
+ }
+ entry.mark_unused(texture_cache);
+ return false;
+ });
+ }
+ _ => {}
+ }
+ }
+
+ fn set_image_visible_rect(&mut self, key: ImageKey, rect: &DeviceIntRect) {
+ if let Some(image) = self.resources.image_templates.get_mut(key) {
+ image.visible_rect = *rect;
+ image.descriptor.size = rect.size();
+ }
+ }
+
+ pub fn request_glyphs(
+ &mut self,
+ mut font: FontInstance,
+ glyph_keys: &[GlyphKey],
+ gpu_cache: &mut GpuCache,
+ ) {
+ debug_assert_eq!(self.state, State::AddResources);
+
+ self.glyph_rasterizer.prepare_font(&mut font);
+ let glyph_key_cache = self.cached_glyphs.insert_glyph_key_cache_for_font(&font);
+ let texture_cache = &mut self.texture_cache;
+ self.glyph_rasterizer.request_glyphs(
+ font,
+ glyph_keys,
+ |key| {
+ if let Some(entry) = glyph_key_cache.try_get(key) {
+ match entry {
+ GlyphCacheEntry::Cached(ref glyph) => {
+ // Skip the glyph if it is already has a valid texture cache handle.
+ if !texture_cache.request(&glyph.texture_cache_handle, gpu_cache) {
+ return false;
+ }
+ // This case gets hit when we already rasterized the glyph, but the
+ // glyph has been evicted from the texture cache. Just force it to
+ // pending so it gets rematerialized.
+ }
+ // Otherwise, skip the entry if it is blank or pending.
+ GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => return false,
+ }
+ };
+
+ glyph_key_cache.add_glyph(*key, GlyphCacheEntry::Pending);
+
+ true
+ }
+ );
+ }
+
+ pub fn pending_updates(&mut self) -> ResourceUpdateList {
+ ResourceUpdateList {
+ texture_updates: self.texture_cache.pending_updates(),
+ native_surface_updates: mem::replace(&mut self.pending_native_surface_updates, Vec::new()),
+ }
+ }
+
+ pub fn fetch_glyphs<F>(
+ &self,
+ mut font: FontInstance,
+ glyph_keys: &[GlyphKey],
+ fetch_buffer: &mut Vec<GlyphFetchResult>,
+ gpu_cache: &mut GpuCache,
+ mut f: F,
+ ) where
+ F: FnMut(TextureSource, GlyphFormat, &[GlyphFetchResult]),
+ {
+ debug_assert_eq!(self.state, State::QueryResources);
+
+ self.glyph_rasterizer.prepare_font(&mut font);
+ let glyph_key_cache = self.cached_glyphs.get_glyph_key_cache_for_font(&font);
+
+ let mut current_texture_id = TextureSource::Invalid;
+ let mut current_glyph_format = GlyphFormat::Subpixel;
+ debug_assert!(fetch_buffer.is_empty());
+
+ for (loop_index, key) in glyph_keys.iter().enumerate() {
+ let (cache_item, glyph_format) = match *glyph_key_cache.get(key) {
+ GlyphCacheEntry::Cached(ref glyph) => {
+ (self.texture_cache.get(&glyph.texture_cache_handle), glyph.format)
+ }
+ GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => continue,
+ };
+ if current_texture_id != cache_item.texture_id ||
+ current_glyph_format != glyph_format {
+ if !fetch_buffer.is_empty() {
+ f(current_texture_id, current_glyph_format, fetch_buffer);
+ fetch_buffer.clear();
+ }
+ current_texture_id = cache_item.texture_id;
+ current_glyph_format = glyph_format;
+ }
+ fetch_buffer.push(GlyphFetchResult {
+ index_in_text_run: loop_index as i32,
+ uv_rect_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
+ offset: DevicePoint::new(cache_item.user_data[0], cache_item.user_data[1]),
+ size: cache_item.uv_rect.size(),
+ scale: cache_item.user_data[2],
+ });
+ }
+
+ if !fetch_buffer.is_empty() {
+ f(current_texture_id, current_glyph_format, fetch_buffer);
+ fetch_buffer.clear();
+ }
+ }
+
+ pub fn map_font_key(&self, key: FontKey) -> FontKey {
+ self.resources.fonts.font_keys.map_key(&key)
+ }
+
+ pub fn map_font_instance_key(&self, key: FontInstanceKey) -> FontInstanceKey {
+ self.resources.fonts.instance_keys.map_key(&key)
+ }
+
+ pub fn get_glyph_dimensions(
+ &mut self,
+ font: &FontInstance,
+ glyph_index: GlyphIndex,
+ ) -> Option<GlyphDimensions> {
+ match self.cached_glyph_dimensions.entry((font.instance_key, glyph_index)) {
+ Occupied(entry) => *entry.get(),
+ Vacant(entry) => *entry.insert(
+ self.glyph_rasterizer
+ .get_glyph_dimensions(font, glyph_index),
+ ),
+ }
+ }
+
+ pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
+ self.glyph_rasterizer.get_glyph_index(font_key, ch)
+ }
+
+ #[inline]
+ pub fn get_cached_image(&self, request: ImageRequest) -> Result<CacheItem, ()> {
+ debug_assert_eq!(self.state, State::QueryResources);
+ let image_info = self.get_image_info(request)?;
+ Ok(self.get_texture_cache_item(&image_info.texture_cache_handle))
+ }
+
+ pub fn get_cached_render_task(
+ &self,
+ handle: &RenderTaskCacheEntryHandle,
+ ) -> &RenderTaskCacheEntry {
+ self.cached_render_tasks.get_cache_entry(handle)
+ }
+
+ #[inline]
+ fn get_image_info(&self, request: ImageRequest) -> Result<&CachedImageInfo, ()> {
+ // TODO(Jerry): add a debug option to visualize the corresponding area for
+ // the Err() case of CacheItem.
+ match *self.cached_images.get(&request.key) {
+ ImageResult::UntiledAuto(ref image_info) => Ok(image_info),
+ ImageResult::Multi(ref entries) => Ok(entries.get(&request.into())),
+ ImageResult::Err(_) => Err(()),
+ }
+ }
+
+ #[inline]
+ pub fn get_texture_cache_item(&self, handle: &TextureCacheHandle) -> CacheItem {
+ self.texture_cache.get(handle)
+ }
+
+ pub fn get_image_properties(&self, image_key: ImageKey) -> Option<ImageProperties> {
+ let image_template = &self.resources.image_templates.get(image_key);
+
+ image_template.map(|image_template| {
+ let external_image = match image_template.data {
+ CachedImageData::External(ext_image) => match ext_image.image_type {
+ ExternalImageType::TextureHandle(_) => Some(ext_image),
+ // external buffer uses resource_cache.
+ ExternalImageType::Buffer => None,
+ },
+ // raw and blob image are all using resource_cache.
+ CachedImageData::Raw(..) | CachedImageData::Blob => None,
+ };
+
+ ImageProperties {
+ descriptor: image_template.descriptor,
+ external_image,
+ tiling: image_template.tiling,
+ visible_rect: image_template.visible_rect,
+ }
+ })
+ }
+
+ pub fn begin_frame(&mut self, stamp: FrameStamp, gpu_cache: &mut GpuCache, profile: &mut TransactionProfile) {
+ profile_scope!("begin_frame");
+ debug_assert_eq!(self.state, State::Idle);
+ self.state = State::AddResources;
+ self.texture_cache.begin_frame(stamp, profile);
+ self.picture_textures.begin_frame(stamp, &mut self.texture_cache.pending_updates);
+
+ self.cached_glyphs.begin_frame(
+ stamp,
+ &mut self.texture_cache,
+ &mut self.glyph_rasterizer,
+ );
+ self.cached_render_tasks.begin_frame(&mut self.texture_cache);
+ self.current_frame_id = stamp.frame_id();
+
+ // pop the old frame and push a new one
+ self.deleted_blob_keys.pop_front();
+ self.deleted_blob_keys.push_back(Vec::new());
+
+ self.texture_cache.run_compaction(gpu_cache);
+ }
+
+ pub fn block_until_all_resources_added(
+ &mut self,
+ gpu_cache: &mut GpuCache,
+ profile: &mut TransactionProfile,
+ ) {
+ profile_scope!("block_until_all_resources_added");
+
+ debug_assert_eq!(self.state, State::AddResources);
+ self.state = State::QueryResources;
+
+ let cached_glyphs = &mut self.cached_glyphs;
+ let texture_cache = &mut self.texture_cache;
+
+ self.glyph_rasterizer.resolve_glyphs(
+ |job, can_use_r8_format| {
+ let GlyphRasterJob { font, key, result } = job;
+ let glyph_key_cache = cached_glyphs.get_glyph_key_cache_for_font_mut(&*font);
+ let glyph_info = match result {
+ Err(_) => GlyphCacheEntry::Blank,
+ Ok(ref glyph) if glyph.width == 0 || glyph.height == 0 => {
+ GlyphCacheEntry::Blank
+ }
+ Ok(glyph) => {
+ let mut texture_cache_handle = TextureCacheHandle::invalid();
+ texture_cache.request(&texture_cache_handle, gpu_cache);
+ texture_cache.update(
+ &mut texture_cache_handle,
+ ImageDescriptor {
+ size: size2(glyph.width, glyph.height),
+ stride: None,
+ format: glyph.format.image_format(can_use_r8_format),
+ flags: ImageDescriptorFlags::empty(),
+ offset: 0,
+ },
+ TextureFilter::Linear,
+ Some(CachedImageData::Raw(Arc::new(glyph.bytes))),
+ [glyph.left, -glyph.top, glyph.scale, 0.0],
+ DirtyRect::All,
+ gpu_cache,
+ Some(glyph_key_cache.eviction_notice()),
+ UvRectKind::Rect,
+ Eviction::Auto,
+ TargetShader::Text,
+ );
+ GlyphCacheEntry::Cached(CachedGlyphInfo {
+ texture_cache_handle,
+ format: glyph.format,
+ })
+ }
+ };
+ glyph_key_cache.insert(key, glyph_info);
+ },
+ profile,
+ );
+
+ // Apply any updates of new / updated images (incl. blobs) to the texture cache.
+ self.update_texture_cache(gpu_cache);
+ }
+
+ fn update_texture_cache(&mut self, gpu_cache: &mut GpuCache) {
+ profile_scope!("update_texture_cache");
+ for request in self.pending_image_requests.drain() {
+ let image_template = self.resources.image_templates.get_mut(request.key).unwrap();
+ debug_assert!(image_template.data.uses_texture_cache());
+
+ let mut updates: SmallVec<[(CachedImageData, Option<DeviceIntRect>); 1]> = SmallVec::new();
+
+ match image_template.data {
+ CachedImageData::Raw(..) | CachedImageData::External(..) => {
+ // Safe to clone here since the Raw image data is an
+ // Arc, and the external image data is small.
+ updates.push((image_template.data.clone(), None));
+ }
+ CachedImageData::Blob => {
+ let blob_image = self.rasterized_blob_images.get_mut(&BlobImageKey(request.key)).unwrap();
+ let img = &blob_image[&request.tile.unwrap()];
+ updates.push((
+ CachedImageData::Raw(Arc::clone(&img.data)),
+ Some(img.rasterized_rect)
+ ));
+ }
+ };
+
+ for (image_data, blob_rasterized_rect) in updates {
+ let entry = match *self.cached_images.get_mut(&request.key) {
+ ImageResult::UntiledAuto(ref mut entry) => entry,
+ ImageResult::Multi(ref mut entries) => entries.get_mut(&request.into()),
+ ImageResult::Err(_) => panic!("Update requested for invalid entry")
+ };
+
+ let mut descriptor = image_template.descriptor.clone();
+ let mut dirty_rect = entry.dirty_rect.replace_with_empty();
+
+ if let Some(tile) = request.tile {
+ let tile_size = image_template.tiling.unwrap();
+ let clipped_tile_size = compute_tile_size(&image_template.visible_rect, tile_size, tile);
+ // The tiled image could be stored on the CPU as one large image or be
+ // already broken up into tiles. This affects the way we compute the stride
+ // and offset.
+ let tiled_on_cpu = image_template.data.is_blob();
+ if !tiled_on_cpu {
+ // we don't expect to have partial tiles at the top and left of non-blob
+ // images.
+ debug_assert_eq!(image_template.visible_rect.min, point2(0, 0));
+ let bpp = descriptor.format.bytes_per_pixel();
+ let stride = descriptor.compute_stride();
+ descriptor.stride = Some(stride);
+ descriptor.offset +=
+ tile.y as i32 * tile_size as i32 * stride +
+ tile.x as i32 * tile_size as i32 * bpp;
+ }
+
+ descriptor.size = clipped_tile_size;
+ }
+
+ // If we are uploading the dirty region of a blob image we might have several
+ // rects to upload so we use each of these rasterized rects rather than the
+ // overall dirty rect of the image.
+ if let Some(rect) = blob_rasterized_rect {
+ dirty_rect = DirtyRect::Partial(rect);
+ }
+
+ let filter = match request.rendering {
+ ImageRendering::Pixelated => {
+ TextureFilter::Nearest
+ }
+ ImageRendering::Auto | ImageRendering::CrispEdges => {
+ // If the texture uses linear filtering, enable mipmaps and
+ // trilinear filtering, for better image quality. We only
+ // support this for now on textures that are not placed
+ // into the shared cache. This accounts for any image
+ // that is > 512 in either dimension, so it should cover
+ // the most important use cases. We may want to support
+ // mip-maps on shared cache items in the future.
+ if descriptor.allow_mipmaps() &&
+ descriptor.size.width > 512 &&
+ descriptor.size.height > 512 &&
+ !self.texture_cache.is_allowed_in_shared_cache(
+ TextureFilter::Linear,
+ &descriptor,
+ ) {
+ TextureFilter::Trilinear
+ } else {
+ TextureFilter::Linear
+ }
+ }
+ };
+
+ let eviction = if image_template.data.is_blob() {
+ entry.manual_eviction = true;
+ Eviction::Manual
+ } else {
+ Eviction::Auto
+ };
+
+ //Note: at this point, the dirty rectangle is local to the descriptor space
+ self.texture_cache.update(
+ &mut entry.texture_cache_handle,
+ descriptor,
+ filter,
+ Some(image_data),
+ [0.0; 4],
+ dirty_rect,
+ gpu_cache,
+ None,
+ UvRectKind::Rect,
+ eviction,
+ TargetShader::Default,
+ );
+ }
+ }
+ }
+
+ pub fn create_compositor_backdrop_surface(
+ &mut self,
+ color: ColorF
+ ) -> NativeSurfaceId {
+ let id = NativeSurfaceId(NEXT_NATIVE_SURFACE_ID.fetch_add(1, Ordering::Relaxed) as u64);
+
+ self.pending_native_surface_updates.push(
+ NativeSurfaceOperation {
+ details: NativeSurfaceOperationDetails::CreateBackdropSurface {
+ id,
+ color,
+ },
+ }
+ );
+
+ id
+ }
+
+ /// Queue up allocation of a new OS native compositor surface with the
+ /// specified tile size.
+ pub fn create_compositor_surface(
+ &mut self,
+ virtual_offset: DeviceIntPoint,
+ tile_size: DeviceIntSize,
+ is_opaque: bool,
+ ) -> NativeSurfaceId {
+ let id = NativeSurfaceId(NEXT_NATIVE_SURFACE_ID.fetch_add(1, Ordering::Relaxed) as u64);
+
+ self.pending_native_surface_updates.push(
+ NativeSurfaceOperation {
+ details: NativeSurfaceOperationDetails::CreateSurface {
+ id,
+ virtual_offset,
+ tile_size,
+ is_opaque,
+ },
+ }
+ );
+
+ id
+ }
+
+ pub fn create_compositor_external_surface(
+ &mut self,
+ is_opaque: bool,
+ ) -> NativeSurfaceId {
+ let id = NativeSurfaceId(NEXT_NATIVE_SURFACE_ID.fetch_add(1, Ordering::Relaxed) as u64);
+
+ self.pending_native_surface_updates.push(
+ NativeSurfaceOperation {
+ details: NativeSurfaceOperationDetails::CreateExternalSurface {
+ id,
+ is_opaque,
+ },
+ }
+ );
+
+ id
+ }
+
+ /// Queue up destruction of an existing native OS surface. This is used when
+ /// a picture cache surface is dropped or resized.
+ pub fn destroy_compositor_surface(
+ &mut self,
+ id: NativeSurfaceId,
+ ) {
+ self.pending_native_surface_updates.push(
+ NativeSurfaceOperation {
+ details: NativeSurfaceOperationDetails::DestroySurface {
+ id,
+ }
+ }
+ );
+ }
+
+ /// Queue construction of a native compositor tile on a given surface.
+ pub fn create_compositor_tile(
+ &mut self,
+ id: NativeTileId,
+ ) {
+ self.pending_native_surface_updates.push(
+ NativeSurfaceOperation {
+ details: NativeSurfaceOperationDetails::CreateTile {
+ id,
+ },
+ }
+ );
+ }
+
+ /// Queue destruction of a native compositor tile.
+ pub fn destroy_compositor_tile(
+ &mut self,
+ id: NativeTileId,
+ ) {
+ self.pending_native_surface_updates.push(
+ NativeSurfaceOperation {
+ details: NativeSurfaceOperationDetails::DestroyTile {
+ id,
+ },
+ }
+ );
+ }
+
+ pub fn attach_compositor_external_image(
+ &mut self,
+ id: NativeSurfaceId,
+ external_image: ExternalImageId,
+ ) {
+ self.pending_native_surface_updates.push(
+ NativeSurfaceOperation {
+ details: NativeSurfaceOperationDetails::AttachExternalImage {
+ id,
+ external_image,
+ },
+ }
+ );
+ }
+
+
+ pub fn end_frame(&mut self, profile: &mut TransactionProfile) {
+ debug_assert_eq!(self.state, State::QueryResources);
+ profile_scope!("end_frame");
+ self.state = State::Idle;
+
+ // GC the render target pool, if it's currently > 64 MB in size.
+ //
+ // We use a simple scheme whereby we drop any texture that hasn't been used
+ // in the last 60 frames, until we are below the size threshold. This should
+ // generally prevent any sustained build-up of unused textures, unless we don't
+ // generate frames for a long period. This can happen when the window is
+ // minimized, and we probably want to flush all the WebRender caches in that case [1].
+ // There is also a second "red line" memory threshold which prevents
+ // memory exhaustion if many render targets are allocated within a small
+ // number of frames. For now this is set at 320 MB (10x the normal memory threshold).
+ //
+ // [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1494099
+ self.gc_render_targets(
+ 64 * 1024 * 1024,
+ 32 * 1024 * 1024 * 10,
+ 60,
+ );
+
+ self.texture_cache.end_frame(profile);
+ self.picture_textures.gc(
+ &mut self.texture_cache.pending_updates,
+ );
+
+ self.picture_textures.update_profile(profile);
+ }
+
+ pub fn set_debug_flags(&mut self, flags: DebugFlags) {
+ GLYPH_FLASHING.store(flags.contains(DebugFlags::GLYPH_FLASHING), std::sync::atomic::Ordering::Relaxed);
+ self.texture_cache.set_debug_flags(flags);
+ self.picture_textures.set_debug_flags(flags);
+ }
+
+ pub fn clear(&mut self, what: ClearCache) {
+ if what.contains(ClearCache::IMAGES) {
+ for (_key, mut cached) in self.cached_images.resources.drain() {
+ cached.drop_from_cache(&mut self.texture_cache);
+ }
+ }
+ if what.contains(ClearCache::GLYPHS) {
+ self.cached_glyphs.clear();
+ }
+ if what.contains(ClearCache::GLYPH_DIMENSIONS) {
+ self.cached_glyph_dimensions.clear();
+ }
+ if what.contains(ClearCache::RENDER_TASKS) {
+ self.cached_render_tasks.clear();
+ }
+ if what.contains(ClearCache::TEXTURE_CACHE) {
+ self.texture_cache.clear_all();
+ self.picture_textures.clear(&mut self.texture_cache.pending_updates);
+ }
+ if what.contains(ClearCache::RENDER_TARGETS) {
+ self.clear_render_target_pool();
+ }
+ }
+
+ pub fn clear_namespace(&mut self, namespace: IdNamespace) {
+ self.clear_images(|k| k.0 == namespace);
+
+ // First clear out any non-shared resources associated with the namespace.
+ self.resources.fonts.instances.clear_namespace(namespace);
+ let deleted_keys = self.resources.fonts.templates.clear_namespace(namespace);
+ self.glyph_rasterizer.delete_fonts(&deleted_keys);
+ self.cached_glyphs.clear_namespace(namespace);
+ if let Some(handler) = &mut self.blob_image_handler {
+ handler.clear_namespace(namespace);
+ }
+
+ // Check for any shared instance keys that were remapped from the namespace.
+ let shared_instance_keys = self.resources.fonts.instance_keys.clear_namespace(namespace);
+ if !shared_instance_keys.is_empty() {
+ self.resources.fonts.instances.delete_font_instances(&shared_instance_keys);
+ self.cached_glyphs.delete_font_instances(&shared_instance_keys, &mut self.glyph_rasterizer);
+ // Blob font instances are not shared across namespaces, so there is no
+ // need to call the handler for them individually.
+ }
+
+ // Finally check for any shared font keys that were remapped from the namespace.
+ let shared_keys = self.resources.fonts.font_keys.clear_namespace(namespace);
+ if !shared_keys.is_empty() {
+ self.glyph_rasterizer.delete_fonts(&shared_keys);
+ self.resources.fonts.templates.delete_fonts(&shared_keys);
+ self.cached_glyphs.delete_fonts(&shared_keys);
+ if let Some(handler) = &mut self.blob_image_handler {
+ for &key in &shared_keys {
+ handler.delete_font(key);
+ }
+ }
+ }
+ }
+
+ /// Reports the CPU heap usage of this ResourceCache.
+ ///
+ /// NB: It would be much better to use the derive(MallocSizeOf) machinery
+ /// here, but the Arcs complicate things. The two ways to handle that would
+ /// be to either (a) Implement MallocSizeOf manually for the things that own
+ /// them and manually avoid double-counting, or (b) Use the "seen this pointer
+ /// yet" machinery from the proper malloc_size_of crate. We can do this if/when
+ /// more accurate memory reporting on these resources becomes a priority.
+ pub fn report_memory(&self, op: VoidPtrToSizeFn) -> MemoryReport {
+ let mut report = MemoryReport::default();
+
+ let mut seen_fonts = std::collections::HashSet::new();
+ // Measure fonts. We only need the templates here, because the instances
+ // don't have big buffers.
+ for (_, font) in self.resources.fonts.templates.lock().iter() {
+ if let FontTemplate::Raw(ref raw, _) = font {
+ report.fonts += unsafe { op(raw.as_ptr() as *const c_void) };
+ seen_fonts.insert(raw.as_ptr());
+ }
+ }
+
+ for font in self.resources.weak_fonts.iter() {
+ if !seen_fonts.contains(&font.as_ptr()) {
+ report.weak_fonts += unsafe { op(font.as_ptr() as *const c_void) };
+ }
+ }
+
+ // Measure images.
+ for (_, image) in self.resources.image_templates.images.iter() {
+ report.images += match image.data {
+ CachedImageData::Raw(ref v) => unsafe { op(v.as_ptr() as *const c_void) },
+ CachedImageData::Blob | CachedImageData::External(..) => 0,
+ }
+ }
+
+ // Mesure rasterized blobs.
+ // TODO(gw): Temporarily disabled while we roll back a crash. We can re-enable
+ // these when that crash is fixed.
+ /*
+ for (_, image) in self.rasterized_blob_images.iter() {
+ let mut accumulate = |b: &RasterizedBlobImage| {
+ report.rasterized_blobs += unsafe { op(b.data.as_ptr() as *const c_void) };
+ };
+ match image {
+ RasterizedBlob::Tiled(map) => map.values().for_each(&mut accumulate),
+ RasterizedBlob::NonTiled(vec) => vec.iter().for_each(&mut accumulate),
+ };
+ }
+ */
+
+ report
+ }
+
+ /// Properly deletes all images matching the predicate.
+ fn clear_images<F: Fn(&ImageKey) -> bool>(&mut self, f: F) {
+ let keys = self.resources.image_templates.images.keys().filter(|k| f(*k))
+ .cloned().collect::<SmallVec<[ImageKey; 16]>>();
+
+ for key in keys {
+ self.delete_image_template(key);
+ }
+
+ #[cfg(features="leak_checks")]
+ let check_leaks = true;
+ #[cfg(not(features="leak_checks"))]
+ let check_leaks = false;
+
+ if check_leaks {
+ let blob_f = |key: &BlobImageKey| { f(&key.as_image()) };
+ assert!(!self.resources.image_templates.images.keys().any(&f));
+ assert!(!self.cached_images.resources.keys().any(&f));
+ assert!(!self.rasterized_blob_images.keys().any(&blob_f));
+ }
+ }
+
+ /// Get a render target from the pool, or allocate a new one if none are
+ /// currently available that match the requested parameters.
+ pub fn get_or_create_render_target_from_pool(
+ &mut self,
+ size: DeviceIntSize,
+ format: ImageFormat,
+ ) -> CacheTextureId {
+ for target in &mut self.render_target_pool {
+ if target.size == size &&
+ target.format == format &&
+ !target.is_active {
+ // Found a target that's not currently in use which matches. Update
+ // the last_frame_used for GC purposes.
+ target.is_active = true;
+ target.last_frame_used = self.current_frame_id;
+ return target.texture_id;
+ }
+ }
+
+ // Need to create a new render target and add it to the pool
+
+ let texture_id = self.texture_cache.alloc_render_target(
+ size,
+ format,
+ );
+
+ self.render_target_pool.push(RenderTarget {
+ size,
+ format,
+ texture_id,
+ is_active: true,
+ last_frame_used: self.current_frame_id,
+ });
+
+ texture_id
+ }
+
+ /// Return a render target to the pool.
+ pub fn return_render_target_to_pool(
+ &mut self,
+ id: CacheTextureId,
+ ) {
+ let target = self.render_target_pool
+ .iter_mut()
+ .find(|t| t.texture_id == id)
+ .expect("bug: invalid render target id");
+
+ assert!(target.is_active);
+ target.is_active = false;
+ }
+
+ /// Clear all current render targets (e.g. on memory pressure)
+ fn clear_render_target_pool(
+ &mut self,
+ ) {
+ for target in self.render_target_pool.drain(..) {
+ debug_assert!(!target.is_active);
+ self.texture_cache.free_render_target(target.texture_id);
+ }
+ }
+
+ /// Garbage collect and remove old render targets from the pool that haven't
+ /// been used for some time.
+ fn gc_render_targets(
+ &mut self,
+ total_bytes_threshold: usize,
+ total_bytes_red_line_threshold: usize,
+ frames_threshold: u64,
+ ) {
+ // Get the total GPU memory size used by the current render target pool
+ let mut rt_pool_size_in_bytes: usize = self.render_target_pool
+ .iter()
+ .map(|t| t.size_in_bytes())
+ .sum();
+
+ // If the total size of the pool is less than the threshold, don't bother
+ // trying to GC any targets
+ if rt_pool_size_in_bytes <= total_bytes_threshold {
+ return;
+ }
+
+ // Sort the current pool by age, so that we remove oldest textures first
+ self.render_target_pool.sort_by_key(|t| t.last_frame_used);
+
+ // We can't just use retain() because `RenderTarget` requires manual cleanup.
+ let mut retained_targets = SmallVec::<[RenderTarget; 8]>::new();
+
+ for target in self.render_target_pool.drain(..) {
+ assert!(!target.is_active);
+
+ // Drop oldest textures until we are under the allowed size threshold.
+ // However, if it's been used in very recently, it is always kept around,
+ // which ensures we don't thrash texture allocations on pages that do
+ // require a very large render target pool and are regularly changing.
+ let above_red_line = rt_pool_size_in_bytes > total_bytes_red_line_threshold;
+ let above_threshold = rt_pool_size_in_bytes > total_bytes_threshold;
+ let used_recently = target.used_recently(self.current_frame_id, frames_threshold);
+ let used_this_frame = target.last_frame_used == self.current_frame_id;
+
+ if !used_this_frame && (above_red_line || (above_threshold && !used_recently)) {
+ rt_pool_size_in_bytes -= target.size_in_bytes();
+ self.texture_cache.free_render_target(target.texture_id);
+ } else {
+ retained_targets.push(target);
+ }
+ }
+
+ self.render_target_pool.extend(retained_targets);
+ }
+
+ #[cfg(test)]
+ pub fn validate_surfaces(
+ &self,
+ expected_surfaces: &[(i32, i32, ImageFormat)],
+ ) {
+ assert_eq!(expected_surfaces.len(), self.render_target_pool.len());
+
+ for (expected, surface) in expected_surfaces.iter().zip(self.render_target_pool.iter()) {
+ assert_eq!(DeviceIntSize::new(expected.0, expected.1), surface.size);
+ assert_eq!(expected.2, surface.format);
+ }
+ }
+}
+
+impl Drop for ResourceCache {
+ fn drop(&mut self) {
+ self.clear_images(|_| true);
+ }
+}
+
+#[cfg(any(feature = "capture", feature = "replay"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+struct PlainFontTemplate {
+ data: String,
+ index: u32,
+}
+
+#[cfg(any(feature = "capture", feature = "replay"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+struct PlainImageTemplate {
+ data: String,
+ descriptor: ImageDescriptor,
+ tiling: Option<TileSize>,
+ generation: ImageGeneration,
+}
+
+#[cfg(any(feature = "capture", feature = "replay"))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PlainResources {
+ font_templates: FastHashMap<FontKey, PlainFontTemplate>,
+ font_instances: Vec<BaseFontInstance>,
+ image_templates: FastHashMap<ImageKey, PlainImageTemplate>,
+}
+
+#[cfg(feature = "capture")]
+#[derive(Serialize)]
+pub struct PlainCacheRef<'a> {
+ current_frame_id: FrameId,
+ glyphs: &'a GlyphCache,
+ glyph_dimensions: &'a GlyphDimensionsCache,
+ images: &'a ImageCache,
+ render_tasks: &'a RenderTaskCache,
+ textures: &'a TextureCache,
+ picture_textures: &'a PictureTextures,
+}
+
+#[cfg(feature = "replay")]
+#[derive(Deserialize)]
+pub struct PlainCacheOwn {
+ current_frame_id: FrameId,
+ glyphs: GlyphCache,
+ glyph_dimensions: GlyphDimensionsCache,
+ images: ImageCache,
+ render_tasks: RenderTaskCache,
+ textures: TextureCache,
+ picture_textures: PictureTextures,
+}
+
+#[cfg(feature = "replay")]
+const NATIVE_FONT: &'static [u8] = include_bytes!("../res/Proggy.ttf");
+
+// This currently only casts the unit but will soon apply an offset
+fn to_image_dirty_rect(blob_dirty_rect: &BlobDirtyRect) -> ImageDirtyRect {
+ match *blob_dirty_rect {
+ DirtyRect::Partial(rect) => DirtyRect::Partial(rect.cast_unit()),
+ DirtyRect::All => DirtyRect::All,
+ }
+}
+
+impl ResourceCache {
+ #[cfg(feature = "capture")]
+ pub fn save_capture(
+ &mut self, root: &PathBuf
+ ) -> (PlainResources, Vec<ExternalCaptureImage>) {
+ use std::fs;
+ use std::io::Write;
+
+ info!("saving resource cache");
+ let res = &self.resources;
+ let path_fonts = root.join("fonts");
+ if !path_fonts.is_dir() {
+ fs::create_dir(&path_fonts).unwrap();
+ }
+ let path_images = root.join("images");
+ if !path_images.is_dir() {
+ fs::create_dir(&path_images).unwrap();
+ }
+ let path_blobs = root.join("blobs");
+ if !path_blobs.is_dir() {
+ fs::create_dir(&path_blobs).unwrap();
+ }
+ let path_externals = root.join("externals");
+ if !path_externals.is_dir() {
+ fs::create_dir(&path_externals).unwrap();
+ }
+
+ info!("\tfont templates");
+ let mut font_paths = FastHashMap::default();
+ for template in res.fonts.templates.lock().values() {
+ let data: &[u8] = match *template {
+ FontTemplate::Raw(ref arc, _) => arc,
+ FontTemplate::Native(_) => continue,
+ };
+ let font_id = res.fonts.templates.len() + 1;
+ let entry = match font_paths.entry(data.as_ptr()) {
+ Entry::Occupied(_) => continue,
+ Entry::Vacant(e) => e,
+ };
+ let file_name = format!("{}.raw", font_id);
+ let short_path = format!("fonts/{}", file_name);
+ fs::File::create(path_fonts.join(file_name))
+ .expect(&format!("Unable to create {}", short_path))
+ .write_all(data)
+ .unwrap();
+ entry.insert(short_path);
+ }
+
+ info!("\timage templates");
+ let mut image_paths = FastHashMap::default();
+ let mut other_paths = FastHashMap::default();
+ let mut num_blobs = 0;
+ let mut external_images = Vec::new();
+ for (&key, template) in res.image_templates.images.iter() {
+ let desc = &template.descriptor;
+ match template.data {
+ CachedImageData::Raw(ref arc) => {
+ let image_id = image_paths.len() + 1;
+ let entry = match image_paths.entry(arc.as_ptr()) {
+ Entry::Occupied(_) => continue,
+ Entry::Vacant(e) => e,
+ };
+
+ #[cfg(feature = "png")]
+ CaptureConfig::save_png(
+ root.join(format!("images/{}.png", image_id)),
+ desc.size,
+ desc.format,
+ desc.stride,
+ &arc,
+ );
+ let file_name = format!("{}.raw", image_id);
+ let short_path = format!("images/{}", file_name);
+ fs::File::create(path_images.join(file_name))
+ .expect(&format!("Unable to create {}", short_path))
+ .write_all(&*arc)
+ .unwrap();
+ entry.insert(short_path);
+ }
+ CachedImageData::Blob => {
+ warn!("Tiled blob images aren't supported yet");
+ let result = RasterizedBlobImage {
+ rasterized_rect: desc.size.into(),
+ data: Arc::new(vec![0; desc.compute_total_size() as usize])
+ };
+
+ assert_eq!(result.rasterized_rect.size(), desc.size);
+ assert_eq!(result.data.len(), desc.compute_total_size() as usize);
+
+ num_blobs += 1;
+ #[cfg(feature = "png")]
+ CaptureConfig::save_png(
+ root.join(format!("blobs/{}.png", num_blobs)),
+ desc.size,
+ desc.format,
+ desc.stride,
+ &result.data,
+ );
+ let file_name = format!("{}.raw", num_blobs);
+ let short_path = format!("blobs/{}", file_name);
+ let full_path = path_blobs.clone().join(&file_name);
+ fs::File::create(full_path)
+ .expect(&format!("Unable to create {}", short_path))
+ .write_all(&result.data)
+ .unwrap();
+ other_paths.insert(key, short_path);
+ }
+ CachedImageData::External(ref ext) => {
+ let short_path = format!("externals/{}", external_images.len() + 1);
+ other_paths.insert(key, short_path.clone());
+ external_images.push(ExternalCaptureImage {
+ short_path,
+ descriptor: desc.clone(),
+ external: ext.clone(),
+ });
+ }
+ }
+ }
+
+ let mut font_templates = FastHashMap::default();
+ let mut font_remap = FastHashMap::default();
+ // Generate a map from duplicate font keys to their template.
+ for key in res.fonts.font_keys.keys() {
+ let shared_key = res.fonts.font_keys.map_key(&key);
+ let template = match res.fonts.templates.get_font(&shared_key) {
+ Some(template) => template,
+ None => {
+ debug!("Failed serializing font template {:?}", key);
+ continue;
+ }
+ };
+ let plain_font = match template {
+ FontTemplate::Raw(arc, index) => {
+ PlainFontTemplate {
+ data: font_paths[&arc.as_ptr()].clone(),
+ index,
+ }
+ }
+ #[cfg(not(target_os = "macos"))]
+ FontTemplate::Native(native) => {
+ PlainFontTemplate {
+ data: native.path.to_string_lossy().to_string(),
+ index: native.index,
+ }
+ }
+ #[cfg(target_os = "macos")]
+ FontTemplate::Native(native) => {
+ PlainFontTemplate {
+ data: native.name,
+ index: 0,
+ }
+ }
+ };
+ font_templates.insert(key, plain_font);
+ // Generate a reverse map from a shared key to a representive key.
+ font_remap.insert(shared_key, key);
+ }
+ let mut font_instances = Vec::new();
+ // Build a list of duplicate instance keys.
+ for instance_key in res.fonts.instance_keys.keys() {
+ let shared_key = res.fonts.instance_keys.map_key(&instance_key);
+ let instance = match res.fonts.instances.get_font_instance(shared_key) {
+ Some(instance) => instance,
+ None => {
+ debug!("Failed serializing font instance {:?}", instance_key);
+ continue;
+ }
+ };
+ // Target the instance towards a representive duplicate font key. The font key will be
+ // de-duplicated on load to an appropriate shared key.
+ font_instances.push(BaseFontInstance {
+ font_key: font_remap.get(&instance.font_key).cloned().unwrap_or(instance.font_key),
+ instance_key,
+ ..(*instance).clone()
+ });
+ }
+ let resources = PlainResources {
+ font_templates,
+ font_instances,
+ image_templates: res.image_templates.images
+ .iter()
+ .map(|(key, template)| {
+ (*key, PlainImageTemplate {
+ data: match template.data {
+ CachedImageData::Raw(ref arc) => image_paths[&arc.as_ptr()].clone(),
+ _ => other_paths[key].clone(),
+ },
+ descriptor: template.descriptor.clone(),
+ tiling: template.tiling,
+ generation: template.generation,
+ })
+ })
+ .collect(),
+ };
+
+ (resources, external_images)
+ }
+
+ #[cfg(feature = "capture")]
+ pub fn save_caches(&self, _root: &PathBuf) -> PlainCacheRef {
+ PlainCacheRef {
+ current_frame_id: self.current_frame_id,
+ glyphs: &self.cached_glyphs,
+ glyph_dimensions: &self.cached_glyph_dimensions,
+ images: &self.cached_images,
+ render_tasks: &self.cached_render_tasks,
+ textures: &self.texture_cache,
+ picture_textures: &self.picture_textures,
+ }
+ }
+
+ #[cfg(feature = "replay")]
+ pub fn load_capture(
+ &mut self,
+ resources: PlainResources,
+ caches: Option<PlainCacheOwn>,
+ config: &CaptureConfig,
+ ) -> Vec<PlainExternalImage> {
+ use std::{fs, path::Path};
+ use crate::texture_cache::TextureCacheConfig;
+
+ info!("loading resource cache");
+ //TODO: instead of filling the local path to Arc<data> map as we process
+ // each of the resource types, we could go through all of the local paths
+ // and fill out the map as the first step.
+ let mut raw_map = FastHashMap::<String, Arc<Vec<u8>>>::default();
+
+ self.clear(ClearCache::all());
+ self.clear_images(|_| true);
+
+ match caches {
+ Some(cached) => {
+ self.current_frame_id = cached.current_frame_id;
+ self.cached_glyphs = cached.glyphs;
+ self.cached_glyph_dimensions = cached.glyph_dimensions;
+ self.cached_images = cached.images;
+ self.cached_render_tasks = cached.render_tasks;
+ self.texture_cache = cached.textures;
+ self.picture_textures = cached.picture_textures;
+ }
+ None => {
+ self.current_frame_id = FrameId::INVALID;
+ self.texture_cache = TextureCache::new(
+ self.texture_cache.max_texture_size(),
+ self.texture_cache.tiling_threshold(),
+ self.texture_cache.color_formats(),
+ self.texture_cache.swizzle_settings(),
+ &TextureCacheConfig::DEFAULT,
+ );
+ self.picture_textures = PictureTextures::new(
+ self.picture_textures.default_tile_size(),
+ self.picture_textures.filter(),
+ );
+ }
+ }
+
+ self.glyph_rasterizer.reset();
+ let res = &mut self.resources;
+ res.fonts.templates.clear();
+ res.fonts.instances.clear();
+ res.image_templates.images.clear();
+
+ info!("\tfont templates...");
+ let root = config.resource_root();
+ let native_font_replacement = Arc::new(NATIVE_FONT.to_vec());
+ for (key, plain_template) in resources.font_templates {
+ let arc = match raw_map.entry(plain_template.data) {
+ Entry::Occupied(e) => {
+ e.get().clone()
+ }
+ Entry::Vacant(e) => {
+ let file_path = if Path::new(e.key()).is_absolute() {
+ PathBuf::from(e.key())
+ } else {
+ root.join(e.key())
+ };
+ let arc = match fs::read(file_path) {
+ Ok(buffer) => Arc::new(buffer),
+ Err(err) => {
+ error!("Unable to open font template {:?}: {:?}", e.key(), err);
+ Arc::clone(&native_font_replacement)
+ }
+ };
+ e.insert(arc).clone()
+ }
+ };
+
+ let template = FontTemplate::Raw(arc, plain_template.index);
+ // Only add the template if this is the first time it has been seen.
+ if let Some(shared_key) = res.fonts.font_keys.add_key(&key, &template) {
+ self.glyph_rasterizer.add_font(shared_key, template.clone());
+ res.fonts.templates.add_font(shared_key, template);
+ }
+ }
+
+ info!("\tfont instances...");
+ for instance in resources.font_instances {
+ // Target the instance to a shared font key.
+ let base = BaseFontInstance {
+ font_key: res.fonts.font_keys.map_key(&instance.font_key),
+ ..instance
+ };
+ if let Some(shared_instance) = res.fonts.instance_keys.add_key(base) {
+ res.fonts.instances.add_font_instance(shared_instance);
+ }
+ }
+
+ info!("\timage templates...");
+ let mut external_images = Vec::new();
+ for (key, template) in resources.image_templates {
+ let data = match config.deserialize_for_resource::<PlainExternalImage, _>(&template.data) {
+ Some(plain) => {
+ let ext_data = plain.external;
+ external_images.push(plain);
+ CachedImageData::External(ext_data)
+ }
+ None => {
+ let arc = match raw_map.entry(template.data) {
+ Entry::Occupied(e) => {
+ e.get().clone()
+ }
+ Entry::Vacant(e) => {
+ let buffer = fs::read(root.join(e.key()))
+ .expect(&format!("Unable to open {}", e.key()));
+ e.insert(Arc::new(buffer))
+ .clone()
+ }
+ };
+ CachedImageData::Raw(arc)
+ }
+ };
+
+ res.image_templates.images.insert(key, ImageResource {
+ data,
+ descriptor: template.descriptor,
+ tiling: template.tiling,
+ visible_rect: template.descriptor.size.into(),
+ generation: template.generation,
+ });
+ }
+
+ external_images
+ }
+
+ #[cfg(feature = "capture")]
+ pub fn save_capture_sequence(&mut self, config: &mut CaptureConfig) -> Vec<ExternalCaptureImage> {
+ if self.capture_dirty {
+ self.capture_dirty = false;
+ config.prepare_resource();
+ let (resources, deferred) = self.save_capture(&config.resource_root());
+ config.serialize_for_resource(&resources, "plain-resources.ron");
+ deferred
+ } else {
+ Vec::new()
+ }
+ }
+}