Skip to content

Commit

Permalink
fonts: Clean up WebRender web fonts when they are no longer used (#32545
Browse files Browse the repository at this point in the history
)

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>
  • Loading branch information
mrobinson and mukilan committed Jun 18, 2024
1 parent bd15a4f commit fef1337
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 7 deletions.
17 changes: 17 additions & 0 deletions components/compositing/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
46 changes: 45 additions & 1 deletion components/gfx/font_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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> {
Expand All @@ -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),
}
}

Expand Down Expand Up @@ -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>> {
Expand Down Expand Up @@ -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)
}
}

Expand Down
67 changes: 66 additions & 1 deletion components/gfx/font_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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)
}
}
}
16 changes: 16 additions & 0 deletions components/layout_thread/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
},
);
}
Expand Down
16 changes: 16 additions & 0 deletions components/layout_thread_2020/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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);
Expand Down
26 changes: 21 additions & 5 deletions components/shared/webrender/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,24 +191,25 @@ 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,
size: f32,
flags: FontInstanceFlags,
result_receiver: IpcSender<FontInstanceKey>,
);
fn add_system_font(&self, handle: NativeFontHandle) -> FontKey;
}

pub enum CanvasToCompositorMsg {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)]
Expand Down

0 comments on commit fef1337

Please sign in to comment.