mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 21:00:03 -04:00
Faster galley cache (#699)
* Speed up galley cache by only using the hash as key This hashes the job but doesn't compare them with Eq, which speeds up demo_with_tessellate__realistic by 5-6%, winning back all the performance lost in https://github.com/emilk/egui/pull/682 * Remove custom Eq/PartialEq code for LayoutJob and friends * Silence clippy * Unrelated clippy fixes
This commit is contained in:
@@ -189,12 +189,3 @@ pub(crate) fn f32_hash<H: std::hash::Hasher>(state: &mut H, f: f32) {
|
||||
f.to_bits().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn f32_eq(a: f32, b: f32) -> bool {
|
||||
if a.is_nan() && b.is_nan() {
|
||||
true
|
||||
} else {
|
||||
a == b
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::derive_hash_xor_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Describes the width and color of a line.
|
||||
///
|
||||
/// The default stroke is the same as [`Stroke::none`].
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Stroke {
|
||||
pub width: f32,
|
||||
@@ -44,12 +46,3 @@ impl std::hash::Hash for Stroke {
|
||||
color.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Stroke {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.color == other.color && crate::f32_eq(self.width, other.width)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::Eq for Stroke {}
|
||||
|
||||
@@ -4,8 +4,6 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use ahash::AHashMap;
|
||||
|
||||
use crate::{
|
||||
mutex::Mutex,
|
||||
text::{
|
||||
@@ -321,8 +319,8 @@ impl Fonts {
|
||||
/// [`Self::layout_delayed_color`].
|
||||
///
|
||||
/// The implementation uses memoization so repeated calls are cheap.
|
||||
pub fn layout_job(&self, job: impl Into<Arc<LayoutJob>>) -> Arc<Galley> {
|
||||
self.galley_cache.lock().layout(self, job.into())
|
||||
pub fn layout_job(&self, job: LayoutJob) -> Arc<Galley> {
|
||||
self.galley_cache.lock().layout(self, job)
|
||||
}
|
||||
|
||||
/// Will wrap text at the given width and line break at `\n`.
|
||||
@@ -400,19 +398,25 @@ struct CachedGalley {
|
||||
struct GalleyCache {
|
||||
/// Frame counter used to do garbage collection on the cache
|
||||
generation: u32,
|
||||
cache: AHashMap<Arc<LayoutJob>, CachedGalley>,
|
||||
cache: nohash_hasher::IntMap<u64, CachedGalley>,
|
||||
}
|
||||
|
||||
impl GalleyCache {
|
||||
fn layout(&mut self, fonts: &Fonts, job: Arc<LayoutJob>) -> Arc<Galley> {
|
||||
match self.cache.entry(job.clone()) {
|
||||
fn layout(&mut self, fonts: &Fonts, job: LayoutJob) -> Arc<Galley> {
|
||||
let hash = {
|
||||
let mut hasher = ahash::AHasher::new_with_keys(123, 456); // TODO: even faster hasher?
|
||||
job.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
};
|
||||
|
||||
match self.cache.entry(hash) {
|
||||
std::collections::hash_map::Entry::Occupied(entry) => {
|
||||
let cached = entry.into_mut();
|
||||
cached.last_used = self.generation;
|
||||
cached.galley.clone()
|
||||
}
|
||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||
let galley = super::layout(fonts, job);
|
||||
let galley = super::layout(fonts, job.into());
|
||||
let galley = Arc::new(galley);
|
||||
entry.insert(CachedGalley {
|
||||
last_used: self.generation,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::derive_hash_xor_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine
|
||||
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -10,7 +12,7 @@ use emath::*;
|
||||
/// This supports mixing different fonts, color and formats (underline etc).
|
||||
///
|
||||
/// Pass this to [`Fonts::layout_job]` or [`crate::text::layout`].
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct LayoutJob {
|
||||
/// The complete text of this job, referenced by `LayoutSection`.
|
||||
pub text: String, // TODO: Cow<'static, str>
|
||||
@@ -120,22 +122,9 @@ impl std::hash::Hash for LayoutJob {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for LayoutJob {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.text == other.text
|
||||
&& self.sections == other.sections
|
||||
&& crate::f32_eq(self.wrap_width, other.wrap_width)
|
||||
&& crate::f32_eq(self.first_row_min_height, other.first_row_min_height)
|
||||
&& self.break_on_newline == other.break_on_newline
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::Eq for LayoutJob {}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct LayoutSection {
|
||||
/// Can be used for first row indentation.
|
||||
pub leading_space: f32,
|
||||
@@ -158,20 +147,9 @@ impl std::hash::Hash for LayoutSection {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for LayoutSection {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
crate::f32_eq(self.leading_space, other.leading_space)
|
||||
&& self.byte_range == other.byte_range
|
||||
&& self.format == other.format
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::Eq for LayoutSection {}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq)]
|
||||
pub struct TextFormat {
|
||||
pub style: TextStyle,
|
||||
/// Text color
|
||||
|
||||
Reference in New Issue
Block a user