Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
use crate::glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
6
use crate::internal_types::FastHashMap;
7
use crate::render_backend::{FrameId, FrameStamp};
8
use crate::render_task_cache::RenderTaskCache;
9
use crate::resource_cache::ResourceClassCache;
10
use std::sync::Arc;
11
use crate::texture_cache::{EvictionNotice, TextureCache};
12
use crate::texture_cache::TextureCacheHandle;
13
14
#[cfg_attr(feature = "capture", derive(Serialize))]
15
#[cfg_attr(feature = "replay", derive(Deserialize))]
16
#[derive(Clone, Debug)]
17
pub struct CachedGlyphInfo {
18
pub format: GlyphFormat,
19
pub texture_cache_handle: TextureCacheHandle,
20
}
21
22
#[cfg_attr(feature = "capture", derive(Serialize))]
23
#[cfg_attr(feature = "replay", derive(Deserialize))]
24
pub enum GlyphCacheEntry {
25
// A glyph that has been successfully rasterized.
26
Cached(CachedGlyphInfo),
27
// A glyph that should not be rasterized (i.e. a space).
28
Blank,
29
// A glyph that has been submitted to the font backend for rasterization,
30
// but is still pending a result.
31
#[allow(dead_code)]
32
Pending,
33
}
34
35
impl GlyphCacheEntry {
36
fn get_allocated_size(&self, texture_cache: &TextureCache, _: &RenderTaskCache)
37
-> Option<usize> {
38
match *self {
39
GlyphCacheEntry::Cached(ref glyph) => {
40
texture_cache.get_allocated_size(&glyph.texture_cache_handle)
41
}
42
GlyphCacheEntry::Pending | GlyphCacheEntry::Blank => Some(0),
43
}
44
}
45
46
fn mark_unused(&self, texture_cache: &mut TextureCache) {
47
if let GlyphCacheEntry::Cached(ref glyph) = *self {
48
texture_cache.mark_unused(&glyph.texture_cache_handle);
49
}
50
}
51
52
fn is_recently_used(&self, texture_cache: &mut TextureCache) -> bool {
53
if let GlyphCacheEntry::Cached(ref glyph) = *self {
54
texture_cache.is_recently_used(&glyph.texture_cache_handle, 1)
55
} else {
56
false
57
}
58
}
59
}
60
61
#[allow(dead_code)]
62
#[cfg_attr(feature = "capture", derive(Serialize))]
63
#[cfg_attr(feature = "replay", derive(Deserialize))]
64
#[derive(Clone)]
65
pub enum CachedGlyphData {
66
Memory(Arc<Vec<u8>>),
67
Gpu,
68
}
69
70
#[cfg_attr(feature = "capture", derive(Serialize))]
71
#[cfg_attr(feature = "replay", derive(Deserialize))]
72
#[derive(Default)]
73
pub struct GlyphKeyCacheInfo {
74
eviction_notice: EvictionNotice,
75
last_frame_used: FrameId,
76
bytes_used: usize,
77
}
78
79
pub type GlyphKeyCache = ResourceClassCache<GlyphKey, GlyphCacheEntry, GlyphKeyCacheInfo>;
80
81
impl GlyphKeyCache {
82
const DIRTY: usize = !0;
83
84
pub fn eviction_notice(&self) -> &EvictionNotice {
85
&self.user_data.eviction_notice
86
}
87
88
fn is_recently_used(&self, current_frame: FrameId) -> bool {
89
self.user_data.last_frame_used + 1 >= current_frame
90
}
91
92
fn clear_glyphs(&mut self, texture_cache: &mut TextureCache) -> usize {
93
let pruned = self.user_data.bytes_used;
94
for (_, entry) in self.iter() {
95
entry.mark_unused(texture_cache);
96
}
97
self.clear();
98
self.user_data.bytes_used = 0;
99
pruned
100
}
101
102
fn prune_glyphs(
103
&mut self,
104
skip_recent: bool,
105
excess_bytes_used: usize,
106
texture_cache: &mut TextureCache,
107
render_task_cache: &RenderTaskCache,
108
) -> usize {
109
let mut pruned = 0;
110
self.retain(|_, entry| {
111
if pruned <= excess_bytes_used &&
112
(!skip_recent || !entry.is_recently_used(texture_cache)) {
113
match entry.get_allocated_size(texture_cache, render_task_cache) {
114
Some(size) => {
115
pruned += size;
116
entry.mark_unused(texture_cache);
117
false
118
}
119
None => true,
120
}
121
} else {
122
true
123
}
124
});
125
self.user_data.bytes_used -= pruned;
126
pruned
127
}
128
129
pub fn add_glyph(&mut self, key: GlyphKey, value: GlyphCacheEntry) {
130
self.insert(key, value);
131
self.user_data.bytes_used = Self::DIRTY;
132
}
133
134
fn clear_evicted(
135
&mut self,
136
texture_cache: &TextureCache,
137
render_task_cache: &RenderTaskCache,
138
) {
139
if self.eviction_notice().check() || self.user_data.bytes_used == Self::DIRTY {
140
// If there are evictions, filter out any glyphs evicted from the
141
// texture cache from the glyph key cache.
142
let mut usage = 0;
143
self.retain(|_, entry| {
144
let size = entry.get_allocated_size(texture_cache, render_task_cache);
145
usage += size.unwrap_or(0);
146
size.is_some()
147
});
148
self.user_data.bytes_used = usage;
149
}
150
}
151
}
152
153
#[cfg_attr(feature = "capture", derive(Serialize))]
154
#[cfg_attr(feature = "replay", derive(Deserialize))]
155
pub struct GlyphCache {
156
glyph_key_caches: FastHashMap<FontInstance, GlyphKeyCache>,
157
current_frame: FrameId,
158
bytes_used: usize,
159
max_bytes_used: usize,
160
}
161
162
impl GlyphCache {
163
/// The default space usage threshold, in bytes, after which to start pruning away old fonts.
164
pub const DEFAULT_MAX_BYTES_USED: usize = 6 * 1024 * 1024;
165
166
pub fn new(max_bytes_used: usize) -> Self {
167
GlyphCache {
168
glyph_key_caches: FastHashMap::default(),
169
current_frame: Default::default(),
170
bytes_used: 0,
171
max_bytes_used,
172
}
173
}
174
175
pub fn get_glyph_key_cache_for_font_mut(&mut self, font: FontInstance) -> &mut GlyphKeyCache {
176
let cache = self.glyph_key_caches
177
.entry(font)
178
.or_insert_with(GlyphKeyCache::new);
179
cache.user_data.last_frame_used = self.current_frame;
180
cache
181
}
182
183
pub fn get_glyph_key_cache_for_font(&self, font: &FontInstance) -> &GlyphKeyCache {
184
self.glyph_key_caches
185
.get(font)
186
.expect("BUG: Unable to find glyph key cache!")
187
}
188
189
pub fn clear(&mut self) {
190
for (_, glyph_key_cache) in &mut self.glyph_key_caches {
191
glyph_key_cache.clear()
192
}
193
// We use this in on_memory_pressure where retaining memory allocations
194
// isn't desirable, so we completely remove the hash map instead of clearing it.
195
self.glyph_key_caches = FastHashMap::default();
196
}
197
198
pub fn clear_fonts<F>(&mut self, texture_cache: &mut TextureCache, key_fun: F)
199
where
200
for<'r> F: Fn(&'r &FontInstance) -> bool,
201
{
202
self.glyph_key_caches.retain(|k, cache| {
203
let should_clear = key_fun(&k);
204
if !should_clear {
205
return true;
206
}
207
208
cache.clear_glyphs(texture_cache);
209
false
210
})
211
}
212
213
/// Clear out evicted entries from glyph key caches.
214
fn clear_evicted(
215
&mut self,
216
texture_cache: &TextureCache,
217
render_task_cache: &RenderTaskCache,
218
) {
219
let mut usage = 0;
220
for cache in self.glyph_key_caches.values_mut() {
221
// Scan for any glyph key caches that have evictions.
222
cache.clear_evicted(texture_cache, render_task_cache);
223
usage += cache.user_data.bytes_used;
224
}
225
self.bytes_used = usage;
226
}
227
228
/// If possible, remove entirely any empty glyph key caches.
229
fn clear_empty_caches(&mut self, glyph_rasterizer: &mut GlyphRasterizer) {
230
self.glyph_key_caches.retain(|key, cache| {
231
// Discard the glyph key cache if it has no valid glyphs.
232
if cache.is_empty() {
233
glyph_rasterizer.delete_font_instance(key);
234
false
235
} else {
236
true
237
}
238
});
239
}
240
241
/// Check the total space usage of the glyph cache. If it exceeds the maximum usage threshold,
242
/// then start clearing the oldest glyphs until below the threshold.
243
fn prune_excess_usage(
244
&mut self,
245
texture_cache: &mut TextureCache,
246
render_task_cache: &RenderTaskCache,
247
) {
248
if self.bytes_used < self.max_bytes_used {
249
return;
250
}
251
// Usage is above the threshold. Get a last-recently-used ordered list of caches to clear.
252
let mut caches: Vec<_> = self.glyph_key_caches.values_mut().collect();
253
caches.sort_unstable_by(|a, b| {
254
a.user_data.last_frame_used.cmp(&b.user_data.last_frame_used)
255
});
256
// Clear out the oldest caches until below the threshold.
257
for cache in caches {
258
if self.bytes_used < self.max_bytes_used {
259
break;
260
}
261
let recent = cache.is_recently_used(self.current_frame);
262
let excess = self.bytes_used - self.max_bytes_used;
263
if !recent && excess >= cache.user_data.bytes_used {
264
// If the excess is greater than the cache's size, just clear the whole thing.
265
self.bytes_used -= cache.clear_glyphs(texture_cache);
266
} else {
267
// Otherwise, just clear as little of the cache as needed to remove the excess
268
// and avoid rematerialization costs.
269
self.bytes_used -= cache.prune_glyphs(
270
recent,
271
excess,
272
texture_cache,
273
render_task_cache,
274
);
275
}
276
}
277
}
278
279
pub fn begin_frame(
280
&mut self,
281
stamp: FrameStamp,
282
texture_cache: &mut TextureCache,
283
render_task_cache: &RenderTaskCache,
284
glyph_rasterizer: &mut GlyphRasterizer,
285
) {
286
self.current_frame = stamp.frame_id();
287
self.clear_evicted(texture_cache, render_task_cache);
288
self.prune_excess_usage(texture_cache, render_task_cache);
289
// Clearing evicted glyphs and pruning excess usage might have produced empty caches,
290
// so get rid of them if possible.
291
self.clear_empty_caches(glyph_rasterizer);
292
}
293
}