Wrap Fonts in a Mutex
This commit is contained in:
parent
d70c80569f
commit
b0196527e5
7 changed files with 135 additions and 76 deletions
|
@ -93,7 +93,7 @@ impl ContextImpl {
|
||||||
new_font_definitions.unwrap_or_else(|| {
|
new_font_definitions.unwrap_or_else(|| {
|
||||||
self.fonts
|
self.fonts
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|font| font.definitions().clone())
|
.map(|font| font.lock().definitions().clone())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
|
@ -521,7 +521,7 @@ impl Context {
|
||||||
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
||||||
if let Some(current_fonts) = &*self.fonts_mut() {
|
if let Some(current_fonts) = &*self.fonts_mut() {
|
||||||
// NOTE: this comparison is expensive since it checks TTF data for equality
|
// NOTE: this comparison is expensive since it checks TTF data for equality
|
||||||
if current_fonts.definitions() == &font_definitions {
|
if current_fonts.lock().definitions() == &font_definitions {
|
||||||
return; // no change - save us from reloading font textures
|
return; // no change - save us from reloading font textures
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -700,7 +700,7 @@ impl Context {
|
||||||
self.request_repaint();
|
self.request_repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fonts().end_frame();
|
self.fonts().lock().end_frame();
|
||||||
|
|
||||||
{
|
{
|
||||||
let ctx_impl = &mut *self.write();
|
let ctx_impl = &mut *self.write();
|
||||||
|
@ -708,7 +708,7 @@ impl Context {
|
||||||
.memory
|
.memory
|
||||||
.end_frame(&ctx_impl.input, &ctx_impl.frame_state.used_ids);
|
.end_frame(&ctx_impl.input, &ctx_impl.frame_state.used_ids);
|
||||||
|
|
||||||
let font_image_delta = ctx_impl.fonts.as_ref().unwrap().font_image_delta();
|
let font_image_delta = ctx_impl.fonts.as_ref().unwrap().lock().font_image_delta();
|
||||||
if let Some(font_image_delta) = font_image_delta {
|
if let Some(font_image_delta) = font_image_delta {
|
||||||
ctx_impl
|
ctx_impl
|
||||||
.tex_manager
|
.tex_manager
|
||||||
|
@ -754,7 +754,7 @@ impl Context {
|
||||||
let clipped_meshes = tessellator::tessellate_shapes(
|
let clipped_meshes = tessellator::tessellate_shapes(
|
||||||
shapes,
|
shapes,
|
||||||
tessellation_options,
|
tessellation_options,
|
||||||
self.fonts().font_image_size(),
|
self.fonts().lock().font_image_size(),
|
||||||
);
|
);
|
||||||
self.write().paint_stats = paint_stats.with_clipped_meshes(&clipped_meshes);
|
self.write().paint_stats = paint_stats.with_clipped_meshes(&clipped_meshes);
|
||||||
clipped_meshes
|
clipped_meshes
|
||||||
|
@ -956,9 +956,9 @@ impl Context {
|
||||||
CollapsingHeader::new("🔠 Fonts")
|
CollapsingHeader::new("🔠 Fonts")
|
||||||
.default_open(false)
|
.default_open(false)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
let mut font_definitions = self.fonts().definitions().clone();
|
let mut font_definitions = self.fonts().lock().definitions().clone();
|
||||||
font_definitions.ui(ui);
|
font_definitions.ui(ui);
|
||||||
let font_image_size = self.fonts().font_image_size();
|
let font_image_size = self.fonts().lock().font_image_size();
|
||||||
crate::introspection::font_texture_ui(ui, font_image_size);
|
crate::introspection::font_texture_ui(ui, font_image_size);
|
||||||
self.set_fonts(font_definitions);
|
self.set_fonts(font_definitions);
|
||||||
});
|
});
|
||||||
|
@ -1015,7 +1015,7 @@ impl Context {
|
||||||
|
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"There are {} text galleys in the layout cache",
|
"There are {} text galleys in the layout cache",
|
||||||
self.fonts().num_galleys_in_cache()
|
self.fonts().lock().num_galleys_in_cache()
|
||||||
))
|
))
|
||||||
.on_hover_text("This is approximately the number of text strings on screen");
|
.on_hover_text("This is approximately the number of text strings on screen");
|
||||||
ui.add_space(16.0);
|
ui.add_space(16.0);
|
||||||
|
|
|
@ -91,19 +91,22 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
let color = egui::Color32::WHITE;
|
let color = egui::Color32::WHITE;
|
||||||
let fonts =
|
let fonts =
|
||||||
egui::epaint::text::Fonts::new(pixels_per_point, egui::FontDefinitions::default());
|
egui::epaint::text::Fonts::new(pixels_per_point, egui::FontDefinitions::default());
|
||||||
c.bench_function("text_layout_uncached", |b| {
|
{
|
||||||
b.iter(|| {
|
let mut fonts_impl = fonts.lock();
|
||||||
use egui::epaint::text::{layout, LayoutJob};
|
c.bench_function("text_layout_uncached", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
use egui::epaint::text::{layout, LayoutJob};
|
||||||
|
|
||||||
let job = LayoutJob::simple(
|
let job = LayoutJob::simple(
|
||||||
LOREM_IPSUM_LONG.to_owned(),
|
LOREM_IPSUM_LONG.to_owned(),
|
||||||
egui::TextStyle::Body,
|
egui::TextStyle::Body,
|
||||||
color,
|
color,
|
||||||
wrap_width,
|
wrap_width,
|
||||||
);
|
);
|
||||||
layout(&fonts, job.into())
|
layout(&mut fonts_impl, job.into())
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
}
|
||||||
c.bench_function("text_layout_cached", |b| {
|
c.bench_function("text_layout_cached", |b| {
|
||||||
b.iter(|| fonts.layout(LOREM_IPSUM_LONG.to_owned(), text_style, color, wrap_width))
|
b.iter(|| fonts.layout(LOREM_IPSUM_LONG.to_owned(), text_style, color, wrap_width))
|
||||||
});
|
});
|
||||||
|
@ -112,9 +115,10 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
let mut tessellator = egui::epaint::Tessellator::from_options(Default::default());
|
let mut tessellator = egui::epaint::Tessellator::from_options(Default::default());
|
||||||
let mut mesh = egui::epaint::Mesh::default();
|
let mut mesh = egui::epaint::Mesh::default();
|
||||||
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
||||||
|
let font_image_size = fonts.lock().font_image_size();
|
||||||
c.bench_function("tessellate_text", |b| {
|
c.bench_function("tessellate_text", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
tessellator.tessellate_text(fonts.font_image_size(), text_shape.clone(), &mut mesh);
|
tessellator.tessellate_text(font_image_size, text_shape.clone(), &mut mesh);
|
||||||
mesh.clear();
|
mesh.clear();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -72,6 +72,7 @@ impl super::View for FontBook {
|
||||||
let filter = &self.filter;
|
let filter = &self.filter;
|
||||||
let named_chars = self.named_chars.entry(text_style).or_insert_with(|| {
|
let named_chars = self.named_chars.entry(text_style).or_insert_with(|| {
|
||||||
ui.fonts()
|
ui.fonts()
|
||||||
|
.lock()
|
||||||
.font(text_style)
|
.font(text_style)
|
||||||
.characters()
|
.characters()
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -230,7 +230,106 @@ impl Default for FontDefinitions {
|
||||||
/// The collection of fonts used by `epaint`.
|
/// The collection of fonts used by `epaint`.
|
||||||
///
|
///
|
||||||
/// Required in order to paint text.
|
/// Required in order to paint text.
|
||||||
pub struct Fonts {
|
/// Create one and reuse. Cheap to clone.
|
||||||
|
///
|
||||||
|
/// Wrapper for `Arc<Mutex<FontsImpl>>`.
|
||||||
|
pub struct Fonts(Arc<Mutex<FontsImpl>>);
|
||||||
|
|
||||||
|
impl Fonts {
|
||||||
|
/// Create a new [`Fonts`] for text layout.
|
||||||
|
/// This call is expensive, so only create one [`Fonts`] and then reuse it.
|
||||||
|
pub fn new(pixels_per_point: f32, definitions: FontDefinitions) -> Self {
|
||||||
|
Self(Arc::new(Mutex::new(FontsImpl::new(
|
||||||
|
pixels_per_point,
|
||||||
|
definitions,
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the underlying [`FontsImpl`].
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[inline]
|
||||||
|
pub fn lock(&self) -> crate::mutex::MutexGuard<'_, FontsImpl> {
|
||||||
|
self.0.lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn pixels_per_point(&self) -> f32 {
|
||||||
|
self.lock().pixels_per_point
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Width of this character in points.
|
||||||
|
#[inline]
|
||||||
|
pub fn glyph_width(&self, text_style: TextStyle, c: char) -> f32 {
|
||||||
|
self.lock().glyph_width(text_style, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Height of one row of text. In points
|
||||||
|
#[inline]
|
||||||
|
pub fn row_height(&self, text_style: TextStyle) -> f32 {
|
||||||
|
self.lock().row_height(text_style)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout some text.
|
||||||
|
/// This is the most advanced layout function.
|
||||||
|
/// See also [`Self::layout`], [`Self::layout_no_wrap`] and
|
||||||
|
/// [`Self::layout_delayed_color`].
|
||||||
|
///
|
||||||
|
/// The implementation uses memoization so repeated calls are cheap.
|
||||||
|
/// #[inline]
|
||||||
|
pub fn layout_job(&self, job: LayoutJob) -> Arc<Galley> {
|
||||||
|
self.lock().layout_job(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Will wrap text at the given width and line break at `\n`.
|
||||||
|
///
|
||||||
|
/// The implementation uses memoization so repeated calls are cheap.
|
||||||
|
pub fn layout(
|
||||||
|
&self,
|
||||||
|
text: String,
|
||||||
|
text_style: TextStyle,
|
||||||
|
color: crate::Color32,
|
||||||
|
wrap_width: f32,
|
||||||
|
) -> Arc<Galley> {
|
||||||
|
let job = LayoutJob::simple(text, text_style, color, wrap_width);
|
||||||
|
self.layout_job(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Will line break at `\n`.
|
||||||
|
///
|
||||||
|
/// The implementation uses memoization so repeated calls are cheap.
|
||||||
|
pub fn layout_no_wrap(
|
||||||
|
&self,
|
||||||
|
text: String,
|
||||||
|
text_style: TextStyle,
|
||||||
|
color: crate::Color32,
|
||||||
|
) -> Arc<Galley> {
|
||||||
|
let job = LayoutJob::simple(text, text_style, color, f32::INFINITY);
|
||||||
|
self.layout_job(job)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like [`Self::layout`], made for when you want to pick a color for the text later.
|
||||||
|
///
|
||||||
|
/// The implementation uses memoization so repeated calls are cheap.
|
||||||
|
pub fn layout_delayed_color(
|
||||||
|
&self,
|
||||||
|
text: String,
|
||||||
|
text_style: TextStyle,
|
||||||
|
wrap_width: f32,
|
||||||
|
) -> Arc<Galley> {
|
||||||
|
self.layout_job(LayoutJob::simple(
|
||||||
|
text,
|
||||||
|
text_style,
|
||||||
|
crate::Color32::TEMPORARY_COLOR,
|
||||||
|
wrap_width,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// The collection of fonts used by `epaint`.
|
||||||
|
///
|
||||||
|
/// Required in order to paint text.
|
||||||
|
pub struct FontsImpl {
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
definitions: FontDefinitions,
|
definitions: FontDefinitions,
|
||||||
fonts: BTreeMap<TextStyle, Font>,
|
fonts: BTreeMap<TextStyle, Font>,
|
||||||
|
@ -238,9 +337,9 @@ pub struct Fonts {
|
||||||
galley_cache: Mutex<GalleyCache>,
|
galley_cache: Mutex<GalleyCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fonts {
|
impl FontsImpl {
|
||||||
/// Create a new [`Fonts`] for text layout.
|
/// Create a new [`FontsImpl`] for text layout.
|
||||||
/// This call is expensive, so only create one [`Fonts`] and then reuse it.
|
/// This call is expensive, so only create one [`FontsImpl`] and then reuse it.
|
||||||
pub fn new(pixels_per_point: f32, definitions: FontDefinitions) -> Self {
|
pub fn new(pixels_per_point: f32, definitions: FontDefinitions) -> Self {
|
||||||
assert!(
|
assert!(
|
||||||
0.0 < pixels_per_point && pixels_per_point < 100.0,
|
0.0 < pixels_per_point && pixels_per_point < 100.0,
|
||||||
|
@ -332,51 +431,6 @@ impl Fonts {
|
||||||
pub fn layout_job(&self, job: LayoutJob) -> Arc<Galley> {
|
pub fn layout_job(&self, job: LayoutJob) -> Arc<Galley> {
|
||||||
self.galley_cache.lock().layout(self, job)
|
self.galley_cache.lock().layout(self, job)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Will wrap text at the given width and line break at `\n`.
|
|
||||||
///
|
|
||||||
/// The implementation uses memoization so repeated calls are cheap.
|
|
||||||
pub fn layout(
|
|
||||||
&self,
|
|
||||||
text: String,
|
|
||||||
text_style: TextStyle,
|
|
||||||
color: crate::Color32,
|
|
||||||
wrap_width: f32,
|
|
||||||
) -> Arc<Galley> {
|
|
||||||
let job = LayoutJob::simple(text, text_style, color, wrap_width);
|
|
||||||
self.layout_job(job)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Will line break at `\n`.
|
|
||||||
///
|
|
||||||
/// The implementation uses memoization so repeated calls are cheap.
|
|
||||||
pub fn layout_no_wrap(
|
|
||||||
&self,
|
|
||||||
text: String,
|
|
||||||
text_style: TextStyle,
|
|
||||||
color: crate::Color32,
|
|
||||||
) -> Arc<Galley> {
|
|
||||||
let job = LayoutJob::simple(text, text_style, color, f32::INFINITY);
|
|
||||||
self.layout_job(job)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like [`Self::layout`], made for when you want to pick a color for the text later.
|
|
||||||
///
|
|
||||||
/// The implementation uses memoization so repeated calls are cheap.
|
|
||||||
pub fn layout_delayed_color(
|
|
||||||
&self,
|
|
||||||
text: String,
|
|
||||||
text_style: TextStyle,
|
|
||||||
wrap_width: f32,
|
|
||||||
) -> Arc<Galley> {
|
|
||||||
self.layout_job(LayoutJob::simple(
|
|
||||||
text,
|
|
||||||
text_style,
|
|
||||||
crate::Color32::TEMPORARY_COLOR,
|
|
||||||
wrap_width,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn num_galleys_in_cache(&self) -> usize {
|
pub fn num_galleys_in_cache(&self) -> usize {
|
||||||
self.galley_cache.lock().num_galleys_in_cache()
|
self.galley_cache.lock().num_galleys_in_cache()
|
||||||
}
|
}
|
||||||
|
@ -403,7 +457,7 @@ struct GalleyCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GalleyCache {
|
impl GalleyCache {
|
||||||
fn layout(&mut self, fonts: &Fonts, job: LayoutJob) -> Arc<Galley> {
|
fn layout(&mut self, fonts: &FontsImpl, job: LayoutJob) -> Arc<Galley> {
|
||||||
let hash = crate::util::hash(&job); // TODO: even faster hasher?
|
let hash = crate::util::hash(&job); // TODO: even faster hasher?
|
||||||
|
|
||||||
match self.cache.entry(hash) {
|
match self.cache.entry(hash) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ mod text_layout_types;
|
||||||
pub const TAB_SIZE: usize = 4;
|
pub const TAB_SIZE: usize = 4;
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
fonts::{FontData, FontDefinitions, FontFamily, Fonts, TextStyle},
|
fonts::{FontData, FontDefinitions, FontFamily, Fonts, FontsImpl, TextStyle},
|
||||||
text_layout::layout,
|
text_layout::layout,
|
||||||
text_layout_types::*,
|
text_layout_types::*,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use super::{Fonts, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals};
|
use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals};
|
||||||
use crate::{mutex::Arc, Color32, Mesh, Stroke, Vertex};
|
use crate::{mutex::Arc, Color32, Mesh, Stroke, Vertex};
|
||||||
use emath::*;
|
use emath::*;
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ struct Paragraph {
|
||||||
///
|
///
|
||||||
/// In most cases you should use [`Fonts::layout_job`] instead
|
/// In most cases you should use [`Fonts::layout_job`] instead
|
||||||
/// since that memoizes the input, making subsequent layouting of the same text much faster.
|
/// since that memoizes the input, making subsequent layouting of the same text much faster.
|
||||||
pub fn layout(fonts: &Fonts, job: Arc<LayoutJob>) -> Galley {
|
pub fn layout(fonts: &FontsImpl, job: Arc<LayoutJob>) -> Galley {
|
||||||
let mut paragraphs = vec![Paragraph::default()];
|
let mut paragraphs = vec![Paragraph::default()];
|
||||||
for (section_index, section) in job.sections.iter().enumerate() {
|
for (section_index, section) in job.sections.iter().enumerate() {
|
||||||
layout_section(fonts, &job, section_index as u32, section, &mut paragraphs);
|
layout_section(fonts, &job, section_index as u32, section, &mut paragraphs);
|
||||||
|
@ -75,7 +75,7 @@ pub fn layout(fonts: &Fonts, job: Arc<LayoutJob>) -> Galley {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_section(
|
fn layout_section(
|
||||||
fonts: &Fonts,
|
fonts: &FontsImpl,
|
||||||
job: &LayoutJob,
|
job: &LayoutJob,
|
||||||
section_index: u32,
|
section_index: u32,
|
||||||
section: &LayoutSection,
|
section: &LayoutSection,
|
||||||
|
|
|
@ -285,7 +285,7 @@ impl Frame {
|
||||||
Self(Arc::new(Mutex::new(frame_data)))
|
Self(Arc::new(Mutex::new(frame_data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience to access the underlying `backend::FrameData`.
|
/// Access the underlying [`backend::FrameData`].
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> {
|
pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> {
|
||||||
|
|
Loading…
Reference in a new issue