Skip to content

Commit

Permalink
feat!: Support fractional grid and font sizes (#2485)
Browse files Browse the repository at this point in the history
* Always use fractional font sizes

NOTE: The grid is still integer sized, so it's rendered wrong

* Remove fudge factor

* Properly set a fractional grid size

* Disable baseline snapping, and enable rounding to integers for scroll offsets again

The baseline snapping needs to be disabled so that the lines are
properly placed, and not snapped to pixels. While using integer based
scroll offsets greatly reduces the pressure on the Skia font cache.

* Plot the skia cache sizes

* Improve the Skia font cache usage

A bigger default size with cleanup when idling
  • Loading branch information
fredizzimo committed Apr 23, 2024
1 parent 776ad1d commit 12a415a
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 73 deletions.
4 changes: 2 additions & 2 deletions src/bridge/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub enum GuiOption {
GuiFont(String),
GuiFontSet(String),
GuiFontWide(String),
LineSpace(i64),
LineSpace(f64),
Pumblend(u64),
ShowTabLine(u64),
TermGuiColors(bool),
Expand Down Expand Up @@ -454,7 +454,7 @@ fn parse_option_set(option_set_arguments: Vec<Value>) -> Result<RedrawEvent> {
"guifont" => GuiOption::GuiFont(parse_string(value)?),
"guifontset" => GuiOption::GuiFontSet(parse_string(value)?),
"guifontwide" => GuiOption::GuiFontWide(parse_string(value)?),
"linespace" => GuiOption::LineSpace(parse_i64(value)?),
"linespace" => GuiOption::LineSpace(parse_f64(value)?),
"pumblend" => GuiOption::Pumblend(parse_u64(value)?),
"showtabline" => GuiOption::ShowTabLine(parse_u64(value)?),
"termguicolors" => GuiOption::TermGuiColors(parse_bool(value)?),
Expand Down
2 changes: 1 addition & 1 deletion src/editor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ impl Editor {
}
GuiOption::LineSpace(linespace) => {
self.draw_command_batcher
.queue(DrawCommand::LineSpaceChanged(linespace));
.queue(DrawCommand::LineSpaceChanged(linespace as f32));

self.redraw_screen();
}
Expand Down
74 changes: 22 additions & 52 deletions src/renderer/fonts/caching_shaper.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
use std::{num::NonZeroUsize, sync::Arc};

use itertools::Itertools;
use log::{debug, error, trace, warn};
use log::{debug, error, info, trace};
use lru::LruCache;
use skia_safe::{
graphics::{font_cache_limit, font_cache_used, set_font_cache_limit},
TextBlob, TextBlobBuilder,
};
use skia_safe::{graphics::set_font_cache_limit, TextBlob, TextBlobBuilder};
use swash::{
shape::ShapeContext,
text::{
Expand All @@ -30,13 +27,14 @@ struct ShapeKey {
pub style: CoarseStyle,
}

const FONT_CACHE_SIZE: usize = 8 * 1024 * 1024;

pub struct CachingShaper {
options: FontOptions,
font_loader: FontLoader,
blob_cache: LruCache<ShapeKey, Vec<TextBlob>>,
shape_context: ShapeContext,
scale_factor: f32,
fudge_factor: f32,
linespace: f32,
font_info: Option<(Metrics, f32)>,
}
Expand All @@ -51,7 +49,6 @@ impl CachingShaper {
blob_cache: LruCache::new(NonZeroUsize::new(10000).unwrap()),
shape_context: ShapeContext::new(),
scale_factor,
fudge_factor: 1.0,
linespace: 0.0,
font_info: None,
};
Expand All @@ -75,7 +72,7 @@ impl CachingShaper {

pub fn current_size(&self) -> f32 {
let min_font_size = 1.0;
(self.options.size * self.scale_factor * self.fudge_factor).max(min_font_size)
(self.options.size * self.scale_factor).max(min_font_size)
}

pub fn update_scale_factor(&mut self, scale_factor: f32) {
Expand Down Expand Up @@ -154,31 +151,16 @@ impl CachingShaper {
}

fn reset_font_loader(&mut self) {
self.fudge_factor = 1.0;
self.font_info = None;
let mut font_size = self.current_size();
debug!("Original font_size: {:.2}px", font_size);
let font_size = self.current_size();

self.font_loader = FontLoader::new(font_size);
let (metrics, font_width) = self.info();

debug!("Original font_width: {:.2}px", font_width);
let (_, font_width) = self.info();
info!(
"Reset Font Loader: font_size: {:.2}px, font_width: {:.2}px",
font_size, font_width
);

if !self.options.allow_float_size {
// Calculate the new fudge factor required to scale the font width to the nearest exact pixel
debug!(
"Font width: {:.2}px (avg: {:.2}px)",
font_width, metrics.average_width
);
let min_fudged_width = 1.0;
self.fudge_factor = font_width.round().max(min_fudged_width) / font_width;
debug!("Fudge factor: {:.2}", self.fudge_factor);
font_size = self.current_size();
self.font_info = None;
self.font_loader = FontLoader::new(font_size);
debug!("Fudged font size: {:.2}px", font_size);
debug!("Fudged font width: {:.2}px", self.info().1);
}
self.blob_cache.clear();
}

Expand Down Expand Up @@ -219,16 +201,12 @@ impl CachingShaper {
pub fn font_base_dimensions(&mut self) -> PixelSize<f32> {
let (metrics, glyph_advance) = self.info();

let bare_font_height = (metrics.ascent + metrics.descent + metrics.leading).ceil();
let bare_font_height = metrics.ascent + metrics.descent + metrics.leading;
// assuming that linespace is checked on receive for validity
let font_height = bare_font_height + self.linespace;
let font_width = (glyph_advance + self.options.width + 0.5).floor();

(
font_width,
font_height, // assuming that linespace is checked on receive for
// validity
)
.into()
let font_width = glyph_advance + self.options.width;

(font_width, font_height).into()
}

pub fn underline_position(&mut self) -> f32 {
Expand All @@ -237,7 +215,7 @@ impl CachingShaper {

pub fn y_adjustment(&mut self) -> f32 {
let metrics = self.metrics();
(metrics.ascent + metrics.leading + self.linespace / 2.).ceil()
metrics.ascent + metrics.leading + self.linespace / 2.
}

fn build_clusters(
Expand Down Expand Up @@ -373,16 +351,10 @@ impl CachingShaper {
grouped_results
}

pub fn adjust_font_cache_size(&self) {
let current_font_cache_size = font_cache_limit() as f32;
let percent_font_cache_used = font_cache_used() as f32 / current_font_cache_size;
if percent_font_cache_used > 0.9 {
warn!(
"Font cache is {}% full, increasing cache size",
percent_font_cache_used * 100.0
);
set_font_cache_limit((percent_font_cache_used * 1.5) as usize);
}
pub fn cleanup_font_cache(&self) {
tracy_zone!("purge_font_cache");
set_font_cache_limit(FONT_CACHE_SIZE / 2);
set_font_cache_limit(FONT_CACHE_SIZE);
}

pub fn shape(&mut self, text: String, style: CoarseStyle) -> Vec<TextBlob> {
Expand Down Expand Up @@ -420,7 +392,7 @@ impl CachingShaper {

shaper.shape_with(|glyph_cluster| {
for glyph in glyph_cluster.glyphs {
let position = ((glyph.data as f32 * glyph_width), glyph.y);
let position = (glyph.data as f32 * glyph_width, glyph.y);
glyph_data.push((glyph.id, position));
}
});
Expand All @@ -441,8 +413,6 @@ impl CachingShaper {
resulting_blobs.push(blob.expect("Could not create textblob"));
}

self.adjust_font_cache_size();

resulting_blobs
}

Expand Down
1 change: 1 addition & 0 deletions src/renderer/fonts/font_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub struct FontPair {
impl FontPair {
fn new(key: FontKey, mut skia_font: Font) -> Option<FontPair> {
skia_font.set_subpixel(true);
skia_font.set_baseline_snap(false);
skia_font.set_hinting(font_hinting(&key.hinting));
skia_font.set_edging(font_edging(&key.edging));

Expand Down
11 changes: 0 additions & 11 deletions src/renderer/fonts/font_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const FONT_LIST_SEPARATOR: char = ',';
const FONT_HINTING_PREFIX: &str = "#h-";
const FONT_EDGING_PREFIX: &str = "#e-";
const FONT_HEIGHT_PREFIX: char = 'h';
const ALLOW_FLOAT_SIZE_OPT: char = '.';
const FONT_WIDTH_PREFIX: char = 'w';
const FONT_BOLD_OPT: &str = "b";
const FONT_ITALIC_OPT: &str = "i";
Expand Down Expand Up @@ -109,7 +108,6 @@ pub struct FontOptions {
pub features: HashMap<String /* family */, Vec<FontFeature> /* features */>,
pub size: f32,
pub width: f32,
pub allow_float_size: bool,
pub hinting: FontHinting,
pub edging: FontEdging,
}
Expand Down Expand Up @@ -168,10 +166,8 @@ impl FontOptions {
} else if let Some(edging_string) = part.strip_prefix(FONT_EDGING_PREFIX) {
font_options.edging = FontEdging::parse(edging_string)?;
} else if part.starts_with(FONT_HEIGHT_PREFIX) && part.len() > 1 {
font_options.allow_float_size |= part[1..].contains(ALLOW_FLOAT_SIZE_OPT);
font_options.size = parse_pixels(part).map_err(|_| INVALID_SIZE_ERR)?;
} else if part.starts_with(FONT_WIDTH_PREFIX) && part.len() > 1 {
font_options.allow_float_size |= part[1..].contains(ALLOW_FLOAT_SIZE_OPT);
font_options.width = parse_pixels(part).map_err(|_| INVALID_WIDTH_ERR)?;
} else if part == FONT_BOLD_OPT {
style.push("Bold".to_string());
Expand Down Expand Up @@ -238,7 +234,6 @@ impl Default for FontOptions {
bold: None,
bold_italic: None,
features: HashMap::new(),
allow_float_size: false,
size: points_to_pixels(DEFAULT_FONT_SIZE),
width: 0.0,
hinting: FontHinting::default(),
Expand Down Expand Up @@ -554,12 +549,6 @@ mod tests {
font_size_pixels, font_options.size,
);

assert_eq!(
font_options.allow_float_size, true,
"allow float size should equal {}, but {}",
true, font_options.allow_float_size,
);

for font in font_options.normal.iter() {
assert_eq!(
font.family, "Fira Code Mono",
Expand Down
37 changes: 35 additions & 2 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use std::{
use itertools::Itertools;
use log::{error, warn};
use skia_safe::Canvas;

use winit::{
event::Event,
event_loop::{EventLoop, EventLoopProxy},
Expand All @@ -37,6 +38,15 @@ use crate::{
WindowSettings,
};

#[cfg(feature = "profiling")]
use crate::profiling::tracy_plot;
#[cfg(feature = "profiling")]
use skia_safe::graphics::{
font_cache_count_limit, font_cache_count_used, font_cache_limit, font_cache_used,
resource_cache_single_allocation_byte_limit, resource_cache_total_bytes_limit,
resource_cache_total_bytes_used,
};

#[cfg(feature = "gpu_profiling")]
use crate::profiling::GpuCtx;

Expand All @@ -52,6 +62,26 @@ pub use vsync::VSync;

use self::fonts::font_options::FontOptions;

#[cfg(feature = "profiling")]
fn plot_skia_cache() {
tracy_plot!("font_cache_limit", font_cache_limit() as f64);
tracy_plot!("font_cache_used", font_cache_used() as f64);
tracy_plot!("font_cache_count_used", font_cache_count_used() as f64);
tracy_plot!("font_cache_count_limit", font_cache_count_limit() as f64);
tracy_plot!(
"resource_cache_total_bytes_used",
resource_cache_total_bytes_used() as f64
);
tracy_plot!(
"resource_cache_total_bytes_limit",
resource_cache_total_bytes_limit() as f64
);
tracy_plot!(
"resource_cache_single_allocation_byte_limit",
resource_cache_single_allocation_byte_limit().unwrap_or_default() as f64
);
}

#[derive(SettingGroup, Clone)]
pub struct RendererSettings {
position_animation_length: f32,
Expand Down Expand Up @@ -98,7 +128,7 @@ impl Default for RendererSettings {
pub enum DrawCommand {
UpdateCursor(Cursor),
FontChanged(String),
LineSpaceChanged(i64),
LineSpaceChanged(f32),
DefaultStyleChanged(Style),
ModeChanged(EditorMode),
UIReady,
Expand Down Expand Up @@ -277,6 +307,9 @@ impl Renderer {
self.profiler.draw(root_canvas, dt);

root_canvas.restore();

#[cfg(feature = "profiling")]
plot_skia_cache();
}

pub fn animate_frame(&mut self, grid_rect: &GridRect<f32>, dt: f32) -> bool {
Expand Down Expand Up @@ -412,7 +445,7 @@ impl Renderer {
result.font_changed = true;
}
DrawCommand::LineSpaceChanged(new_linespace) => {
self.grid_renderer.update_linespace(new_linespace as f32);
self.grid_renderer.update_linespace(new_linespace);
result.font_changed = true;
}
DrawCommand::DefaultStyleChanged(new_style) => {
Expand Down
5 changes: 2 additions & 3 deletions src/renderer/rendered_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,16 +689,15 @@ impl RenderedWindow {
) -> impl Iterator<Item = (Matrix, &Rc<RefCell<Line>>)> {
let scroll_offset_lines = self.scroll_animation.position.floor();
let scroll_offset = scroll_offset_lines - self.scroll_animation.position;
let scroll_offset_pixels = (scroll_offset * grid_scale.height()).round() as isize;
let scroll_offset_pixels = (scroll_offset * grid_scale.0.height).round();

self.iter_scrollable_lines().map(move |(i, line)| {
let mut matrix = Matrix::new_identity();
matrix.set_translate((
pixel_region.min.x,
pixel_region.min.y
+ (scroll_offset_pixels
+ ((i + self.viewport_margins.top as isize) * grid_scale.height() as isize))
as f32,
+ ((i + self.viewport_margins.top as isize) as f32 * grid_scale.0.height)),
));
(matrix, line)
})
Expand Down
1 change: 0 additions & 1 deletion src/settings/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ impl From<FontSettings> for FontOptions {
.unwrap_or_default(),
size: value.size,
width: value.width.unwrap_or_default(),
allow_float_size: value.allow_float_size.unwrap_or_default(),
hinting: value
.hinting
.map(|hinting| FontHinting::parse(&hinting).unwrap_or_default())
Expand Down
5 changes: 5 additions & 0 deletions src/window/update_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ impl UpdateLoop {
self.pending_render && Instant::now() > (self.animation_start + self.animation_time);
let should_prepare = !self.pending_render || skipped_frame;
if !should_prepare {
window_wrapper
.renderer
.grid_renderer
.shaper
.cleanup_font_cache();
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/window/window_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub struct WinitWindowWrapper {
// Don't rearrange this, unless you have a good reason to do so
// The destruction order has to be correct
pub skia_renderer: Box<dyn SkiaRenderer>,
renderer: Renderer,
pub renderer: Renderer,
keyboard_manager: KeyboardManager,
mouse_manager: MouseManager,
title: String,
Expand Down

0 comments on commit 12a415a

Please sign in to comment.