fonts: Clean up WebRender web fonts when they are no longer used (#32545)

This is the first part of cleaning up unused WebRender resources.
Currently this only cleans up web font resources, but a more
full-featured implementation in the future could also clean up unused
system fonts.

Fixes #32345.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
Martin Robinson 2024-06-18 16:02:27 +02:00 committed by GitHub
parent bd15a4fbd8
commit fef1337da0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 181 additions and 7 deletions

View file

@ -845,6 +845,23 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
.send_transaction(self.webrender_document, txn);
},
ForwardedToCompositorMsg::Layout(ScriptToCompositorMsg::RemoveFonts(
keys,
instance_keys,
)) => {
let mut transaction = Transaction::new();
for instance in instance_keys.into_iter() {
transaction.delete_font_instance(instance);
}
for key in keys.into_iter() {
transaction.delete_font(key);
}
self.webrender_api
.send_transaction(self.webrender_document, transaction);
},
ForwardedToCompositorMsg::Net(NetToCompositorMsg::AddImage(key, desc, data)) => {
let mut txn = Transaction::new();
txn.add_image(key, desc, data, None);

View file

@ -2,9 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::default::Default;
use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use app_units::Au;
@ -26,6 +27,7 @@ use style::shared_lock::SharedRwLockReadGuard;
use style::stylesheets::{DocumentStyleSheet, StylesheetInDocument};
use style::Atom;
use url::Url;
use webrender_api::{FontInstanceKey, FontKey};
use crate::font::{
Font, FontDescriptor, FontFamilyDescriptor, FontFamilyName, FontGroup, FontRef, FontSearchScope,
@ -48,6 +50,7 @@ pub struct FontContext<S: FontSource> {
cache: CachingFontSource<S>,
web_fonts: CrossThreadFontStore,
webrender_font_store: CrossThreadWebRenderFontStore,
have_removed_web_fonts: AtomicBool,
}
impl<S: FontSource> MallocSizeOf for FontContext<S> {
@ -65,6 +68,7 @@ impl<S: FontSource> FontContext<S> {
cache: CachingFontSource::new(font_source),
web_fonts: Arc::new(RwLock::default()),
webrender_font_store: Arc::new(RwLock::default()),
have_removed_web_fonts: AtomicBool::new(false),
}
}
@ -244,6 +248,8 @@ pub trait FontContextWebFontMethods {
) -> usize;
fn process_next_web_font_source(&self, web_font_download_state: WebFontDownloadState);
fn remove_all_web_fonts_from_stylesheet(&self, stylesheet: &DocumentStyleSheet);
fn collect_unused_webrender_resources(&self, all: bool)
-> (Vec<FontKey>, Vec<FontInstanceKey>);
}
impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontContext<S>> {
@ -399,6 +405,44 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte
// Removing this stylesheet modified the available fonts, so invalidate the cache
// of resolved font groups.
font_groups.clear();
// Ensure that we clean up any WebRender resources on the next display list update.
self.have_removed_web_fonts.store(true, Ordering::Relaxed);
}
fn collect_unused_webrender_resources(
&self,
all: bool,
) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
if all {
let mut webrender_font_store = self.webrender_font_store.write();
self.have_removed_web_fonts.store(false, Ordering::Relaxed);
return webrender_font_store.remove_all_fonts();
}
if !self.have_removed_web_fonts.load(Ordering::Relaxed) {
return (Vec::new(), Vec::new());
}
// Lock everything to prevent adding new fonts while we are cleaning up the old ones.
let web_fonts = self.web_fonts.write();
let _fonts = self.cache.fonts.write();
let _font_groups = self.cache.resolved_font_groups.write();
let mut webrender_font_store = self.webrender_font_store.write();
let mut unused_identifiers: HashSet<FontIdentifier> = webrender_font_store
.webrender_font_key_map
.keys()
.cloned()
.collect();
for templates in web_fonts.families.values() {
templates.for_all_identifiers(|identifier| {
unused_identifiers.remove(identifier);
});
}
self.have_removed_web_fonts.store(false, Ordering::Relaxed);
webrender_font_store.remove_all_fonts_for_identifiers(unused_identifiers)
}
}

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use app_units::Au;
@ -132,6 +132,50 @@ impl WebRenderFontStore {
font_cache_thread.get_web_font_instance(font_key, pt_size.to_f32_px(), flags)
})
}
pub(crate) fn remove_all_fonts(&mut self) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
(
self.webrender_font_key_map
.drain()
.map(|(_, key)| key)
.collect(),
self.webrender_font_instance_map
.drain()
.map(|(_, key)| key)
.collect(),
)
}
pub(crate) fn remove_all_fonts_for_identifiers(
&mut self,
identifiers: HashSet<FontIdentifier>,
) -> (Vec<FontKey>, Vec<FontInstanceKey>) {
let mut removed_keys: HashSet<FontKey> = HashSet::new();
self.webrender_font_key_map.retain(|identifier, font_key| {
if identifiers.contains(identifier) {
removed_keys.insert(*font_key);
false
} else {
true
}
});
let mut removed_instance_keys: HashSet<FontInstanceKey> = HashSet::new();
self.webrender_font_instance_map
.retain(|(font_key, _), instance_key| {
if removed_keys.contains(font_key) {
removed_instance_keys.insert(*instance_key);
false
} else {
true
}
});
(
removed_keys.into_iter().collect(),
removed_instance_keys.into_iter().collect(),
)
}
}
/// A struct that represents the available templates in a "simple family." A simple family
@ -182,6 +226,18 @@ impl SimpleFamily {
remove_if_template_matches(&mut self.italic);
remove_if_template_matches(&mut self.bold_italic);
}
pub(crate) fn for_all_identifiers(&self, mut callback: impl FnMut(&FontIdentifier)) {
let mut call_if_not_none = |template: &Option<FontTemplateRef>| {
if let Some(template) = template {
callback(&template.identifier())
}
};
call_if_not_none(&self.regular);
call_if_not_none(&self.bold);
call_if_not_none(&self.italic);
call_if_not_none(&self.bold_italic);
}
}
/// A list of font templates that make up a given font family.
#[derive(Clone, Debug)]
@ -328,4 +384,13 @@ impl FontTemplates {
length_before != self.templates.len()
}
pub(crate) fn for_all_identifiers(&self, mut callback: impl FnMut(&FontIdentifier)) {
for template in self.templates.iter() {
callback(&template.borrow().identifier);
}
if let Some(ref simple_family) = self.simple_family {
simple_family.for_all_identifiers(callback)
}
}
}

