|
|
|
@@ -1,4 +1,5 @@
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::collections::{HashMap, hash_map::DefaultHasher};
|
|
|
|
|
|
|
|
use std::hash::{Hash, Hasher};
|
|
|
|
use std::rc::Rc;
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
|
|
|
|
|
|
use js_sys::Float32Array;
|
|
|
|
use js_sys::Float32Array;
|
|
|
|
@@ -378,6 +379,8 @@ pub struct WebGlRenderStats {
|
|
|
|
pub vertex_upload_ms: f64,
|
|
|
|
pub vertex_upload_ms: f64,
|
|
|
|
pub draw_ms: f64,
|
|
|
|
pub draw_ms: f64,
|
|
|
|
pub cells: usize,
|
|
|
|
pub cells: usize,
|
|
|
|
|
|
|
|
pub row_cache_hits: usize,
|
|
|
|
|
|
|
|
pub row_cache_misses: usize,
|
|
|
|
pub atlas_entries: usize,
|
|
|
|
pub atlas_entries: usize,
|
|
|
|
pub atlas_insertions: usize,
|
|
|
|
pub atlas_insertions: usize,
|
|
|
|
pub atlas_resets: usize,
|
|
|
|
pub atlas_resets: usize,
|
|
|
|
@@ -466,11 +469,13 @@ pub(super) struct WebGlRendererState {
|
|
|
|
tex_coord_location: u32,
|
|
|
|
tex_coord_location: u32,
|
|
|
|
color_location: u32,
|
|
|
|
color_location: u32,
|
|
|
|
sampler_location: Option<WebGlUniformLocation>,
|
|
|
|
sampler_location: Option<WebGlUniformLocation>,
|
|
|
|
|
|
|
|
atlas_signature: u64,
|
|
|
|
|
|
|
|
row_cache: WebGlRowCache,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
|
|
struct GlyphKey {
|
|
|
|
struct GlyphKey {
|
|
|
|
text: String,
|
|
|
|
text: Rc<str>,
|
|
|
|
bold: bool,
|
|
|
|
bold: bool,
|
|
|
|
italic: bool,
|
|
|
|
italic: bool,
|
|
|
|
cols: usize,
|
|
|
|
cols: usize,
|
|
|
|
@@ -564,6 +569,30 @@ struct GlyphCell {
|
|
|
|
fg: [f32; 4],
|
|
|
|
fg: [f32; 4],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
|
|
|
|
struct CachedGlyphCell {
|
|
|
|
|
|
|
|
key: GlyphKey,
|
|
|
|
|
|
|
|
col: usize,
|
|
|
|
|
|
|
|
fg: [f32; 4],
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
|
|
|
struct CachedRowGlyphs {
|
|
|
|
|
|
|
|
key: u64,
|
|
|
|
|
|
|
|
cells: Vec<CachedGlyphCell>,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
|
|
|
|
struct WebGlRowCache {
|
|
|
|
|
|
|
|
glyph_rows: Vec<CachedRowGlyphs>,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
|
|
|
|
|
|
struct WebGlRowCacheStats {
|
|
|
|
|
|
|
|
hits: usize,
|
|
|
|
|
|
|
|
misses: usize,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
struct GlyphQuad {
|
|
|
|
struct GlyphQuad {
|
|
|
|
x: f64,
|
|
|
|
x: f64,
|
|
|
|
@@ -592,6 +621,11 @@ fn split_segment_glyphs(
|
|
|
|
let mut cells = Vec::new();
|
|
|
|
let mut cells = Vec::new();
|
|
|
|
let mut col = start_col;
|
|
|
|
let mut col = start_col;
|
|
|
|
let mut used_cols = 0usize;
|
|
|
|
let mut used_cols = 0usize;
|
|
|
|
|
|
|
|
let fg = if segment.is_cursor && options.cursor.style == TerminalCursorStyle::Block {
|
|
|
|
|
|
|
|
parse_css_rgb(&options.theme.cursor_foreground)?
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
parse_css_rgb(themed_color(&segment.style.color, options))?
|
|
|
|
|
|
|
|
};
|
|
|
|
for grapheme in UnicodeSegmentation::graphemes(segment.text.as_str(), true) {
|
|
|
|
for grapheme in UnicodeSegmentation::graphemes(segment.text.as_str(), true) {
|
|
|
|
let mut cols = UnicodeWidthStr::width(grapheme);
|
|
|
|
let mut cols = UnicodeWidthStr::width(grapheme);
|
|
|
|
if cols == 0 {
|
|
|
|
if cols == 0 {
|
|
|
|
@@ -600,14 +634,9 @@ fn split_segment_glyphs(
|
|
|
|
if used_cols + cols > segment.cols {
|
|
|
|
if used_cols + cols > segment.cols {
|
|
|
|
return None;
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let fg = if segment.is_cursor && options.cursor.style == TerminalCursorStyle::Block {
|
|
|
|
|
|
|
|
parse_css_rgb(&options.theme.cursor_foreground)?
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
parse_css_rgb(themed_color(&segment.style.color, options))?
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
cells.push(GlyphCell {
|
|
|
|
cells.push(GlyphCell {
|
|
|
|
key: GlyphKey {
|
|
|
|
key: GlyphKey {
|
|
|
|
text: grapheme.to_owned(),
|
|
|
|
text: Rc::<str>::from(grapheme),
|
|
|
|
bold: segment.style.bold,
|
|
|
|
bold: segment.style.bold,
|
|
|
|
italic: segment.style.italic,
|
|
|
|
italic: segment.style.italic,
|
|
|
|
cols,
|
|
|
|
cols,
|
|
|
|
@@ -629,18 +658,100 @@ fn split_segment_glyphs(
|
|
|
|
fn collect_glyph_cells(
|
|
|
|
fn collect_glyph_cells(
|
|
|
|
visible: &[TerminalRow],
|
|
|
|
visible: &[TerminalRow],
|
|
|
|
options: &ResolvedTerminalOptions,
|
|
|
|
options: &ResolvedTerminalOptions,
|
|
|
|
) -> Option<Vec<GlyphCell>> {
|
|
|
|
cache: &mut WebGlRowCache,
|
|
|
|
|
|
|
|
) -> Option<(Vec<GlyphCell>, WebGlRowCacheStats)> {
|
|
|
|
let mut cells = Vec::new();
|
|
|
|
let mut cells = Vec::new();
|
|
|
|
|
|
|
|
let mut stats = WebGlRowCacheStats::default();
|
|
|
|
for (row_idx, row) in visible.iter().enumerate() {
|
|
|
|
for (row_idx, row) in visible.iter().enumerate() {
|
|
|
|
let mut col = 0usize;
|
|
|
|
let cached = cache.glyphs_for_row(row, options, &mut stats)?;
|
|
|
|
for segment in &row.segments {
|
|
|
|
cells.extend(cached.iter().map(|cell| GlyphCell {
|
|
|
|
cells.extend(split_segment_glyphs(segment, row_idx, col, options)?);
|
|
|
|
key: cell.key.clone(),
|
|
|
|
col += segment.cols;
|
|
|
|
row: row_idx,
|
|
|
|
|
|
|
|
col: cell.col,
|
|
|
|
|
|
|
|
fg: cell.fg,
|
|
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
cache.retain_recent(visible.len());
|
|
|
|
|
|
|
|
Some((cells, stats))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl WebGlRowCache {
|
|
|
|
|
|
|
|
fn glyphs_for_row(
|
|
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
|
|
row: &TerminalRow,
|
|
|
|
|
|
|
|
options: &ResolvedTerminalOptions,
|
|
|
|
|
|
|
|
stats: &mut WebGlRowCacheStats,
|
|
|
|
|
|
|
|
) -> Option<&[CachedGlyphCell]> {
|
|
|
|
|
|
|
|
let key = row_glyph_cache_key(row, options);
|
|
|
|
|
|
|
|
if let Some(index) = self.glyph_rows.iter().position(|cached| cached.key == key) {
|
|
|
|
|
|
|
|
stats.hits += 1;
|
|
|
|
|
|
|
|
if index != 0 {
|
|
|
|
|
|
|
|
let cached = self.glyph_rows.remove(index);
|
|
|
|
|
|
|
|
self.glyph_rows.insert(0, cached);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return Some(&self.glyph_rows[0].cells);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let cells = collect_row_glyph_cells(row, options)?;
|
|
|
|
|
|
|
|
stats.misses += 1;
|
|
|
|
|
|
|
|
self.glyph_rows.insert(0, CachedRowGlyphs { key, cells });
|
|
|
|
|
|
|
|
Some(&self.glyph_rows[0].cells)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn retain_recent(&mut self, visible_rows: usize) {
|
|
|
|
|
|
|
|
let max_rows = visible_rows.saturating_mul(4).clamp(64, 512);
|
|
|
|
|
|
|
|
if self.glyph_rows.len() > max_rows {
|
|
|
|
|
|
|
|
self.glyph_rows.truncate(max_rows);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn collect_row_glyph_cells(
|
|
|
|
|
|
|
|
row: &TerminalRow,
|
|
|
|
|
|
|
|
options: &ResolvedTerminalOptions,
|
|
|
|
|
|
|
|
) -> Option<Vec<CachedGlyphCell>> {
|
|
|
|
|
|
|
|
let mut cells = Vec::new();
|
|
|
|
|
|
|
|
let mut col = 0usize;
|
|
|
|
|
|
|
|
for segment in &row.segments {
|
|
|
|
|
|
|
|
cells.extend(
|
|
|
|
|
|
|
|
split_segment_glyphs(segment, 0, col, options)?
|
|
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
|
|
.map(|cell| CachedGlyphCell {
|
|
|
|
|
|
|
|
key: cell.key,
|
|
|
|
|
|
|
|
col: cell.col,
|
|
|
|
|
|
|
|
fg: cell.fg,
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
col += segment.cols;
|
|
|
|
|
|
|
|
}
|
|
|
|
Some(cells)
|
|
|
|
Some(cells)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn row_glyph_cache_key(row: &TerminalRow, options: &ResolvedTerminalOptions) -> u64 {
|
|
|
|
|
|
|
|
let mut hasher = DefaultHasher::new();
|
|
|
|
|
|
|
|
cursor_style_cache_value(options.cursor.style).hash(&mut hasher);
|
|
|
|
|
|
|
|
options.theme.foreground.hash(&mut hasher);
|
|
|
|
|
|
|
|
options.theme.background.hash(&mut hasher);
|
|
|
|
|
|
|
|
options.theme.cursor_foreground.hash(&mut hasher);
|
|
|
|
|
|
|
|
for segment in &row.segments {
|
|
|
|
|
|
|
|
segment.text.hash(&mut hasher);
|
|
|
|
|
|
|
|
segment.cols.hash(&mut hasher);
|
|
|
|
|
|
|
|
segment.style.color.hash(&mut hasher);
|
|
|
|
|
|
|
|
segment.style.bold.hash(&mut hasher);
|
|
|
|
|
|
|
|
segment.style.italic.hash(&mut hasher);
|
|
|
|
|
|
|
|
segment.is_cursor.hash(&mut hasher);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
hasher.finish()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn cursor_style_cache_value(style: TerminalCursorStyle) -> u8 {
|
|
|
|
|
|
|
|
match style {
|
|
|
|
|
|
|
|
TerminalCursorStyle::Block => 0,
|
|
|
|
|
|
|
|
TerminalCursorStyle::Underline => 1,
|
|
|
|
|
|
|
|
TerminalCursorStyle::Bar => 2,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn collect_backdrop_quads(frame: CanvasSurfaceFrame<'_>) -> Option<Vec<SolidQuad>> {
|
|
|
|
fn collect_backdrop_quads(frame: CanvasSurfaceFrame<'_>) -> Option<Vec<SolidQuad>> {
|
|
|
|
let (cell_w, cell_h) = frame.cell_size;
|
|
|
|
let (cell_w, cell_h) = frame.cell_size;
|
|
|
|
let mut quads = Vec::new();
|
|
|
|
let mut quads = Vec::new();
|
|
|
|
@@ -809,9 +920,12 @@ pub(super) fn render_webgl(
|
|
|
|
state_ref.take();
|
|
|
|
state_ref.take();
|
|
|
|
return Err("WebGL context is lost".to_owned());
|
|
|
|
return Err("WebGL context is lost".to_owned());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
reset_webgl_caches_if_needed(state, frame);
|
|
|
|
|
|
|
|
|
|
|
|
let collect_start = perf_now();
|
|
|
|
let collect_start = perf_now();
|
|
|
|
let Some(cells) = collect_glyph_cells(frame.visible, frame.options) else {
|
|
|
|
let Some((cells, row_cache_stats)) =
|
|
|
|
|
|
|
|
collect_glyph_cells(frame.visible, frame.options, &mut state.row_cache)
|
|
|
|
|
|
|
|
else {
|
|
|
|
stats.collect_ms = perf_now() - collect_start;
|
|
|
|
stats.collect_ms = perf_now() - collect_start;
|
|
|
|
render_webgl_canvas_fallback(state, frame, &mut stats)?;
|
|
|
|
render_webgl_canvas_fallback(state, frame, &mut stats)?;
|
|
|
|
stats.total_ms = perf_now() - total_start;
|
|
|
|
stats.total_ms = perf_now() - total_start;
|
|
|
|
@@ -825,6 +939,8 @@ pub(super) fn render_webgl(
|
|
|
|
};
|
|
|
|
};
|
|
|
|
stats.collect_ms = perf_now() - collect_start;
|
|
|
|
stats.collect_ms = perf_now() - collect_start;
|
|
|
|
stats.cells = cells.len();
|
|
|
|
stats.cells = cells.len();
|
|
|
|
|
|
|
|
stats.row_cache_hits = row_cache_stats.hits;
|
|
|
|
|
|
|
|
stats.row_cache_misses = row_cache_stats.misses;
|
|
|
|
|
|
|
|
|
|
|
|
let backdrop_start = perf_now();
|
|
|
|
let backdrop_start = perf_now();
|
|
|
|
draw_webgl_backdrop_quads(state, frame.dom_state, &backdrop_quads)?;
|
|
|
|
draw_webgl_backdrop_quads(state, frame.dom_state, &backdrop_quads)?;
|
|
|
|
@@ -857,6 +973,32 @@ fn render_webgl_canvas_fallback(
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn reset_webgl_caches_if_needed(state: &mut WebGlRendererState, frame: CanvasSurfaceFrame<'_>) {
|
|
|
|
|
|
|
|
let signature = webgl_atlas_signature(frame);
|
|
|
|
|
|
|
|
if state.atlas_signature == signature {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
state.atlas_signature = signature;
|
|
|
|
|
|
|
|
state.atlas.reset();
|
|
|
|
|
|
|
|
state.row_cache.glyph_rows.clear();
|
|
|
|
|
|
|
|
state.atlas_ctx.clear_rect(
|
|
|
|
|
|
|
|
0.0,
|
|
|
|
|
|
|
|
0.0,
|
|
|
|
|
|
|
|
f64::from(state.atlas.size),
|
|
|
|
|
|
|
|
f64::from(state.atlas.size),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn webgl_atlas_signature(frame: CanvasSurfaceFrame<'_>) -> u64 {
|
|
|
|
|
|
|
|
let mut hasher = DefaultHasher::new();
|
|
|
|
|
|
|
|
frame.options.font_family.hash(&mut hasher);
|
|
|
|
|
|
|
|
frame.options.font_size_px.to_bits().hash(&mut hasher);
|
|
|
|
|
|
|
|
frame.dpr.to_bits().hash(&mut hasher);
|
|
|
|
|
|
|
|
frame.cell_size.0.to_bits().hash(&mut hasher);
|
|
|
|
|
|
|
|
frame.cell_size.1.to_bits().hash(&mut hasher);
|
|
|
|
|
|
|
|
hasher.finish()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn create_webgl_renderer(canvas: &HtmlCanvasElement) -> Result<WebGlRendererState, String> {
|
|
|
|
fn create_webgl_renderer(canvas: &HtmlCanvasElement) -> Result<WebGlRendererState, String> {
|
|
|
|
let gl = canvas
|
|
|
|
let gl = canvas
|
|
|
|
.get_context("webgl2")
|
|
|
|
.get_context("webgl2")
|
|
|
|
@@ -958,6 +1100,8 @@ fn create_webgl_renderer(canvas: &HtmlCanvasElement) -> Result<WebGlRendererStat
|
|
|
|
tex_coord_location: tex_coord_location as u32,
|
|
|
|
tex_coord_location: tex_coord_location as u32,
|
|
|
|
color_location: color_location as u32,
|
|
|
|
color_location: color_location as u32,
|
|
|
|
sampler_location,
|
|
|
|
sampler_location,
|
|
|
|
|
|
|
|
atlas_signature: 0,
|
|
|
|
|
|
|
|
row_cache: WebGlRowCache::default(),
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1330,7 +1474,7 @@ fn draw_glyph_into_atlas(
|
|
|
|
&frame.options.font_family,
|
|
|
|
&frame.options.font_family,
|
|
|
|
));
|
|
|
|
));
|
|
|
|
ctx.set_fill_style_str("#ffffff");
|
|
|
|
ctx.set_fill_style_str("#ffffff");
|
|
|
|
ctx.fill_text(&key.text, f64::from(entry.x), f64::from(entry.y))
|
|
|
|
ctx.fill_text(key.text.as_ref(), f64::from(entry.x), f64::from(entry.y))
|
|
|
|
.map_err(|_| "failed to draw glyph into atlas".to_owned())?;
|
|
|
|
.map_err(|_| "failed to draw glyph into atlas".to_owned())?;
|
|
|
|
ctx.restore();
|
|
|
|
ctx.restore();
|
|
|
|
stats.atlas_insertions += 1;
|
|
|
|
stats.atlas_insertions += 1;
|
|
|
|
@@ -1572,10 +1716,10 @@ mod tests {
|
|
|
|
let options = default_options();
|
|
|
|
let options = default_options();
|
|
|
|
let cells = split_segment_glyphs(&segment, 2, 4, &options).expect("glyph cells");
|
|
|
|
let cells = split_segment_glyphs(&segment, 2, 4, &options).expect("glyph cells");
|
|
|
|
assert_eq!(cells.len(), 2);
|
|
|
|
assert_eq!(cells.len(), 2);
|
|
|
|
assert_eq!(cells[0].key.text, "a");
|
|
|
|
assert_eq!(cells[0].key.text.as_ref(), "a");
|
|
|
|
assert_eq!(cells[0].key.cols, 1);
|
|
|
|
assert_eq!(cells[0].key.cols, 1);
|
|
|
|
assert_eq!(cells[0].col, 4);
|
|
|
|
assert_eq!(cells[0].col, 4);
|
|
|
|
assert_eq!(cells[1].key.text, "界");
|
|
|
|
assert_eq!(cells[1].key.text.as_ref(), "界");
|
|
|
|
assert_eq!(cells[1].key.cols, 2);
|
|
|
|
assert_eq!(cells[1].key.cols, 2);
|
|
|
|
assert_eq!(cells[1].col, 5);
|
|
|
|
assert_eq!(cells[1].col, 5);
|
|
|
|
assert_eq!(cells[0].fg, [128.0 / 255.0, 1.0, 0.0, 1.0]);
|
|
|
|
assert_eq!(cells[0].fg, [128.0 / 255.0, 1.0, 0.0, 1.0]);
|
|
|
|
@@ -1603,11 +1747,78 @@ mod tests {
|
|
|
|
assert_eq!(underline[0].fg, [128.0 / 255.0, 1.0, 0.0, 1.0]);
|
|
|
|
assert_eq!(underline[0].fg, [128.0 / 255.0, 1.0, 0.0, 1.0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn row_glyph_cache_reuses_matching_rows_and_invalidates_theme() {
|
|
|
|
|
|
|
|
let row = TerminalRow {
|
|
|
|
|
|
|
|
segments: vec![TerminalSegment {
|
|
|
|
|
|
|
|
text: "bench-00001 abc".to_owned(),
|
|
|
|
|
|
|
|
cols: 15,
|
|
|
|
|
|
|
|
style: TerminalStyle {
|
|
|
|
|
|
|
|
color: CANVAS_FG.to_owned(),
|
|
|
|
|
|
|
|
..TerminalStyle::default()
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
is_cursor: false,
|
|
|
|
|
|
|
|
}],
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut options = default_options();
|
|
|
|
|
|
|
|
let mut cache = WebGlRowCache::default();
|
|
|
|
|
|
|
|
let mut stats = WebGlRowCacheStats::default();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let first = cache
|
|
|
|
|
|
|
|
.glyphs_for_row(&row, &options, &mut stats)
|
|
|
|
|
|
|
|
.expect("first row")
|
|
|
|
|
|
|
|
.to_vec();
|
|
|
|
|
|
|
|
assert_eq!(cache.glyph_rows.len(), 1);
|
|
|
|
|
|
|
|
let second = cache
|
|
|
|
|
|
|
|
.glyphs_for_row(&row, &options, &mut stats)
|
|
|
|
|
|
|
|
.expect("cached row")
|
|
|
|
|
|
|
|
.to_vec();
|
|
|
|
|
|
|
|
assert_eq!(cache.glyph_rows.len(), 1);
|
|
|
|
|
|
|
|
assert_eq!(first, second);
|
|
|
|
|
|
|
|
assert_eq!(stats.misses, 1);
|
|
|
|
|
|
|
|
assert_eq!(stats.hits, 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
options.theme.foreground = "#ffffff".to_owned();
|
|
|
|
|
|
|
|
let changed = cache
|
|
|
|
|
|
|
|
.glyphs_for_row(&row, &options, &mut stats)
|
|
|
|
|
|
|
|
.expect("theme changed row")
|
|
|
|
|
|
|
|
.to_vec();
|
|
|
|
|
|
|
|
assert_eq!(cache.glyph_rows.len(), 2);
|
|
|
|
|
|
|
|
assert_ne!(first[0].fg, changed[0].fg);
|
|
|
|
|
|
|
|
assert_eq!(stats.misses, 2);
|
|
|
|
|
|
|
|
assert_eq!(stats.hits, 1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn row_glyph_cache_materializes_rows_at_current_positions() {
|
|
|
|
|
|
|
|
let row = TerminalRow {
|
|
|
|
|
|
|
|
segments: vec![TerminalSegment {
|
|
|
|
|
|
|
|
text: "ab".to_owned(),
|
|
|
|
|
|
|
|
cols: 2,
|
|
|
|
|
|
|
|
style: TerminalStyle::default(),
|
|
|
|
|
|
|
|
is_cursor: false,
|
|
|
|
|
|
|
|
}],
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
let options = default_options();
|
|
|
|
|
|
|
|
let mut cache = WebGlRowCache::default();
|
|
|
|
|
|
|
|
let rows = vec![row.clone(), row];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let (cells, stats) = collect_glyph_cells(&rows, &options, &mut cache).expect("cells");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(cells.len(), 4);
|
|
|
|
|
|
|
|
assert_eq!(cells[0].row, 0);
|
|
|
|
|
|
|
|
assert_eq!(cells[1].row, 0);
|
|
|
|
|
|
|
|
assert_eq!(cells[2].row, 1);
|
|
|
|
|
|
|
|
assert_eq!(cells[3].row, 1);
|
|
|
|
|
|
|
|
assert_eq!(cache.glyph_rows.len(), 1);
|
|
|
|
|
|
|
|
assert_eq!(stats, WebGlRowCacheStats { hits: 1, misses: 1 });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn glyph_atlas_reserves_rows_and_reuses_entries() {
|
|
|
|
fn glyph_atlas_reserves_rows_and_reuses_entries() {
|
|
|
|
let mut atlas = GlyphAtlas::new(32, 1);
|
|
|
|
let mut atlas = GlyphAtlas::new(32, 1);
|
|
|
|
let key_a = GlyphKey {
|
|
|
|
let key_a = GlyphKey {
|
|
|
|
text: "a".to_owned(),
|
|
|
|
text: Rc::<str>::from("a"),
|
|
|
|
bold: false,
|
|
|
|
bold: false,
|
|
|
|
italic: false,
|
|
|
|
italic: false,
|
|
|
|
cols: 1,
|
|
|
|
cols: 1,
|
|
|
|
@@ -1617,7 +1828,7 @@ mod tests {
|
|
|
|
assert_eq!(first, reused);
|
|
|
|
assert_eq!(first, reused);
|
|
|
|
|
|
|
|
|
|
|
|
let key_b = GlyphKey {
|
|
|
|
let key_b = GlyphKey {
|
|
|
|
text: "b".to_owned(),
|
|
|
|
text: Rc::<str>::from("b"),
|
|
|
|
bold: false,
|
|
|
|
bold: false,
|
|
|
|
italic: false,
|
|
|
|
italic: false,
|
|
|
|
cols: 1,
|
|
|
|
cols: 1,
|
|
|
|
|