Scope line layout cache to each window (#7235)

This improves a performance problem we were observing when having
multiple windows updating at the same time, where each window would
invalidate the other window's layout cache.

Release Notes:

- Improved performance when having multiple Zed windows open.

Co-authored-by: Max Brunsfeld <max@zed.dev>
This commit is contained in:
Antonio Scandurra 2024-02-02 10:11:20 -07:00 committed by GitHub
parent 5360c0ea28
commit a0b52cc69a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 104 additions and 78 deletions

View file

@ -15,6 +15,7 @@ use crate::{
use anyhow::anyhow;
use collections::{BTreeSet, FxHashMap, FxHashSet};
use core::fmt;
use derive_more::Deref;
use itertools::Itertools;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::{smallvec, SmallVec};
@ -38,9 +39,8 @@ pub struct FontFamilyId(pub usize);
pub(crate) const SUBPIXEL_VARIANTS: u8 = 4;
/// The GPUI text layout and rendering sub system.
/// The GPUI text rendering sub system.
pub struct TextSystem {
line_layout_cache: Arc<LineLayoutCache>,
platform_text_system: Arc<dyn PlatformTextSystem>,
font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
@ -53,7 +53,6 @@ pub struct TextSystem {
impl TextSystem {
pub(crate) fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
TextSystem {
line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())),
platform_text_system,
font_metrics: RwLock::default(),
raster_bounds: RwLock::default(),
@ -234,43 +233,66 @@ impl TextSystem {
}
}
pub(crate) fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
self.line_layout_cache.with_view(view_id, f)
/// Returns a handle to a line wrapper, for the given font and font size.
pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
let lock = &mut self.wrapper_pool.lock();
let font_id = self.resolve_font(&font);
let wrappers = lock
.entry(FontIdWithSize { font_id, font_size })
.or_default();
let wrapper = wrappers.pop().unwrap_or_else(|| {
LineWrapper::new(font_id, font_size, self.platform_text_system.clone())
});
LineWrapperHandle {
wrapper: Some(wrapper),
text_system: self.clone(),
}
}
/// Layout the given line of text, at the given font_size.
/// Subsets of the line can be styled independently with the `runs` parameter.
/// Generally, you should prefer to use `TextLayout::shape_line` instead, which
/// can be painted directly.
pub fn layout_line(
&self,
text: &str,
font_size: Pixels,
runs: &[TextRun],
) -> Result<Arc<LineLayout>> {
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
for run in runs.iter() {
let font_id = self.resolve_font(&run.font);
if let Some(last_run) = font_runs.last_mut() {
if last_run.font_id == font_id {
last_run.len += run.len;
continue;
}
}
font_runs.push(FontRun {
len: run.len,
font_id,
});
/// Get the rasterized size and location of a specific, rendered glyph.
pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
let raster_bounds = self.raster_bounds.upgradable_read();
if let Some(bounds) = raster_bounds.get(params) {
Ok(*bounds)
} else {
let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
raster_bounds.insert(params.clone(), bounds);
Ok(bounds)
}
}
let layout = self
.line_layout_cache
.layout_line(text, font_size, &font_runs);
pub(crate) fn rasterize_glyph(
&self,
params: &RenderGlyphParams,
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
let raster_bounds = self.raster_bounds(params)?;
self.platform_text_system
.rasterize_glyph(params, raster_bounds)
}
}
font_runs.clear();
self.font_runs_pool.lock().push(font_runs);
/// The GPUI text layout subsystem.
#[derive(Deref)]
pub struct WindowTextSystem {
line_layout_cache: Arc<LineLayoutCache>,
#[deref]
text_system: Arc<TextSystem>,
}
Ok(layout)
impl WindowTextSystem {
pub(crate) fn new(text_system: Arc<TextSystem>) -> Self {
Self {
line_layout_cache: Arc::new(LineLayoutCache::new(
text_system.platform_text_system.clone(),
)),
text_system,
}
}
pub(crate) fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
self.line_layout_cache.with_view(view_id, f)
}
/// Shape the given line, at the given font_size, for painting to the screen.
@ -429,43 +451,39 @@ impl TextSystem {
self.line_layout_cache.finish_frame(reused_views)
}
/// Returns a handle to a line wrapper, for the given font and font size.
pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
let lock = &mut self.wrapper_pool.lock();
let font_id = self.resolve_font(&font);
let wrappers = lock
.entry(FontIdWithSize { font_id, font_size })
.or_default();
let wrapper = wrappers.pop().unwrap_or_else(|| {
LineWrapper::new(font_id, font_size, self.platform_text_system.clone())
});
LineWrapperHandle {
wrapper: Some(wrapper),
text_system: self.clone(),
}
}
/// Get the rasterized size and location of a specific, rendered glyph.
pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
let raster_bounds = self.raster_bounds.upgradable_read();
if let Some(bounds) = raster_bounds.get(params) {
Ok(*bounds)
} else {
let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
raster_bounds.insert(params.clone(), bounds);
Ok(bounds)
}
}
pub(crate) fn rasterize_glyph(
/// Layout the given line of text, at the given font_size.
/// Subsets of the line can be styled independently with the `runs` parameter.
/// Generally, you should prefer to use `TextLayout::shape_line` instead, which
/// can be painted directly.
pub fn layout_line(
&self,
params: &RenderGlyphParams,
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
let raster_bounds = self.raster_bounds(params)?;
self.platform_text_system
.rasterize_glyph(params, raster_bounds)
text: &str,
font_size: Pixels,
runs: &[TextRun],
) -> Result<Arc<LineLayout>> {
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
for run in runs.iter() {
let font_id = self.resolve_font(&run.font);
if let Some(last_run) = font_runs.last_mut() {
if last_run.font_id == font_id {
last_run.len += run.len;
continue;
}
}
font_runs.push(FontRun {
len: run.len,
font_id,
});
}
let layout = self
.line_layout_cache
.layout_line(text, font_size, &font_runs);
font_runs.clear();
self.font_runs_pool.lock().push(font_runs);
Ok(layout)
}
}