first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -1,40 +1,42 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use core::fmt;
|
||||
use itertools::Itertools;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
error,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
rc::Rc,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use strum_macros::EnumIter;
|
||||
use std::collections::HashMap;
|
||||
use std::error;
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use crate::{
|
||||
assets::asset_cache::{Asset, AssetCache, AssetSource, AssetState},
|
||||
util::parse_u32,
|
||||
Entity, SingletonEntity,
|
||||
};
|
||||
use image::{
|
||||
codecs::{gif::GifDecoder, webp::WebPDecoder},
|
||||
imageops::FilterType,
|
||||
AnimationDecoder, DynamicImage, Frame, ImageBuffer, ImageFormat,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use image::codecs::gif::GifDecoder;
|
||||
use image::codecs::webp::WebPDecoder;
|
||||
use image::imageops::FilterType;
|
||||
use image::{AnimationDecoder, DynamicImage, Frame, ImageBuffer, ImageFormat};
|
||||
use itertools::Itertools;
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
use pathfinder_geometry::vector::Vector2I;
|
||||
use resvg::{
|
||||
tiny_skia::{self, IntSize},
|
||||
usvg,
|
||||
};
|
||||
use resvg::tiny_skia::{self, IntSize};
|
||||
use resvg::usvg;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
use crate::assets::asset_cache::{Asset, AssetCache, AssetSource, AssetState};
|
||||
use crate::util::parse_u32;
|
||||
use crate::{Entity, SingletonEntity};
|
||||
|
||||
const MIN_REFRESH_DELAY_MS: u32 = 50;
|
||||
|
||||
static SVG_FONT_DB: LazyLock<Arc<usvg::fontdb::Database>> = LazyLock::new(|| {
|
||||
let mut fontdb = usvg::fontdb::Database::new();
|
||||
fontdb.load_system_fonts();
|
||||
load_svg_fallback_fonts(&mut fontdb);
|
||||
Arc::new(fontdb)
|
||||
});
|
||||
|
||||
fn load_svg_fallback_fonts(fontdb: &mut usvg::fontdb::Database) {
|
||||
fontdb.load_font_data(
|
||||
include_bytes!("../../../app/assets/bundled/fonts/roboto/Roboto-Regular.ttf").to_vec(),
|
||||
);
|
||||
fontdb.set_sans_serif_family("Roboto");
|
||||
}
|
||||
|
||||
pub fn prewarm_svg_font_db() {
|
||||
LazyLock::force(&SVG_FONT_DB);
|
||||
}
|
||||
@@ -791,14 +793,16 @@ impl FitTo {
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
struct RenderedImageCacheKey {
|
||||
bounds: Vector2I,
|
||||
fit_type: FitType,
|
||||
animated_image_behavior: AnimatedImageBehavior,
|
||||
}
|
||||
type RenderedImageCache = HashMap<u64, HashMap<RenderedImageCacheKey, Rc<Image>>>;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ImageCache {
|
||||
/// Map of images of any ImageType already scaled to a certain size.
|
||||
/// Map of rendered images of any ImageType already materialized for a certain size and fit.
|
||||
/// Uses the hashed AssetSource and rendered-image properties as a key.
|
||||
images: RwLock<HashMap<u64, HashMap<RenderedImageCacheKey, Rc<Image>>>>,
|
||||
images: Rc<RwLock<RenderedImageCache>>,
|
||||
}
|
||||
|
||||
impl ImageCache {
|
||||
@@ -816,16 +820,16 @@ impl ImageCache {
|
||||
cache.remove(&cache_key);
|
||||
}
|
||||
|
||||
/// Removes a single cached size entry for an asset.
|
||||
/// Removes a single cached rendered entry for an asset.
|
||||
///
|
||||
/// When the removed `Rc<Image>` is the last strong holder of the inner
|
||||
/// `Arc<StaticImage>`, that `Arc`'s strong count drops to zero. On the
|
||||
/// next call to `TextureCache::end_frame()`, the corresponding GPU texture
|
||||
/// will be evicted automatically via the `Weak<StaticImage>` it holds.
|
||||
///
|
||||
/// `bounds` must match the resolved bounds used as the cache key inside
|
||||
/// `image()` (i.e., after any `max_dimension` adjustment), not the
|
||||
/// originally requested bounds.
|
||||
/// `bounds` and `fit_type` must match the resolved values used as the
|
||||
/// cache key inside `image()` (i.e., after any `max_dimension`
|
||||
/// adjustment), not only the originally requested bounds.
|
||||
// Called by the debounce eviction pass added in the main changeset.
|
||||
/// TODO(APP-3877): remove `#[allow(dead_code)]` once the debounce eviction pass wires this up.
|
||||
#[allow(dead_code)]
|
||||
@@ -833,6 +837,7 @@ impl ImageCache {
|
||||
&self,
|
||||
asset_source: &AssetSource,
|
||||
bounds: Vector2I,
|
||||
fit_type: FitType,
|
||||
animated_image_behavior: AnimatedImageBehavior,
|
||||
) {
|
||||
let mut s = DefaultHasher::new();
|
||||
@@ -841,6 +846,7 @@ impl ImageCache {
|
||||
|
||||
let rendered_key = RenderedImageCacheKey {
|
||||
bounds,
|
||||
fit_type,
|
||||
animated_image_behavior,
|
||||
};
|
||||
|
||||
@@ -868,16 +874,15 @@ impl ImageCache {
|
||||
asset_source.hash(&mut s);
|
||||
let cache_key = s.finish();
|
||||
|
||||
match asset_cache.load_asset::<ImageType>(asset_source) {
|
||||
match asset_cache.load_asset::<ImageType>(asset_source.clone()) {
|
||||
AssetState::Loading { handle } => AssetState::Loading { handle },
|
||||
AssetState::Evicted => AssetState::Evicted,
|
||||
AssetState::FailedToLoad(err) => AssetState::FailedToLoad(err),
|
||||
AssetState::Loaded { data } => {
|
||||
let (mut needs_resize, mut bounds) = match cache_option {
|
||||
CacheOption::BySize => {
|
||||
// Only store a resized copy of the source asset if a
|
||||
// specific size was requested and it doesn't match the
|
||||
// source asset's size.
|
||||
// Only resize the source asset if a specific size was requested and it
|
||||
// does not match the source asset's size.
|
||||
let needs_resize = data.image_size() != Some(bounds);
|
||||
(needs_resize, bounds)
|
||||
}
|
||||
@@ -917,19 +922,28 @@ impl ImageCache {
|
||||
|
||||
let rendered_image_cache_key = RenderedImageCacheKey {
|
||||
bounds,
|
||||
fit_type,
|
||||
animated_image_behavior,
|
||||
};
|
||||
// SVG sources always have to be rasterized, even when rendered at their
|
||||
// intrinsic size. Cache those materialized pixels alongside resized images.
|
||||
let should_cache_rendered_image =
|
||||
needs_resize || matches!(data.as_ref(), ImageType::Svg { .. });
|
||||
|
||||
// If it's already in the image cache at the target size,
|
||||
// return it.
|
||||
let cache = self.images.upgradable_read();
|
||||
if let Some(inner_map) = cache.get(&cache_key) {
|
||||
if let Some(image) = inner_map.get(&rendered_image_cache_key) {
|
||||
return AssetState::Loaded {
|
||||
data: image.clone(),
|
||||
};
|
||||
// If it is already in the image cache at the target size and fit, return it.
|
||||
let cache = if should_cache_rendered_image {
|
||||
let cache = self.images.upgradable_read();
|
||||
if let Some(inner_map) = cache.get(&cache_key) {
|
||||
if let Some(image) = inner_map.get(&rendered_image_cache_key) {
|
||||
return AssetState::Loaded {
|
||||
data: image.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(cache)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Otherwise, create the correctly-sized image struct and
|
||||
// insert it into the cache (if necessary).
|
||||
@@ -938,7 +952,7 @@ impl ImageCache {
|
||||
Ok(image) => Rc::new(image),
|
||||
Err(err) => return AssetState::FailedToLoad(Rc::new(err)),
|
||||
};
|
||||
if needs_resize {
|
||||
if let Some(cache) = cache {
|
||||
let mut images_cache = RwLockUpgradableReadGuard::upgrade(cache);
|
||||
images_cache
|
||||
.entry(cache_key)
|
||||
@@ -950,6 +964,17 @@ impl ImageCache {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_size(
|
||||
&self,
|
||||
asset_source: AssetSource,
|
||||
asset_cache: &AssetCache,
|
||||
) -> Option<Vector2I> {
|
||||
let AssetState::Loaded { data } = asset_cache.load_asset::<ImageType>(asset_source) else {
|
||||
return None;
|
||||
};
|
||||
data.image_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for ImageCache {
|
||||
|
||||
Reference in New Issue
Block a user