View file

@ -242,6 +242,16 @@ impl Drop for ScriptReflowResult {
}
}
impl Drop for LayoutThread {
fn drop(&mut self) {
let (keys, instance_keys) = self
.font_context
.collect_unused_webrender_resources(true /* all */);
self.webrender_api
.remove_unused_font_resources(keys, instance_keys)
}
}
impl Layout for LayoutThread {
fn device(&self) -> &Device {
self.stylist.device()
@ -921,6 +931,12 @@ impl LayoutThread {
self.webrender_api
.send_display_list(compositor_info, builder.end().1);
let (keys, instance_keys) = self
.font_context
.collect_unused_webrender_resources(false /* all */);
self.webrender_api
.remove_unused_font_resources(keys, instance_keys)
},
);
}

View file

@ -219,6 +219,16 @@ impl Drop for ScriptReflowResult {
}
}
impl Drop for LayoutThread {
fn drop(&mut self) {
let (keys, instance_keys) = self
.font_context
.collect_unused_webrender_resources(true /* all */);
self.webrender_api
.remove_unused_font_resources(keys, instance_keys)
}
}
impl Layout for LayoutThread {
fn device(&self) -> &Device {
self.stylist.device()
@ -912,6 +922,12 @@ impl LayoutThread {
if reflow_goal.needs_display() {
self.webrender_api
.send_display_list(display_list.compositor_info, display_list.wr.end().1);
let (keys, instance_keys) = self
.font_context
.collect_unused_webrender_resources(false /* all */);
self.webrender_api
.remove_unused_font_resources(keys, instance_keys)
}
self.update_iframe_sizes(iframe_sizes);

View file

@ -191,16 +191,18 @@ pub trait WebRenderFontApi {
flags: FontInstanceFlags,
) -> FontInstanceKey;
fn add_font(&self, data: Arc<Vec<u8>>, index: u32) -> FontKey;
/// Forward an already prepared `AddFont` message, sending it on to the compositor. This is used
/// to get WebRender [`FontKey`]s for web fonts in the per-layout `FontContext`.
fn add_system_font(&self, handle: NativeFontHandle) -> FontKey;
/// Forward a `AddFont` message, sending it on to the compositor. This is used to get WebRender
/// [`FontKey`]s for web fonts in the per-layout `FontContext`.
fn forward_add_font_message(
&self,
bytes_receiver: IpcBytesReceiver,
font_index: u32,
result_sender: IpcSender<FontKey>,
);
/// Forward an already prepared `AddFontInstance` message, sending it on to the compositor. This
/// is used to get WebRender [`FontInstanceKey`]s for web fonts in the per-layout `FontContext`.
/// Forward a `AddFontInstance` message, sending it on to the compositor. This is used to get
/// WebRender [`FontInstanceKey`]s for web fonts in the per-layout `FontContext`.
fn forward_add_font_instance_message(
&self,
font_key: FontKey,
@ -208,7 +210,6 @@ pub trait WebRenderFontApi {
flags: FontInstanceFlags,
result_receiver: IpcSender<FontInstanceKey>,
);
fn add_system_font(&self, handle: NativeFontHandle) -> FontKey;
}
pub enum CanvasToCompositorMsg {
@ -257,6 +258,8 @@ pub enum ScriptToCompositorMsg {
GenerateImageKey(IpcSender<ImageKey>),
/// Perform a resource update operation.
UpdateImages(Vec<SerializedImageUpdate>),
/// Remove the given font resources from our WebRender instance.
RemoveFonts(Vec<FontKey>, Vec<FontInstanceKey>),
}
/// A mechanism to send messages from networking to the WebRender instance.
@ -420,6 +423,19 @@ impl WebRenderScriptApi {
}
});
}
pub fn remove_unused_font_resources(
&self,
keys: Vec<FontKey>,
instance_keys: Vec<FontInstanceKey>,
) {
if keys.is_empty() && instance_keys.is_empty() {
return;
}
let _ = self
.0
.send(ScriptToCompositorMsg::RemoveFonts(keys, instance_keys));
}
}
#[derive(Deserialize, Serialize)]