diff --git a/dash-frontend/src/util/various.rs b/dash-frontend/src/util/various.rs index a67f52dd..a8eae182 100644 --- a/dash-frontend/src/util/various.rs +++ b/dash-frontend/src/util/various.rs @@ -4,7 +4,7 @@ use wgui::{ globals::WguiGlobals, i18n::Translation, layout::{Layout, WidgetID}, - renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData}, + renderer_vk::text::custom_glyph::CustomGlyphData, taffy::{self, prelude::length}, widget::{ label::{WidgetLabel, WidgetLabelParams}, @@ -62,7 +62,7 @@ pub fn mount_simple_sprite_square( layout.add_child( parent_id, WidgetSprite::create(WidgetSpriteParams { - glyph_data: Some(CustomGlyphData::new(CustomGlyphContent::from_assets(globals, path)?)), + glyph_data: Some(CustomGlyphData::from_assets(globals, path)?), ..Default::default() }), taffy::Style { diff --git a/dash-frontend/src/views/game_list.rs b/dash-frontend/src/views/game_list.rs index fa2fc45f..da1d7b21 100644 --- a/dash-frontend/src/views/game_list.rs +++ b/dash-frontend/src/views/game_list.rs @@ -14,7 +14,7 @@ use wgui::{ parser::{Fetchable, ParseDocumentParams, ParserState}, renderer_vk::text::{ FontWeight, HorizontalAlign, TextShadow, TextStyle, - custom_glyph::{CustomGlyphContent, CustomGlyphData}, + custom_glyph::{ CustomGlyphData}, }, taffy::{ self, AlignItems, AlignSelf, JustifyContent, JustifySelf, @@ -413,10 +413,10 @@ impl View { fn get_placeholder_image(&mut self) -> anyhow::Result<&CustomGlyphData> { if self.img_placeholder.is_none() { - let c = CustomGlyphData::new(CustomGlyphContent::from_assets( + let c = CustomGlyphData::from_assets( &self.globals, AssetPath::BuiltIn("dashboard/placeholder_cover.png"), - )?); + )?; self.img_placeholder = Some(c); } @@ -515,7 +515,8 @@ impl View { return Ok(()); }; - let glyph = match CustomGlyphContent::from_bin_raster(&cover_art.compressed_image_data) { + let path = format!("app:{app_id:?}"); + let glyph = match CustomGlyphData::from_bytes_raster(&self.globals, &path ,&cover_art.compressed_image_data) { Ok(c) => c, Err(e) => { log::warn!( @@ -526,7 +527,7 @@ impl View { return Ok(()); } }; - View::mount_image(layout, cell, &CustomGlyphData::new(glyph))?; + View::mount_image(layout, cell, &glyph)?; } Ok(()) diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index cb1f9248..8e29fe15 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -1,24 +1,22 @@ use crate::{ animation::{Animation, AnimationEasing}, assets::AssetPath, - components::{self, Component, ComponentBase, ComponentTrait, RefreshData, tooltip::ComponentTooltip}, + components::{self, tooltip::ComponentTooltip, Component, ComponentBase, ComponentTrait, RefreshData}, drawing::{self, Boundary, Color}, event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind}, i18n::Translation, layout::{LayoutTask, WidgetID, WidgetPair}, renderer_vk::{ - text::{ - FontWeight, TextStyle, - custom_glyph::{CustomGlyphContent, CustomGlyphData}, - }, + text::{custom_glyph::CustomGlyphData, FontWeight, TextStyle}, util::centered_matrix, }, widget::{ - self, ConstructEssentials, EventResult, WidgetData, + self, label::{WidgetLabel, WidgetLabelParams}, rectangle::{WidgetRectangle, WidgetRectangleParams}, sprite::{WidgetSprite, WidgetSpriteParams}, util::WLength, + ConstructEssentials, EventResult, WidgetData, }, }; use glam::{Mat4, Vec3}; @@ -27,7 +25,7 @@ use std::{ rc::Rc, time::{Duration, Instant}, }; -use taffy::{AlignItems, JustifyContent, prelude::length}; +use taffy::{prelude::length, AlignItems, JustifyContent}; pub struct Params<'a> { pub text: Option, // if unset, label will not be populated @@ -461,10 +459,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul if let Some(sprite_path) = params.sprite_src { let sprite = WidgetSprite::create(WidgetSpriteParams { - glyph_data: Some(CustomGlyphData::new(CustomGlyphContent::from_assets( - &globals, - sprite_path, - )?)), + glyph_data: Some(CustomGlyphData::from_assets(&globals, sprite_path)?), ..Default::default() }); diff --git a/wgui/src/globals.rs b/wgui/src/globals.rs index 82b2cabc..e9573dd2 100644 --- a/wgui/src/globals.rs +++ b/wgui/src/globals.rs @@ -12,6 +12,7 @@ use crate::{ assets_internal, drawing, font_config::{WguiFontConfig, WguiFontSystem}, i18n::I18n, + renderer_vk::text::custom_glyph::CustomGlyphCache, }; #[derive(Clone)] @@ -52,6 +53,7 @@ pub struct Globals { pub i18n_builtin: I18n, pub defaults: Defaults, pub font_system: WguiFontSystem, + pub custom_glyph_cache: CustomGlyphCache, } #[derive(Clone)] @@ -74,6 +76,7 @@ impl WguiGlobals { defaults, asset_folder, font_system: WguiFontSystem::new(font_config), + custom_glyph_cache: CustomGlyphCache::new(), })))) } diff --git a/wgui/src/parser/widget_image.rs b/wgui/src/parser/widget_image.rs index ff74006a..89d32a3f 100644 --- a/wgui/src/parser/widget_image.rs +++ b/wgui/src/parser/widget_image.rs @@ -2,10 +2,11 @@ use crate::{ assets::AssetPath, layout::WidgetID, parser::{ - AttribPair, ParserContext, ParserFile, parse_children, parse_widget_universal, print_invalid_attrib, + parse_children, parse_widget_universal, print_invalid_attrib, style::{parse_color, parse_round, parse_style}, + AttribPair, ParserContext, ParserFile, }, - renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData}, + renderer_vk::text::custom_glyph::CustomGlyphData, widget::image::{WidgetImage, WidgetImageParams}, }; @@ -33,7 +34,7 @@ pub fn parse_widget_image<'a>( }; if !value.is_empty() { - glyph = match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals, asset_path) { + glyph = match CustomGlyphData::from_assets(&mut ctx.layout.state.globals, asset_path) { Ok(glyph) => Some(glyph), Err(e) => { log::warn!("failed to load {value}: {e}"); @@ -63,7 +64,7 @@ pub fn parse_widget_image<'a>( } if let Some(glyph) = glyph { - params.glyph_data = Some(CustomGlyphData::new(glyph)); + params.glyph_data = Some(glyph); } else { log::warn!("No source for image node!"); } diff --git a/wgui/src/parser/widget_sprite.rs b/wgui/src/parser/widget_sprite.rs index c961e866..32aec6ae 100644 --- a/wgui/src/parser/widget_sprite.rs +++ b/wgui/src/parser/widget_sprite.rs @@ -1,8 +1,8 @@ use crate::{ assets::AssetPath, layout::WidgetID, - parser::{AttribPair, ParserContext, ParserFile, parse_children, parse_widget_universal, style::parse_style}, - renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData}, + parser::{parse_children, parse_widget_universal, style::parse_style, AttribPair, ParserContext, ParserFile}, + renderer_vk::text::custom_glyph::CustomGlyphData, widget::sprite::{WidgetSprite, WidgetSpriteParams}, }; @@ -32,7 +32,7 @@ pub fn parse_widget_sprite<'a>( }; if !value.is_empty() { - glyph = match CustomGlyphContent::from_assets(&ctx.layout.state.globals, asset_path) { + glyph = match CustomGlyphData::from_assets(&ctx.layout.state.globals, asset_path) { Ok(glyph) => Some(glyph), Err(e) => { log::warn!("failed to load {value}: {e}"); @@ -53,7 +53,7 @@ pub fn parse_widget_sprite<'a>( } if let Some(glyph) = glyph { - params.glyph_data = Some(CustomGlyphData::new(glyph)); + params.glyph_data = Some(glyph); } else { log::warn!("No source for sprite node!"); } diff --git a/wgui/src/renderer_vk/text/custom_glyph.rs b/wgui/src/renderer_vk/text/custom_glyph.rs index 759fa45d..43fd4f19 100644 --- a/wgui/src/renderer_vk/text/custom_glyph.rs +++ b/wgui/src/renderer_vk/text/custom_glyph.rs @@ -1,8 +1,10 @@ use std::{ + collections::HashMap, f32, + hash::{DefaultHasher, Hasher}, sync::{ - Arc, atomic::{AtomicUsize, Ordering}, + Arc, Weak, }, }; @@ -14,15 +16,53 @@ use crate::{assets::AssetPath, globals::WguiGlobals}; static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); +#[derive(Hash, PartialEq, Eq)] +pub struct HashedAsset { + path: String, + hash: u64, +} + +pub struct CustomGlyphCache { + inner: HashMap, +} + +impl CustomGlyphCache { + pub(crate) fn new() -> Self { + Self { inner: HashMap::new() } + } + + fn get(&self, path: &str, bytes: &[u8]) -> Result { + let mut hasher = DefaultHasher::new(); + hasher.write(bytes); + let hash = hasher.finish(); + + let hashed_asset = HashedAsset { + path: path.to_string(), + hash, + }; + + self + .inner + .get(&hashed_asset) + .and_then(|a| a.upgrade()) + .inspect(|_| log::debug!("Glyph cache hit on: '{path}'")) + .ok_or(hashed_asset) + } + + fn insert(&mut self, hashed_asset: HashedAsset, data: &CustomGlyphData) { + self.inner.insert(hashed_asset, data.clone_weak()); + } +} + /// The raw content of a glyph. #[derive(Debug, Clone)] -pub enum CustomGlyphContent { +pub(crate) enum CustomGlyphContent { Svg(Box), Image(RgbaImage), } impl CustomGlyphContent { - pub fn from_bin_svg(data: &[u8]) -> anyhow::Result { + fn from_bin_svg(data: &[u8]) -> anyhow::Result { let options = Options { style_sheet: Some("svg { color: white }".into()), ..Options::default() @@ -32,19 +72,23 @@ impl CustomGlyphContent { Ok(Self::Svg(Box::new(tree))) } - pub fn from_bin_raster(data: &[u8]) -> anyhow::Result { + fn from_bin_raster(data: &[u8]) -> anyhow::Result { let image = image::load_from_memory(data)?.into_rgba8(); Ok(Self::Image(image)) } +} - #[allow(clippy::case_sensitive_file_extension_comparisons)] - pub fn from_assets(globals: &WguiGlobals, path: AssetPath) -> anyhow::Result { - let path_str = path.get_str(); - let data = globals.get_asset(path)?; - if path_str.ends_with(".svg") || path_str.ends_with(".svgz") { - Ok(Self::from_bin_svg(&data)?) +struct CustomGlyphDataWeak { + id: usize, + content: Weak, +} + +impl CustomGlyphDataWeak { + fn upgrade(&self) -> Option { + if let Some(content) = self.content.upgrade() { + Some(CustomGlyphData { id: self.id, content }) } else { - Ok(Self::from_bin_raster(&data)?) + None } } } @@ -58,13 +102,20 @@ pub struct CustomGlyphData { } impl CustomGlyphData { - pub fn new(content: CustomGlyphContent) -> Self { + fn new(content: CustomGlyphContent) -> Self { Self { id: AUTO_INCREMENT.fetch_add(1, Ordering::Relaxed), content: Arc::new(content), } } + fn clone_weak(&self) -> CustomGlyphDataWeak { + CustomGlyphDataWeak { + id: self.id, + content: Arc::downgrade(&self.content), + } + } + pub fn dim_for_cache_key(&self, width: u16, height: u16) -> (u16, u16) { const MAX_RASTER_DIM: u16 = 256; match self.content.as_ref() { @@ -75,6 +126,41 @@ impl CustomGlyphData { CustomGlyphContent::Image(image) => (image.width() as _, image.height() as _), } } + + pub fn from_assets(globals: &WguiGlobals, path: AssetPath) -> anyhow::Result { + let path_str = path.get_str(); + let data = globals.get_asset(path)?; + + if path_str.ends_with(".svg") || path_str.ends_with(".svgz") { + Self::from_bytes_svg(globals, path_str, &data) + } else { + Self::from_bytes_raster(globals, path_str, &data) + } + } + + pub fn from_bytes_raster(globals: &WguiGlobals, path: &str, data: &[u8]) -> anyhow::Result { + let globals_borrow = &mut globals.get(); + match globals_borrow.custom_glyph_cache.get(path, data) { + Ok(data) => return Ok(data), + Err(hashed_asset) => { + let data = Self::new(CustomGlyphContent::from_bin_raster(data)?); + globals_borrow.custom_glyph_cache.insert(hashed_asset, &data); + Ok(data) + } + } + } + + pub fn from_bytes_svg(globals: &WguiGlobals, path: &str, data: &[u8]) -> anyhow::Result { + let globals_borrow = &mut globals.get(); + match globals_borrow.custom_glyph_cache.get(path, data) { + Ok(data) => return Ok(data), + Err(hashed_asset) => { + let data = Self::new(CustomGlyphContent::from_bin_svg(data)?); + globals_borrow.custom_glyph_cache.insert(hashed_asset, &data); + Ok(data) + } + } + } } impl PartialEq for CustomGlyphData { diff --git a/wlx-overlay-s/src/overlays/custom.rs b/wlx-overlay-s/src/overlays/custom.rs index 0877f7b3..933311bd 100644 --- a/wlx-overlay-s/src/overlays/custom.rs +++ b/wlx-overlay-s/src/overlays/custom.rs @@ -7,7 +7,7 @@ use wgui::{ event::{CallbackDataCommon, EventAlterables}, i18n::Translation, parser::{Fetchable, parse_color_hex}, - renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData}, + renderer_vk::text::custom_glyph::CustomGlyphData, taffy, widget::{ image::WidgetImage, label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite, @@ -117,12 +117,11 @@ fn apply_custom_command( .parser_state .fetch_widget(&panel.layout.state, element) { - let content = CustomGlyphContent::from_assets( + let data = CustomGlyphData::from_assets( &app.wgui_globals, wgui::assets::AssetPath::File(path), ) .context("Could not load content from supplied path.")?; - let data = CustomGlyphData::new(content); if let Some(mut sprite) = pair.widget.get_as::() { sprite.set_content(&mut com, Some(data));