Remove FontContextHandle (#32038)

The `FontContextHandle` was really only used on FreeType platforms to
store the `FT_Library` handle to use for creating faces. Each
`FontContext` and `FontCacheThread` would create its own
`FontContextHandle`. This change removes this data structure in favor of
a mutex-protected shared `FontContextHandle` for an entire Servo
process. The handle is initialized using a `OnceLock` to ensure that it
only happens once and also that it stays alive for the entire process
lifetime.

In addition to greatly simplifying the code, this will make it possible
for different threads to share platform-specific `FontHandle`s, avoiding
multiple allocations for a single font.

The only downside to all of this is that memory usage of FreeType fonts
isn't measured (though the mechanism is still there). This is because
the `FontCacheThread` currently doesn't do any memory measurement.
Eventually this *will* happen though, during the font system redesign.
In exchange, this should reduce the memory usage since there is only a
single FreeType library loaded into memory now.

This is part of #32033.
This commit is contained in:
Martin Robinson 2024-04-12 12:39:32 +02:00 committed by GitHub
parent e9591ce62f
commit efa0d45757
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 152 additions and 247 deletions

View file

@ -10,7 +10,7 @@ use std::{mem, ptr};
use app_units::Au;
use freetype::freetype::{
FT_Done_Face, FT_F26Dot6, FT_Face, FT_FaceRec, FT_Get_Char_Index, FT_Get_Kerning,
FT_Get_Postscript_Name, FT_Get_Sfnt_Table, FT_GlyphSlot, FT_Int32, FT_Kerning_Mode, FT_Library,
FT_Get_Postscript_Name, FT_Get_Sfnt_Table, FT_GlyphSlot, FT_Int32, FT_Kerning_Mode,
FT_Load_Glyph, FT_Load_Sfnt_Table, FT_Long, FT_New_Face, FT_New_Memory_Face, FT_Set_Char_Size,
FT_Sfnt_Tag, FT_SizeRec, FT_Size_Metrics, FT_UInt, FT_ULong, FT_Vector, FT_STYLE_FLAG_ITALIC,
};
@ -22,12 +22,12 @@ use style::computed_values::font_weight::T as FontWeight;
use style::values::computed::font::FontStyle;
use super::c_str_to_string;
use super::library_handle::FreeTypeLibraryHandle;
use crate::font::{
FontHandleMethods, FontMetrics, FontTableMethods, FontTableTag, FractionalPixel, GPOS, GSUB,
KERN,
};
use crate::font_cache_thread::FontIdentifier;
use crate::platform::font_context::FontContextHandle;
use crate::platform::font_template::FontTemplateData;
use crate::text::glyph::GlyphId;
use crate::text::util::fixed_to_float;
@ -76,9 +76,6 @@ pub struct FontHandle {
// if the font is created using FT_Memory_Face.
font_data: Arc<FontTemplateData>,
face: FT_Face,
// `context_handle` is unused, but here to ensure that the underlying
// FreeTypeLibraryHandle is not dropped.
context_handle: FontContextHandle,
can_do_fast_shaping: bool,
}
@ -86,6 +83,10 @@ impl Drop for FontHandle {
fn drop(&mut self) {
assert!(!self.face.is_null());
unsafe {
// The FreeType documentation says that both `FT_New_Face` and `FT_Done_Face`
// should be protected by a mutex.
// See https://freetype.org/freetype2/docs/reference/ft2-library_setup.html.
let _guard = FreeTypeLibraryHandle::get().lock();
if !succeeded(FT_Done_Face(self.face)) {
panic!("FT_Done_Face failed");
}
@ -93,20 +94,17 @@ impl Drop for FontHandle {
}
}
fn create_face(
lib: FT_Library,
template: &FontTemplateData,
pt_size: Option<Au>,
) -> Result<FT_Face, &'static str> {
fn create_face(template: &FontTemplateData, pt_size: Option<Au>) -> Result<FT_Face, &'static str> {
unsafe {
let mut face: FT_Face = ptr::null_mut();
let face_index = 0 as FT_Long;
let library = FreeTypeLibraryHandle::get().lock();
let result = match template.identifier {
FontIdentifier::Web(_) => {
let bytes = template.bytes();
FT_New_Memory_Face(
lib,
library.freetype_library,
bytes.as_ptr(),
bytes.len() as FT_Long,
face_index,
@ -120,7 +118,12 @@ fn create_face(
// https://github.com/servo/servo/pull/20506#issuecomment-378838800
let filename =
CString::new(&*local_identifier.path).expect("filename contains NUL byte!");
FT_New_Face(lib, filename.as_ptr(), face_index, &mut face)
FT_New_Face(
library.freetype_library,
filename.as_ptr(),
face_index,
&mut face,
)
},
};
@ -138,21 +141,13 @@ fn create_face(
impl FontHandleMethods for FontHandle {
fn new_from_template(
fctx: &FontContextHandle,
template: Arc<FontTemplateData>,
pt_size: Option<Au>,
) -> Result<FontHandle, &'static str> {
let ft_ctx: FT_Library = fctx.ctx.ctx;
if ft_ctx.is_null() {
return Err("Null FT_Library");
}
let face = create_face(ft_ctx, &template, pt_size)?;
let face = create_face(&template, pt_size)?;
let mut handle = FontHandle {
face,
font_data: template,
context_handle: fctx.clone(),
can_do_fast_shaping: false,
};
// TODO (#11310): Implement basic support for GPOS and GSUB.

View file

@ -1,136 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::os::raw::{c_long, c_void};
use std::ptr;
use std::rc::Rc;
use freetype::freetype::{
FT_Add_Default_Modules, FT_Done_Library, FT_Library, FT_Memory, FT_MemoryRec_, FT_New_Library,
};
use freetype::succeeded;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use servo_allocator::libc_compat::{free, malloc, realloc};
use servo_allocator::usable_size;
// We pass a |User| struct -- via an opaque |void*| -- to FreeType each time a new instance is
// created. FreeType passes it back to the ft_alloc/ft_realloc/ft_free callbacks. We use it to
// record the memory usage of each FreeType instance.
pub struct User {
size: usize,
}
extern "C" fn ft_alloc(mem: FT_Memory, req_size: c_long) -> *mut c_void {
unsafe {
let ptr = malloc(req_size as usize);
let ptr = ptr as *mut c_void; // libc::c_void vs std::os::raw::c_void
let actual_size = usable_size(ptr);
let user = (*mem).user as *mut User;
(*user).size += actual_size;
ptr
}
}
extern "C" fn ft_free(mem: FT_Memory, ptr: *mut c_void) {
unsafe {
let actual_size = usable_size(ptr);
let user = (*mem).user as *mut User;
(*user).size -= actual_size;
free(ptr as *mut _);
}
}
extern "C" fn ft_realloc(
mem: FT_Memory,
_old_size: c_long,
new_req_size: c_long,
old_ptr: *mut c_void,
) -> *mut c_void {
unsafe {
let old_actual_size = usable_size(old_ptr);
let new_ptr = realloc(old_ptr as *mut _, new_req_size as usize);
let new_ptr = new_ptr as *mut c_void;
let new_actual_size = usable_size(new_ptr);
let user = (*mem).user as *mut User;
(*user).size += new_actual_size;
(*user).size -= old_actual_size;
new_ptr
}
}
// A |*mut User| field in a struct triggers a "use of `#[derive]` with a raw pointer" warning from
// rustc. But using a typedef avoids this, so...
pub type UserPtr = *mut User;
// WARNING: We need to be careful how we use this struct. See the comment about Rc<> in
// FontContextHandle.
#[derive(Clone, Debug)]
pub struct FreeTypeLibraryHandle {
pub ctx: FT_Library,
mem: FT_Memory,
user: UserPtr,
}
impl Drop for FreeTypeLibraryHandle {
#[allow(unused)]
fn drop(&mut self) {
assert!(!self.ctx.is_null());
unsafe {
FT_Done_Library(self.ctx);
Box::from_raw(self.mem);
Box::from_raw(self.user);
}
}
}
impl MallocSizeOf for FreeTypeLibraryHandle {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
unsafe {
(*self.user).size +
ops.malloc_size_of(self.ctx as *const _) +
ops.malloc_size_of(self.mem as *const _) +
ops.malloc_size_of(self.user as *const _)
}
}
}
#[derive(Clone, Debug)]
pub struct FontContextHandle {
// WARNING: FreeTypeLibraryHandle contains raw pointers, is clonable, and also implements
// `Drop`. This field needs to be Rc<> to make sure that the `drop` function is only called
// once, otherwise we'll get crashes. Yuk.
pub ctx: Rc<FreeTypeLibraryHandle>,
}
impl MallocSizeOf for FontContextHandle {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.ctx.size_of(ops)
}
}
impl Default for FontContextHandle {
fn default() -> Self {
let user = Box::into_raw(Box::new(User { size: 0 }));
let mem = Box::into_raw(Box::new(FT_MemoryRec_ {
user: user as *mut c_void,
alloc: Some(ft_alloc),
free: Some(ft_free),
realloc: Some(ft_realloc),
}));
unsafe {
let mut ctx: FT_Library = ptr::null_mut();
let result = FT_New_Library(mem, &mut ctx);
if !succeeded(result) {
panic!("Unable to initialize FreeType library");
}
FT_Add_Default_Modules(ctx);
FontContextHandle {
ctx: Rc::new(FreeTypeLibraryHandle { ctx, mem, user }),
}
}
}
}

View file

@ -0,0 +1,116 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::os::raw::{c_long, c_void};
use std::ptr;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::OnceLock;
use freetype::freetype::{
FT_Add_Default_Modules, FT_Done_Library, FT_Library, FT_Memory, FT_MemoryRec_, FT_New_Library,
};
use freetype::succeeded;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use parking_lot::Mutex;
use servo_allocator::libc_compat::{free, malloc, realloc};
use servo_allocator::usable_size;
static FREETYPE_MEMORY_USAGE: AtomicUsize = AtomicUsize::new(0);
static FREETYPE_LIBRARY_HANDLE: OnceLock<Mutex<FreeTypeLibraryHandle>> = OnceLock::new();
extern "C" fn ft_alloc(_: FT_Memory, req_size: c_long) -> *mut c_void {
unsafe {
let pointer = malloc(req_size as usize);
FREETYPE_MEMORY_USAGE.fetch_add(usable_size(pointer), Ordering::Relaxed);
pointer
}
}
extern "C" fn ft_free(_: FT_Memory, pointer: *mut c_void) {
unsafe {
FREETYPE_MEMORY_USAGE.fetch_sub(usable_size(pointer), Ordering::Relaxed);
free(pointer as *mut _);
}
}
extern "C" fn ft_realloc(
_: FT_Memory,
_old_size: c_long,
new_req_size: c_long,
old_pointer: *mut c_void,
) -> *mut c_void {
unsafe {
FREETYPE_MEMORY_USAGE.fetch_sub(usable_size(old_pointer), Ordering::Relaxed);
let new_pointer = realloc(old_pointer, new_req_size as usize);
FREETYPE_MEMORY_USAGE.fetch_add(usable_size(new_pointer), Ordering::Relaxed);
new_pointer
}
}
/// A FreeType library handle to be used for creating and dropping FreeType font faces.
/// It is very important that this handle lives as long as the faces themselves, which
/// is why only one of these is created for the entire execution of Servo and never
/// dropped during execution.
#[derive(Clone, Debug)]
pub(crate) struct FreeTypeLibraryHandle {
pub freetype_library: FT_Library,
freetype_memory: FT_Memory,
}
unsafe impl Sync for FreeTypeLibraryHandle {}
unsafe impl Send for FreeTypeLibraryHandle {}
impl Drop for FreeTypeLibraryHandle {
#[allow(unused)]
fn drop(&mut self) {
assert!(!self.freetype_library.is_null());
unsafe {
FT_Done_Library(self.freetype_library);
Box::from_raw(self.freetype_memory);
}
}
}
impl MallocSizeOf for FreeTypeLibraryHandle {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
unsafe {
FREETYPE_MEMORY_USAGE.load(Ordering::Relaxed) +
ops.malloc_size_of(self.freetype_library as *const _) +
ops.malloc_size_of(self.freetype_memory as *const _)
}
}
}
impl FreeTypeLibraryHandle {
/// Get the shared FreeType library handle. This is protected by a mutex because according to
/// the FreeType documentation:
///
/// > [Since 2.5.6] In multi-threaded applications it is easiest to use one FT_Library object per
/// > thread. In case this is too cumbersome, a single FT_Library object across threads is possible
/// > also, as long as a mutex lock is used around FT_New_Face and FT_Done_Face.
///
/// See <https://freetype.org/freetype2/docs/reference/ft2-library_setup.html>.
pub(crate) fn get() -> &'static Mutex<FreeTypeLibraryHandle> {
FREETYPE_LIBRARY_HANDLE.get_or_init(|| {
let freetype_memory = Box::into_raw(Box::new(FT_MemoryRec_ {
user: ptr::null_mut(),
alloc: Some(ft_alloc),
free: Some(ft_free),
realloc: Some(ft_realloc),
}));
unsafe {
let mut freetype_library: FT_Library = ptr::null_mut();
let result = FT_New_Library(freetype_memory, &mut freetype_library);
if !succeeded(result) {
panic!("Unable to initialize FreeType library");
}
FT_Add_Default_Modules(freetype_library);
Mutex::new(FreeTypeLibraryHandle {
freetype_library,
freetype_memory,
})
}
})
}
}

View file

@ -27,7 +27,6 @@ use crate::font::{
};
use crate::font_cache_thread::FontIdentifier;
use crate::platform::font_template::FontTemplateData;
use crate::platform::macos::font_context::FontContextHandle;
use crate::text::glyph::GlyphId;
const KERN_PAIR_LEN: usize = 6;
@ -158,7 +157,6 @@ impl fmt::Debug for CachedKernTable {
impl FontHandleMethods for FontHandle {
fn new_from_template(
_fctx: &FontContextHandle,
template: Arc<FontTemplateData>,
pt_size: Option<Au>,
) -> Result<FontHandle, &'static str> {

View file

@ -1,17 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
#[derive(Clone, Debug, Default)]
pub struct FontContextHandle {
// this is a placeholder until NSFontManager or whatever is bound in here.
_ctx: (),
}
impl MallocSizeOf for FontContextHandle {
fn size_of(&self, _: &mut MallocSizeOfOps) -> usize {
0
}
}

View file

@ -3,13 +3,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use crate::platform::freetype::{font, font_context};
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use crate::platform::freetype::{font_list, font_template};
pub use crate::platform::freetype::{font, font_list, font_template, library_handle};
#[cfg(target_os = "macos")]
pub use crate::platform::macos::{font, font_context, font_list, font_template};
pub use crate::platform::macos::{font, font_list, font_template};
#[cfg(target_os = "windows")]
pub use crate::platform::windows::{font, font_context, font_list, font_template};
pub use crate::platform::windows::{font, font_list, font_template};
#[cfg(any(target_os = "linux", target_os = "android"))]
mod freetype {
@ -27,7 +25,6 @@ mod freetype {
}
pub mod font;
pub mod font_context;
#[cfg(target_os = "linux")]
pub mod font_list;
@ -39,14 +36,13 @@ mod freetype {
#[cfg(target_os = "android")]
pub use self::android::font_list;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub mod font_template;
pub mod library_handle;
}
#[cfg(target_os = "macos")]
mod macos {
pub mod font;
pub mod font_context;
pub mod font_list;
pub mod font_template;
}
@ -54,7 +50,6 @@ mod macos {
#[cfg(target_os = "windows")]
mod windows {
pub mod font;
pub mod font_context;
pub mod font_list;
pub mod font_template;
}

View file

@ -23,7 +23,6 @@ use crate::font::{
};
use crate::font_cache_thread::FontIdentifier;
use crate::platform::font_template::FontTemplateData;
use crate::platform::windows::font_context::FontContextHandle;
use crate::text::glyph::GlyphId;
// 1em = 12pt = 16px, assuming 72 points per inch and 96 px per inch
@ -228,7 +227,6 @@ impl FontHandle {}
impl FontHandleMethods for FontHandle {
fn new_from_template(
_: &FontContextHandle,
template: Arc<FontTemplateData>,
pt_size: Option<Au>,
) -> Result<Self, &'static str> {

View file

@ -1,10 +0,0 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use malloc_size_of::malloc_size_of_is_0;
#[derive(Clone, Debug, Default)]
pub struct FontContextHandle;
malloc_size_of_is_0!(FontContextHandle);