1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 13:21:38 +03:00

try scaling down images that don't fit in the texture atlas

When trying to display a 4k image there is a high chance that
we'll run out of texture space and then render with no images
displayed.

This commit changes the binary yes/no allow-images into a a series
of attempts: display at natural size, scale down by 2, 4, then 8,
then give up on images.

While looking at this, I noticed that we had a TOCTOU in the blob lease
stuff in the case where we might very quickly try the handle the same
image in succession, and end up deleting a file out from under a live
lease.

I've put in place a simple bandaid for that, but it's probably worth
revisiting the concurrency model for that.
This commit is contained in:
Wez Furlong 2023-05-19 08:57:38 -07:00
parent 134bf0a74d
commit 4ae176dd8a
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
10 changed files with 195 additions and 117 deletions

View File

@ -9,7 +9,7 @@ pub enum Error {
#[error("Content with id {0} not found")] #[error("Content with id {0} not found")]
ContentNotFound(ContentId), ContentNotFound(ContentId),
#[error(transparent)] #[error("Io error in BlobLease: {0}")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("Storage has already been initialized")] #[error("Storage has already been initialized")]

View File

@ -44,6 +44,7 @@ impl SimpleTempDir {
eprintln!("Failed to remove {}: {err:#}", path.display()); eprintln!("Failed to remove {}: {err:#}", path.display());
} }
} }
*count = 0;
} }
Some(count) => { Some(count) => {
*count -= 1; *count -= 1;
@ -57,6 +58,8 @@ impl SimpleTempDir {
impl BlobStorage for SimpleTempDir { impl BlobStorage for SimpleTempDir {
fn store(&self, content_id: ContentId, data: &[u8], _lease_id: LeaseId) -> Result<(), Error> { fn store(&self, content_id: ContentId, data: &[u8], _lease_id: LeaseId) -> Result<(), Error> {
let mut refs = self.refs.lock().unwrap();
let path = self.path_for_content(content_id)?; let path = self.path_for_content(content_id)?;
let mut file = tempfile::Builder::new() let mut file = tempfile::Builder::new()
.prefix("new-") .prefix("new-")
@ -67,12 +70,14 @@ impl BlobStorage for SimpleTempDir {
file.persist(&path) file.persist(&path)
.map_err(|persist_err| persist_err.error)?; .map_err(|persist_err| persist_err.error)?;
self.add_ref(content_id); *refs.entry(content_id).or_insert(0) += 1;
Ok(()) Ok(())
} }
fn lease_by_content(&self, content_id: ContentId, _lease_id: LeaseId) -> Result<(), Error> { fn lease_by_content(&self, content_id: ContentId, _lease_id: LeaseId) -> Result<(), Error> {
let _refs = self.refs.lock().unwrap();
let path = self.path_for_content(content_id)?; let path = self.path_for_content(content_id)?;
if path.exists() { if path.exists() {
self.add_ref(content_id); self.add_ref(content_id);
@ -83,6 +88,8 @@ impl BlobStorage for SimpleTempDir {
} }
fn get_data(&self, content_id: ContentId, _lease_id: LeaseId) -> Result<Vec<u8>, Error> { fn get_data(&self, content_id: ContentId, _lease_id: LeaseId) -> Result<Vec<u8>, Error> {
let _refs = self.refs.lock().unwrap();
let path = self.path_for_content(content_id)?; let path = self.path_for_content(content_id)?;
Ok(std::fs::read(&path)?) Ok(std::fs::read(&path)?)
} }

View File

@ -1,6 +1,7 @@
use super::utilsprites::RenderMetrics; use super::utilsprites::RenderMetrics;
use crate::customglyph::*; use crate::customglyph::*;
use crate::renderstate::RenderContext; use crate::renderstate::RenderContext;
use crate::termwindow::render::paint::AllowImage;
use ::window::bitmaps::atlas::{Atlas, OutOfTextureSpace, Sprite}; use ::window::bitmaps::atlas::{Atlas, OutOfTextureSpace, Sprite};
use ::window::bitmaps::{BitmapImage, Image, ImageTexture, Texture2d}; use ::window::bitmaps::{BitmapImage, Image, ImageTexture, Texture2d};
use ::window::color::SrgbaPixel; use ::window::color::SrgbaPixel;
@ -885,17 +886,26 @@ impl GlyphCache {
decoded: &DecodedImage, decoded: &DecodedImage,
padding: Option<usize>, padding: Option<usize>,
min_frame_duration: Duration, min_frame_duration: Duration,
allow_image: AllowImage,
) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> { ) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> {
let mut handle = DecodedImageHandle { let mut handle = DecodedImageHandle {
h: decoded.image.data(), h: decoded.image.data(),
current_frame: *decoded.current_frame.borrow(), current_frame: *decoded.current_frame.borrow(),
}; };
let scale_down = match allow_image {
AllowImage::Scale(n) => Some(n),
_ => None,
};
match &*handle.h { match &*handle.h {
ImageDataType::Rgba8 { hash, .. } => { ImageDataType::Rgba8 { hash, .. } => {
if let Some(sprite) = frame_cache.get(hash) { if let Some(sprite) = frame_cache.get(hash) {
return Ok((sprite.clone(), None, LoadState::Loaded)); return Ok((sprite.clone(), None, LoadState::Loaded));
} }
let sprite = atlas.allocate_with_padding(&handle, padding)?; let sprite = atlas
.allocate_with_padding(&handle, padding, scale_down)
.context("atlas.allocate_with_padding")?;
frame_cache.insert(*hash, sprite.clone()); frame_cache.insert(*hash, sprite.clone());
return Ok((sprite, None, LoadState::Loaded)); return Ok((sprite, None, LoadState::Loaded));
@ -949,7 +959,9 @@ impl GlyphCache {
return Ok((sprite.clone(), next, LoadState::Loaded)); return Ok((sprite.clone(), next, LoadState::Loaded));
} }
let sprite = atlas.allocate_with_padding(&handle, padding)?; let sprite = atlas
.allocate_with_padding(&handle, padding, scale_down)
.context("atlas.allocate_with_padding")?;
frame_cache.insert(hash, sprite.clone()); frame_cache.insert(hash, sprite.clone());
@ -1005,9 +1017,13 @@ impl GlyphCache {
let frame = Image::from_raw( let frame = Image::from_raw(
frames.current_frame.width, frames.current_frame.width,
frames.current_frame.height, frames.current_frame.height,
frames.current_frame.lease.get_data()?, frames
.current_frame
.lease
.get_data()
.context("frames.current_frame.lease.get_data")?,
); );
let sprite = atlas.allocate_with_padding(&frame, padding)?; let sprite = atlas.allocate_with_padding(&frame, padding, scale_down)?;
frame_cache.insert(hash, sprite.clone()); frame_cache.insert(hash, sprite.clone());
@ -1024,6 +1040,7 @@ impl GlyphCache {
&mut self, &mut self,
image_data: &Arc<ImageData>, image_data: &Arc<ImageData>,
padding: Option<usize>, padding: Option<usize>,
allow_image: AllowImage,
) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> { ) -> anyhow::Result<(Sprite, Option<Instant>, LoadState)> {
let hash = image_data.hash(); let hash = image_data.hash();
@ -1034,6 +1051,7 @@ impl GlyphCache {
decoded, decoded,
padding, padding,
self.min_frame_duration, self.min_frame_duration,
allow_image,
) )
} else { } else {
let decoded = DecodedImage::load(image_data); let decoded = DecodedImage::load(image_data);
@ -1043,6 +1061,7 @@ impl GlyphCache {
&decoded, &decoded,
padding, padding,
self.min_frame_duration, self.min_frame_duration,
allow_image,
)?; )?;
self.image_cache.put(hash, decoded); self.image_cache.put(hash, decoded);
Ok(res) Ok(res)

View File

@ -425,10 +425,11 @@ impl crate::TermWindow {
let color = bg_color.mul_alpha(layer.def.opacity); let color = bg_color.mul_alpha(layer.def.opacity);
let (sprite, next_due, load_state) = gl_state let (sprite, next_due, load_state) = gl_state.glyph_cache.borrow_mut().cached_image(
.glyph_cache &layer.source,
.borrow_mut() None,
.cached_image(&layer.source, None)?; self.allow_images,
)?;
self.update_next_frame_time(next_due); self.update_next_frame_time(next_due);
if load_state == LoadState::Loading { if load_state == LoadState::Loading {

View File

@ -19,6 +19,7 @@ use crate::termwindow::background::{
}; };
use crate::termwindow::keyevent::{KeyTableArgs, KeyTableState}; use crate::termwindow::keyevent::{KeyTableArgs, KeyTableState};
use crate::termwindow::modal::Modal; use crate::termwindow::modal::Modal;
use crate::termwindow::render::paint::AllowImage;
use crate::termwindow::render::{ use crate::termwindow::render::{
CachedLineState, LineQuadCacheKey, LineQuadCacheValue, LineToEleShapeCacheKey, CachedLineState, LineQuadCacheKey, LineQuadCacheValue, LineToEleShapeCacheKey,
LineToElementShapeItem, LineToElementShapeItem,
@ -73,7 +74,7 @@ mod mouseevent;
pub mod palette; pub mod palette;
pub mod paneselect; pub mod paneselect;
mod prevcursor; mod prevcursor;
mod render; pub mod render;
pub mod resize; pub mod resize;
mod selection; mod selection;
pub mod spawn; pub mod spawn;
@ -434,7 +435,7 @@ pub struct TermWindow {
has_animation: RefCell<Option<Instant>>, has_animation: RefCell<Option<Instant>>,
/// We use this to attempt to do something reasonable /// We use this to attempt to do something reasonable
/// if we run out of texture space /// if we run out of texture space
allow_images: bool, allow_images: AllowImage,
scheduled_animation: RefCell<Option<Instant>>, scheduled_animation: RefCell<Option<Instant>>,
created: Instant, created: Instant,
@ -762,7 +763,7 @@ impl TermWindow {
current_event: None, current_event: None,
has_animation: RefCell::new(None), has_animation: RefCell::new(None),
scheduled_animation: RefCell::new(None), scheduled_animation: RefCell::new(None),
allow_images: true, allow_images: AllowImage::Yes,
semantic_zones: HashMap::new(), semantic_zones: HashMap::new(),
ui_items: vec![], ui_items: vec![],
dragging: None, dragging: None,

View File

@ -6,11 +6,12 @@ use crate::quad::{
TripleLayerQuadAllocatorTrait, TripleLayerQuadAllocatorTrait,
}; };
use crate::shapecache::*; use crate::shapecache::*;
use crate::termwindow::render::paint::AllowImage;
use crate::termwindow::{BorrowedShapeCacheKey, RenderState, ShapedInfo, TermWindowNotif}; use crate::termwindow::{BorrowedShapeCacheKey, RenderState, ShapedInfo, TermWindowNotif};
use crate::utilsprites::RenderMetrics; use crate::utilsprites::RenderMetrics;
use ::window::bitmaps::{TextureCoord, TextureRect, TextureSize}; use ::window::bitmaps::{TextureCoord, TextureRect, TextureSize};
use ::window::{DeadKeyStatus, PointF, RectF, SizeF, WindowOps}; use ::window::{DeadKeyStatus, PointF, RectF, SizeF, WindowOps};
use anyhow::anyhow; use anyhow::{anyhow, Context};
use config::{BoldBrightening, ConfigHandle, DimensionContext, TextStyle, VisualBellTarget}; use config::{BoldBrightening, ConfigHandle, DimensionContext, TextStyle, VisualBellTarget};
use euclid::num::Zero; use euclid::num::Zero;
use mux::pane::{Pane, PaneId}; use mux::pane::{Pane, PaneId};
@ -417,7 +418,7 @@ impl crate::TermWindow {
hsv: Option<config::HsbTransform>, hsv: Option<config::HsbTransform>,
glyph_color: LinearRgba, glyph_color: LinearRgba,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if !self.allow_images { if self.allow_images == AllowImage::No {
return Ok(()); return Ok(());
} }
@ -435,7 +436,8 @@ impl crate::TermWindow {
let (sprite, next_due, _load_state) = gl_state let (sprite, next_due, _load_state) = gl_state
.glyph_cache .glyph_cache
.borrow_mut() .borrow_mut()
.cached_image(image.image_data(), Some(padding))?; .cached_image(image.image_data(), Some(padding), self.allow_images)
.context("cached_image")?;
self.update_next_frame_time(next_due); self.update_next_frame_time(next_due);
let width = sprite.coords.size.width; let width = sprite.coords.size.width;
let height = sprite.coords.size.height; let height = sprite.coords.size.height;

View File

@ -6,6 +6,13 @@ use smol::Timer;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use wezterm_font::ClearShapeCache; use wezterm_font::ClearShapeCache;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllowImage {
Yes,
Scale(usize),
No,
}
impl crate::TermWindow { impl crate::TermWindow {
pub fn paint_impl(&mut self, frame: &mut RenderFrame) { pub fn paint_impl(&mut self, frame: &mut RenderFrame) {
self.num_frames += 1; self.num_frames += 1;
@ -13,7 +20,7 @@ impl crate::TermWindow {
// invalidating as frequently // invalidating as frequently
*self.has_animation.borrow_mut() = None; *self.has_animation.borrow_mut() = None;
// Start with the assumption that we should allow images to render // Start with the assumption that we should allow images to render
self.allow_images = true; self.allow_images = AllowImage::Yes;
let start = Instant::now(); let start = Instant::now();
@ -61,14 +68,12 @@ impl crate::TermWindow {
self.invalidate_modal(); self.invalidate_modal();
if let Err(err) = result { if let Err(err) = result {
if self.allow_images { self.allow_images = match self.allow_images {
self.allow_images = false; AllowImage::Yes => AllowImage::Scale(2),
log::info!( AllowImage::Scale(2) => AllowImage::Scale(4),
"Not enough texture space ({:#}); \ AllowImage::Scale(4) => AllowImage::Scale(8),
will retry render with images disabled", AllowImage::Scale(8) => AllowImage::No,
err AllowImage::No | _ => {
);
} else {
log::error!( log::error!(
"Failed to {} texture: {}", "Failed to {} texture: {}",
if pass == 0 { "clear" } else { "resize" }, if pass == 0 { "clear" } else { "resize" },
@ -76,6 +81,14 @@ impl crate::TermWindow {
); );
break 'pass; break 'pass;
} }
};
log::info!(
"Not enough texture space ({:#}); \
will retry render with {:?}",
err,
self.allow_images,
);
} }
} else if err.root_cause().downcast_ref::<ClearShapeCache>().is_some() { } else if err.root_cause().downcast_ref::<ClearShapeCache>().is_some() {
self.invalidate_fancy_tab_bar(); self.invalidate_fancy_tab_bar();
@ -175,7 +188,7 @@ impl crate::TermWindow {
// Render the full window background // Render the full window background
match (self.window_background.is_empty(), self.allow_images) { match (self.window_background.is_empty(), self.allow_images) {
(false, true) => { (false, AllowImage::Yes) => {
let bg_color = self.palette().background.to_linear(); let bg_color = self.palette().background.to_linear();
let top = panes let top = panes

View File

@ -8,6 +8,7 @@ use crate::termwindow::render::{
use crate::termwindow::{ScrollHit, UIItem, UIItemType}; use crate::termwindow::{ScrollHit, UIItem, UIItemType};
use ::window::bitmaps::TextureRect; use ::window::bitmaps::TextureRect;
use ::window::DeadKeyStatus; use ::window::DeadKeyStatus;
use anyhow::Context;
use config::VisualBellTarget; use config::VisualBellTarget;
use mux::pane::{PaneId, WithPaneLines}; use mux::pane::{PaneId, WithPaneLines};
use mux::renderable::{RenderableDimensions, StableCursorPosition}; use mux::renderable::{RenderableDimensions, StableCursorPosition};
@ -62,7 +63,8 @@ impl crate::TermWindow {
let (padding_left, padding_top) = self.padding_left_top(); let (padding_left, padding_top) = self.padding_left_top();
let tab_bar_height = if self.show_tab_bar { let tab_bar_height = if self.show_tab_bar {
self.tab_bar_pixel_height()? self.tab_bar_pixel_height()
.context("tab_bar_pixel_height")?
} else { } else {
0. 0.
}; };
@ -152,7 +154,8 @@ impl crate::TermWindow {
if self.window_background.is_empty() { if self.window_background.is_empty() {
// Per-pane, palette-specified background // Per-pane, palette-specified background
let mut quad = self.filled_rectangle( let mut quad = self
.filled_rectangle(
layers, layers,
0, 0,
background_rect, background_rect,
@ -160,7 +163,8 @@ impl crate::TermWindow {
.background .background
.to_linear() .to_linear()
.mul_alpha(config.window_background_opacity), .mul_alpha(config.window_background_opacity),
)?; )
.context("filled_rectangle")?;
quad.set_hsv(if pos.is_active { quad.set_hsv(if pos.is_active {
None None
} else { } else {
@ -205,7 +209,9 @@ impl crate::TermWindow {
}; };
log::trace!("bell color is {:?}", background); log::trace!("bell color is {:?}", background);
let mut quad = self.filled_rectangle(layers, 0, background_rect, background)?; let mut quad = self
.filled_rectangle(layers, 0, background_rect, background)
.context("filled_rectangle")?;
quad.set_hsv(if pos.is_active { quad.set_hsv(if pos.is_active {
None None
@ -279,7 +285,8 @@ impl crate::TermWindow {
thumb_size as f32, thumb_size as f32,
), ),
color, color,
)?; )
.context("filled_rectangle")?;
} }
let (selrange, rectangular) = { let (selrange, rectangular) = {
@ -451,7 +458,10 @@ impl crate::TermWindow {
false false
}; };
if !expired && !hover_changed { if !expired && !hover_changed {
cached_quad.layers.apply_to(self.layers)?; cached_quad
.layers
.apply_to(self.layers)
.context("cached_quad.layers.apply_to")?;
self.term_window.update_next_frame_time(cached_quad.expires); self.term_window.update_next_frame_time(cached_quad.expires);
return Ok(()); return Ok(());
} }
@ -476,7 +486,9 @@ impl crate::TermWindow {
}, },
}; };
let render_result = self.term_window.render_screen_line( let render_result = self
.term_window
.render_screen_line(
RenderScreenLineParams { RenderScreenLineParams {
top_pixel_y: *quad_key.top_pixel_y, top_pixel_y: *quad_key.top_pixel_y,
left_pixel_x: self.left_pixel_x, left_pixel_x: self.left_pixel_x,
@ -513,12 +525,14 @@ impl crate::TermWindow {
password_input, password_input,
}, },
&mut TripleLayerQuadAllocator::Heap(&mut buf), &mut TripleLayerQuadAllocator::Heap(&mut buf),
)?; )
.context("render_screen_line")?;
let expires = self.term_window.has_animation.borrow().as_ref().cloned(); let expires = self.term_window.has_animation.borrow().as_ref().cloned();
self.term_window.update_next_frame_time(next_due); self.term_window.update_next_frame_time(next_due);
buf.apply_to(self.layers)?; buf.apply_to(self.layers)
.context("HeapQuadAllocator::apply_to")?;
let quad_value = LineQuadCacheValue { let quad_value = LineQuadCacheValue {
layers: buf, layers: buf,
@ -554,7 +568,7 @@ impl crate::TermWindow {
pos.pane.with_lines_mut(stable_range.clone(), &mut render); pos.pane.with_lines_mut(stable_range.clone(), &mut render);
if let Some(error) = render.error.take() { if let Some(error) = render.error.take() {
return Err(error); return Err(error).context("error while calling with_lines_mut");
} }
} }

View File

@ -6,6 +6,7 @@ use crate::termwindow::render::{
}; };
use crate::termwindow::LineToElementShapeItem; use crate::termwindow::LineToElementShapeItem;
use ::window::DeadKeyStatus; use ::window::DeadKeyStatus;
use anyhow::Context;
use config::{HsbTransform, TextStyle}; use config::{HsbTransform, TextStyle};
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
@ -170,7 +171,8 @@ impl crate::TermWindow {
} }
if params.dims.reverse_video { if params.dims.reverse_video {
let mut quad = self.filled_rectangle( let mut quad = self
.filled_rectangle(
layers, layers,
0, 0,
euclid::rect( euclid::rect(
@ -180,7 +182,8 @@ impl crate::TermWindow {
cell_height, cell_height,
), ),
params.foreground, params.foreground,
)?; )
.context("filled_rectangle")?;
quad.set_hsv(hsv); quad.set_hsv(hsv);
} }
@ -248,7 +251,9 @@ impl crate::TermWindow {
let rect = euclid::rect(x, params.top_pixel_y, width, cell_height); let rect = euclid::rect(x, params.top_pixel_y, width, cell_height);
if let Some(rect) = rect.intersection(&bounding_rect) { if let Some(rect) = rect.intersection(&bounding_rect) {
let mut quad = self.filled_rectangle(layers, 0, rect, bg_color)?; let mut quad = self
.filled_rectangle(layers, 0, rect, bg_color)
.context("filled_rectangle")?;
quad.set_hsv(hsv); quad.set_hsv(hsv);
} }
} }
@ -258,7 +263,7 @@ impl crate::TermWindow {
// Draw one per cell, otherwise curly underlines // Draw one per cell, otherwise curly underlines
// stretch across the whole span // stretch across the whole span
for i in 0..cluster_width { for i in 0..cluster_width {
let mut quad = layers.allocate(0)?; let mut quad = layers.allocate(0).context("layers.allocate(0)")?;
let x = gl_x let x = gl_x
+ params.left_pixel_x + params.left_pixel_x
+ if params.use_pixel_positioning { + if params.use_pixel_positioning {
@ -283,12 +288,14 @@ impl crate::TermWindow {
let selection_pixel_range = if !params.selection.is_empty() { let selection_pixel_range = if !params.selection.is_empty() {
let start = params.left_pixel_x + (params.selection.start as f32 * cell_width); let start = params.left_pixel_x + (params.selection.start as f32 * cell_width);
let width = (params.selection.end - params.selection.start) as f32 * cell_width; let width = (params.selection.end - params.selection.start) as f32 * cell_width;
let mut quad = self.filled_rectangle( let mut quad = self
.filled_rectangle(
layers, layers,
0, 0,
euclid::rect(start, params.top_pixel_y, width, cell_height), euclid::rect(start, params.top_pixel_y, width, cell_height),
params.selection_bg, params.selection_bg,
)?; )
.context("filled_rectangle")?;
quad.set_hsv(hsv); quad.set_hsv(hsv);
@ -343,7 +350,7 @@ impl crate::TermWindow {
+ (phys(params.cursor.x, num_cols, direction) as f32 * cell_width); + (phys(params.cursor.x, num_cols, direction) as f32 * cell_width);
if cursor_shape.is_some() { if cursor_shape.is_some() {
let mut quad = layers.allocate(0)?; let mut quad = layers.allocate(0).context("layers.allocate(0)")?;
quad.set_hsv(hsv); quad.set_hsv(hsv);
quad.set_has_color(false); quad.set_has_color(false);
@ -355,13 +362,15 @@ impl crate::TermWindow {
.map(|cell| cell.attrs().clone()) .map(|cell| cell.attrs().clone())
.unwrap_or_else(|| CellAttributes::blank()); .unwrap_or_else(|| CellAttributes::blank());
let glyph = self.resolve_lock_glyph( let glyph = self
.resolve_lock_glyph(
&TextStyle::default(), &TextStyle::default(),
&attrs, &attrs,
params.font.as_ref(), params.font.as_ref(),
gl_state, gl_state,
&params.render_metrics, &params.render_metrics,
)?; )
.context("resolve_lock_glyph")?;
if let Some(sprite) = &glyph.texture { if let Some(sprite) = &glyph.texture {
let width = sprite.coords.size.width as f32 * glyph.scale as f32; let width = sprite.coords.size.width as f32 * glyph.scale as f32;
@ -486,7 +495,8 @@ impl crate::TermWindow {
gl_state gl_state
.glyph_cache .glyph_cache
.borrow_mut() .borrow_mut()
.cached_block(*block, &params.render_metrics)?, .cached_block(*block, &params.render_metrics)
.context("cached_block")?,
); );
// Custom glyphs don't have the same offsets as computed // Custom glyphs don't have the same offsets as computed
// by the shaper, and are rendered relative to the cell // by the shaper, and are rendered relative to the cell
@ -624,7 +634,7 @@ impl crate::TermWindow {
let texture_rect = texture.texture.to_texture_coords(pixel_rect); let texture_rect = texture.texture.to_texture_coords(pixel_rect);
let mut quad = layers.allocate(1)?; let mut quad = layers.allocate(1).context("layers.allocate(1)")?;
quad.set_position( quad.set_position(
gl_x + range.start, gl_x + range.start,
pos_y + top, pos_y + top,
@ -691,7 +701,8 @@ impl crate::TermWindow {
&params, &params,
hsv, hsv,
glyph_color, glyph_color,
)?; )
.context("populate_image_quad")?;
} }
metrics::histogram!("render_screen_line", start.elapsed()); metrics::histogram!("render_screen_line", start.elapsed());

View File

@ -56,16 +56,26 @@ impl Atlas {
/// Reserve space for a sprite of the given size /// Reserve space for a sprite of the given size
pub fn allocate(&mut self, im: &dyn BitmapImage) -> Result<Sprite, OutOfTextureSpace> { pub fn allocate(&mut self, im: &dyn BitmapImage) -> Result<Sprite, OutOfTextureSpace> {
self.allocate_with_padding(im, None) self.allocate_with_padding(im, None, None)
} }
pub fn allocate_with_padding( pub fn allocate_with_padding(
&mut self, &mut self,
im: &dyn BitmapImage, im: &dyn BitmapImage,
padding: Option<usize>, padding: Option<usize>,
scale_down: Option<usize>,
) -> Result<Sprite, OutOfTextureSpace> { ) -> Result<Sprite, OutOfTextureSpace> {
let (width, height) = im.image_dimensions(); let (width, height) = im.image_dimensions();
if let Some(scale_down) = scale_down {
let mut copied = crate::Image::new(width, height);
copied.draw_image(Point::new(0, 0), None, im);
let scaled = copied.resize(width / scale_down, height / scale_down);
return self.allocate_with_padding(&scaled, padding, None);
}
// If we can't convert the sizes to i32, then we'll never // If we can't convert the sizes to i32, then we'll never
// be able to store this image // be able to store this image
let reserve_width: i32 = width.try_into().map_err(|_| OutOfTextureSpace { let reserve_width: i32 = width.try_into().map_err(|_| OutOfTextureSpace {