Bug 1560520 - limit the size of WebRender's glyph cache. r=kvark

Differential Revision: https://phabricator.services.mozilla.com/D37982

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Lee Salzman 2019-07-17 17:52:44 +00:00
Родитель 8333f6cdcc
Коммит ee4cc890cb
9 изменённых файлов: 197 добавлений и 93 удалений

Просмотреть файл

@ -6,6 +6,7 @@
use crate::api::units::DeviceIntPoint;
use crate::glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
use crate::internal_types::FastHashMap;
use crate::render_backend::{FrameId, FrameStamp};
use crate::render_task::RenderTaskCache;
#[cfg(feature = "pathfinder")]
use crate::render_task::RenderTaskCacheKey;
@ -43,29 +44,41 @@ pub enum GlyphCacheEntry {
impl GlyphCacheEntry {
#[cfg(feature = "pathfinder")]
fn is_allocated(&self, texture_cache: &TextureCache, render_task_cache: &RenderTaskCache)
-> bool {
fn get_allocated_size(&self, texture_cache: &TextureCache, render_task_cache: &RenderTaskCache)
-> Option<usize> {
match *self {
GlyphCacheEntry::Cached(ref glyph) => {
let render_task_cache_key = &glyph.render_task_cache_key;
render_task_cache.cache_item_is_allocated_for_render_task(texture_cache,
&render_task_cache_key)
render_task_cache.get_allocated_size_for_render_task(texture_cache,
&render_task_cache_key)
}
GlyphCacheEntry::Pending => true,
GlyphCacheEntry::Pending => Some(0),
// If the cache only has blank glyphs left, just get rid of it.
GlyphCacheEntry::Blank => false,
GlyphCacheEntry::Blank => None,
}
}
#[cfg(feature = "pathfinder")]
fn mark_unused(&self, _: &mut TextureCache) {
}
#[cfg(not(feature = "pathfinder"))]
fn get_allocated_size(&self, texture_cache: &TextureCache, _: &RenderTaskCache)
-> Option<usize> {
match *self {
GlyphCacheEntry::Cached(ref glyph) => {
texture_cache.get_allocated_size(&glyph.texture_cache_handle)
}
GlyphCacheEntry::Pending => Some(0),
// If the cache only has blank glyphs left, just get rid of it.
GlyphCacheEntry::Blank => None,
}
}
#[cfg(not(feature = "pathfinder"))]
fn is_allocated(&self, texture_cache: &TextureCache, _: &RenderTaskCache) -> bool {
match *self {
GlyphCacheEntry::Cached(ref glyph) => {
texture_cache.is_allocated(&glyph.texture_cache_handle)
}
GlyphCacheEntry::Pending => true,
// If the cache only has blank glyphs left, just get rid of it.
GlyphCacheEntry::Blank => false,
fn mark_unused(&self, texture_cache: &mut TextureCache) {
if let GlyphCacheEntry::Cached(ref glyph) = *self {
texture_cache.mark_unused(&glyph.texture_cache_handle);
}
}
}
@ -79,11 +92,53 @@ pub enum CachedGlyphData {
Gpu,
}
pub type GlyphKeyCache = ResourceClassCache<GlyphKey, GlyphCacheEntry, EvictionNotice>;
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Default)]
pub struct GlyphKeyCacheInfo {
eviction_notice: EvictionNotice,
last_frame_used: FrameId,
bytes_used: usize,
}
pub type GlyphKeyCache = ResourceClassCache<GlyphKey, GlyphCacheEntry, GlyphKeyCacheInfo>;
impl GlyphKeyCache {
const DIRTY: usize = !0;
pub fn eviction_notice(&self) -> &EvictionNotice {
&self.user_data
&self.user_data.eviction_notice
}
fn clear_glyphs(&mut self, texture_cache: &mut TextureCache) {
for (_, entry) in self.iter() {
entry.mark_unused(texture_cache);
}
self.clear();
self.user_data.bytes_used = 0;
}
pub fn add_glyph(&mut self, key: GlyphKey, value: GlyphCacheEntry) {
self.insert(key, value);
self.user_data.bytes_used = Self::DIRTY;
}
fn clear_evicted(
&mut self,
texture_cache: &TextureCache,
render_task_cache: &RenderTaskCache,
) {
if self.eviction_notice().check() || self.user_data.bytes_used == Self::DIRTY {
// If there are evictions, filter out any glyphs evicted from the
// texture cache from the glyph key cache.
let mut usage = 0;
self.retain(|_, entry| {
let size = entry.get_allocated_size(texture_cache, render_task_cache);
usage += size.unwrap_or(0);
size.is_some()
});
self.user_data.bytes_used = usage;
}
}
}
@ -91,19 +146,30 @@ impl GlyphKeyCache {
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct GlyphCache {
glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
current_frame: FrameId,
bytes_used: usize,
max_bytes_used: usize,
}
impl GlyphCache {
pub fn new() -> Self {
/// The default space usage threshold, in bytes, after which to start pruning away old fonts.
pub const DEFAULT_MAX_BYTES_USED: usize = 5 * 1024 * 1024;
pub fn new(max_bytes_used: usize) -> Self {
GlyphCache {
glyph_key_caches: FastHashMap::default(),
current_frame: Default::default(),
bytes_used: 0,
max_bytes_used,
}
}
pub fn get_glyph_key_cache_for_font_mut(&mut self, font: FontInstance) -> &mut GlyphKeyCache {
self.glyph_key_caches
.entry(font)
.or_insert_with(GlyphKeyCache::new)
let cache = self.glyph_key_caches
.entry(font)
.or_insert_with(GlyphKeyCache::new);
cache.user_data.last_frame_used = self.current_frame;
cache
}
pub fn get_glyph_key_cache_for_font(&self, font: &FontInstance) -> &GlyphKeyCache {
@ -121,7 +187,7 @@ impl GlyphCache {
self.glyph_key_caches = FastHashMap::default();
}
pub fn clear_fonts<F>(&mut self, key_fun: F)
pub fn clear_fonts<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
where
for<'r> F: Fn(&'r &FontInstance) -> bool,
{
@ -131,45 +197,70 @@ impl GlyphCache {
return true;
}
cache.clear();
cache.clear_glyphs(texture_cache);
false
})
}
// Clear out evicted entries from glyph key caches and, if possible,
// also remove entirely any subsequently empty glyph key caches.
/// Clear out evicted entries from glyph key caches.
fn clear_evicted(
&mut self,
texture_cache: &TextureCache,
render_task_cache: &RenderTaskCache,
glyph_rasterizer: &mut GlyphRasterizer,
) {
self.glyph_key_caches.retain(|key, cache| {
let mut usage = 0;
for cache in self.glyph_key_caches.values_mut() {
// Scan for any glyph key caches that have evictions.
if cache.eviction_notice().check() {
// If there are evictions, filter out any glyphs evicted from the
// texture cache from the glyph key cache.
let mut keep_cache = false;
cache.retain(|_, entry| {
let keep_glyph = entry.is_allocated(texture_cache, render_task_cache);
keep_cache |= keep_glyph;
keep_glyph
});
if !keep_cache {
glyph_rasterizer.delete_font_instance(key);
}
// Only keep the glyph key cache if it still has valid glyphs.
keep_cache
cache.clear_evicted(texture_cache, render_task_cache);
usage += cache.user_data.bytes_used;
}
self.bytes_used = usage;
}
/// If possible, remove entirely any empty glyph key caches.
fn clear_empty_caches(&mut self, glyph_rasterizer: &mut GlyphRasterizer) {
self.glyph_key_caches.retain(|key, cache| {
// Discard the glyph key cache if it has no valid glyphs.
if cache.is_empty() {
glyph_rasterizer.delete_font_instance(key);
false
} else {
true
}
});
}
/// Check the total space usage of the glyph cache. If it exceeds the maximum usage threshold,
/// then start clearing the oldest glyphs until below the threshold.
fn prune_excess_usage(&mut self, texture_cache: &mut TextureCache) {
if self.bytes_used < self.max_bytes_used {
return;
}
// Usage is above the threshold. Get a last-recently-used ordered list of caches to clear.
let mut caches: Vec<_> = self.glyph_key_caches.values_mut().collect();
caches.sort_unstable_by(|a, b| {
a.user_data.last_frame_used.cmp(&b.user_data.last_frame_used)
});
// Clear out the oldest caches until below the threshold.
for cache in caches {
self.bytes_used -= cache.user_data.bytes_used;
cache.clear_glyphs(texture_cache);
if self.bytes_used < self.max_bytes_used {
break;
}
}
}
pub fn begin_frame(&mut self,
texture_cache: &TextureCache,
stamp: FrameStamp,
texture_cache: &mut TextureCache,
render_task_cache: &RenderTaskCache,
glyph_rasterizer: &mut GlyphRasterizer) {
self.clear_evicted(texture_cache, render_task_cache, glyph_rasterizer);
self.current_frame = stamp.frame_id();
self.clear_evicted(texture_cache, render_task_cache);
self.prune_excess_usage(texture_cache);
// Clearing evicted glyphs and pruning excess usage might have produced empty caches,
// so get rid of them if possible.
self.clear_empty_caches(glyph_rasterizer);
}
}

Просмотреть файл

@ -863,7 +863,7 @@ mod test_glyph_rasterizer {
.build();
let workers = Arc::new(worker.unwrap());
let mut glyph_rasterizer = GlyphRasterizer::new(workers).unwrap();
let mut glyph_cache = GlyphCache::new();
let mut glyph_cache = GlyphCache::new(GlyphCache::DEFAULT_MAX_BYTES_USED);
let mut gpu_cache = GpuCache::new_for_testing();
let mut texture_cache = TextureCache::new_for_testing(2048, 1024);
let mut render_task_cache = RenderTaskCache::new();

Просмотреть файл

@ -20,7 +20,6 @@ use crate::texture_cache::{TextureCache, TextureCacheHandle, Eviction};
use crate::gpu_cache::GpuCache;
use crate::render_task::{RenderTaskGraph, RenderTaskCache};
use crate::profiler::TextureCacheProfileCounters;
use std::collections::hash_map::Entry;
impl FontContexts {
/// Get access to the font context associated to the current thread.
@ -57,32 +56,23 @@ impl GlyphRasterizer {
// select glyphs that have not been requested yet.
for key in glyph_keys {
match glyph_key_cache.entry(key.clone()) {
Entry::Occupied(entry) => {
let value = entry.into_mut();
match *value {
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) {
continue;
}
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) {
continue;
}
// Otherwise, skip the entry if it is blank or pending.
GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => continue,
// 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.
}
// 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.
*value = GlyphCacheEntry::Pending;
new_glyphs.push((*key).clone());
}
Entry::Vacant(entry) => {
// This is the first time we've seen the glyph, so mark it as pending.
entry.insert(GlyphCacheEntry::Pending);
new_glyphs.push((*key).clone());
// Otherwise, skip the entry if it is blank or pending.
GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => continue,
}
}
new_glyphs.push(key.clone());
glyph_key_cache.add_glyph(key.clone(), GlyphCacheEntry::Pending);
}
if new_glyphs.is_empty() {
@ -197,7 +187,7 @@ impl GlyphRasterizer {
})
}
};
glyph_key_cache.insert(key, glyph_info);
glyph_key_cache.add_glyph(key, glyph_info);
}
}

Просмотреть файл

@ -19,7 +19,6 @@ use std::sync::{Arc, Mutex, MutexGuard};
use crate::glyph_rasterizer::AddFont;
use crate::internal_types::ResourceCacheError;
use crate::glyph_cache::{GlyphCache, GlyphCacheEntry, CachedGlyphInfo};
use std::collections::hash_map::Entry;
use std::f32;
use crate::glyph_rasterizer::{FontInstance, GlyphRasterizer, GlyphFormat, GlyphKey, FontContexts};
use crate::texture_cache::TextureCache;
@ -169,20 +168,9 @@ impl GlyphRasterizer {
// select glyphs that have not been requested yet.
for glyph_key in glyph_keys {
let mut cached_glyph_info = None;
match glyph_key_cache.entry(glyph_key.clone()) {
Entry::Occupied(entry) => {
let value = entry.into_mut();
match *value {
GlyphCacheEntry::Cached(ref glyph_info) => {
cached_glyph_info = Some(glyph_info.clone())
}
GlyphCacheEntry::Blank | GlyphCacheEntry::Pending => {}
}
}
Entry::Vacant(_) => {}
}
if cached_glyph_info.is_none() {
if let Some(GlyphCacheEntry::Cached(ref info)) = glyph_key_cache.try_get(glyph_key) {
cached_glyph_info = Some(info.clone());
} else {
let pathfinder_font_context = self.font_contexts.lock_pathfinder_context();
let pathfinder_font_instance = pathfinder_font_renderer::FontInstance {
@ -234,7 +222,7 @@ impl GlyphRasterizer {
None => GlyphCacheEntry::Blank,
};
glyph_key_cache.insert(glyph_key.clone(), handle);
glyph_key_cache.add_glyph(glyph_key.clone(), handle);
}
}

Просмотреть файл

@ -118,6 +118,12 @@ impl FrameId {
pub const INVALID: FrameId = FrameId(0);
}
impl Default for FrameId {
fn default() -> Self {
FrameId::INVALID
}
}
impl ::std::ops::Add<usize> for FrameId {
type Output = Self;
fn add(self, other: usize) -> FrameId {

Просмотреть файл

@ -2136,13 +2136,13 @@ impl RenderTaskCache {
}
#[allow(dead_code)]
pub fn cache_item_is_allocated_for_render_task(&self,
texture_cache: &TextureCache,
key: &RenderTaskCacheKey)
-> bool {
pub fn get_allocated_size_for_render_task(&self,
texture_cache: &TextureCache,
key: &RenderTaskCacheKey)
-> Option<usize> {
let handle = self.map.get(key).unwrap();
let cache_entry = self.cache_entries.get(handle);
texture_cache.is_allocated(&cache_entry.handle)
texture_cache.get_allocated_size(&cache_entry.handle)
}
}

Просмотреть файл

@ -58,6 +58,7 @@ use crate::device::query::GpuTimer;
use euclid::{rect, Transform3D, TypedScale};
use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
use gleam::gl;
use crate::glyph_cache::GlyphCache;
use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
use crate::gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd};
@ -2082,6 +2083,7 @@ impl Renderer {
});
let sampler = options.sampler;
let namespace_alloc_by_client = options.namespace_alloc_by_client;
let max_glyph_cache_size = options.max_glyph_cache_size.unwrap_or(GlyphCache::DEFAULT_MAX_BYTES_USED);
let blob_image_handler = options.blob_image_handler.take();
let thread_listener_for_render_backend = thread_listener.clone();
@ -2161,9 +2163,12 @@ impl Renderer {
start_size,
);
let glyph_cache = GlyphCache::new(max_glyph_cache_size);
let resource_cache = ResourceCache::new(
texture_cache,
glyph_rasterizer,
glyph_cache,
blob_image_handler,
);
@ -5478,6 +5483,7 @@ pub struct RendererOptions {
pub clear_color: Option<ColorF>,
pub enable_clear_scissor: bool,
pub max_texture_size: Option<i32>,
pub max_glyph_cache_size: Option<usize>,
pub scatter_gpu_cache_updates: bool,
pub upload_method: UploadMethod,
pub workers: Option<Arc<ThreadPool>>,
@ -5532,6 +5538,7 @@ impl Default for RendererOptions {
clear_color: Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
enable_clear_scissor: true,
max_texture_size: None,
max_glyph_cache_size: None,
// Scattered GPU cache updates haven't met a test that would show their superiority yet.
scatter_gpu_cache_updates: false,
// This is best as `Immediate` on Angle, or `Pixelbuffer(Dynamic)` on GL,

Просмотреть файл

@ -33,7 +33,7 @@ use crate::render_task::{RenderTaskCache, RenderTaskCacheKey, RenderTaskId};
use crate::render_task::{RenderTaskCacheEntry, RenderTaskCacheEntryHandle, RenderTaskGraph};
use smallvec::SmallVec;
use std::collections::hash_map::Entry::{self, Occupied, Vacant};
use std::collections::hash_map::IterMut;
use std::collections::hash_map::{Iter, IterMut};
use std::collections::VecDeque;
use std::{cmp, mem};
use std::fmt::Debug;
@ -273,10 +273,18 @@ where
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();
}
@ -491,10 +499,11 @@ impl ResourceCache {
pub fn new(
texture_cache: TextureCache,
glyph_rasterizer: GlyphRasterizer,
cached_glyphs: GlyphCache,
blob_image_handler: Option<Box<dyn BlobImageHandler>>,
) -> Self {
ResourceCache {
cached_glyphs: GlyphCache::new(),
cached_glyphs,
cached_images: ResourceClassCache::new(),
cached_render_tasks: RenderTaskCache::new(),
resources: Resources::default(),
@ -781,7 +790,7 @@ impl ResourceCache {
self.glyph_rasterizer.delete_font(font_key);
self.resources.font_templates.remove(&font_key);
self.cached_glyphs
.clear_fonts(|font| font.font_key == font_key);
.clear_fonts(&mut self.texture_cache, |font| font.font_key == font_key);
if let Some(ref mut r) = self.blob_image_handler {
r.delete_font(font_key);
}
@ -1604,7 +1613,12 @@ impl ResourceCache {
debug_assert_eq!(self.state, State::Idle);
self.state = State::AddResources;
self.texture_cache.begin_frame(stamp);
self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks, &mut self.glyph_rasterizer);
self.cached_glyphs.begin_frame(
stamp,
&mut self.texture_cache,
&self.cached_render_tasks,
&mut self.glyph_rasterizer,
);
self.cached_render_tasks.begin_frame(&mut self.texture_cache);
self.current_frame_id = stamp.frame_id();
self.active_image_keys.clear();
@ -1842,7 +1856,7 @@ impl ResourceCache {
.font_templates
.retain(|key, _| key.0 != namespace);
self.cached_glyphs
.clear_fonts(|font| font.font_key.0 == namespace);
.clear_fonts(&mut self.texture_cache, |font| font.font_key.0 == namespace);
if let Some(ref mut r) = self.blob_image_handler {
r.clear_namespace(namespace);

Просмотреть файл

@ -916,6 +916,14 @@ impl TextureCache {
self.entries.get_opt(handle).is_some()
}
// Return the allocated size of the texture handle's associated data,
// or otherwise indicate the handle is invalid.
pub fn get_allocated_size(&self, handle: &TextureCacheHandle) -> Option<usize> {
self.entries.get_opt(handle).map(|entry| {
(entry.format.bytes_per_pixel() * entry.size.area()) as usize
})
}
// Retrieve the details of an item in the cache. This is used
// during batch creation to provide the resource rect address
// to the shaders and texture ID to the batching logic.