diff --git a/Cargo.lock b/Cargo.lock index 7fd91c26e2..4cf1433f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1471,7 +1471,7 @@ dependencies = [ [[package]] name = "guillotiere" version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/Imberflur/guillotiere#42c298f5bcf0f95f1a004360d05e25ca3711e9ed" dependencies = [ "euclid 0.19.9 (registry+https://github.com/rust-lang/crates.io-index)", "svg_fmt 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3668,7 +3668,7 @@ dependencies = [ "gfx_window_glutin 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", "glsl-include 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", - "guillotiere 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "guillotiere 0.4.2 (git+https://github.com/Imberflur/guillotiere)", "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "heaptrack 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "image 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4132,7 +4132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum gobject-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "70409d6405db8b1591602fcd0cbe8af52cd9976dd39194442b4c149ba343f86d" "checksum gtk 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d695d6be4110618a97c19cd068e8a00e53e33b87e3c65cdc5397667498b1bc24" "checksum gtk-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d9554cf5b3a85a13fb39258c65b04b262989c1d7a758f8f555b77a478621a91" -"checksum guillotiere 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "182af928b4435d8fbef910535586ecdca95ab4068493769c090e6573477f5e35" +"checksum guillotiere 0.4.2 (git+https://github.com/Imberflur/guillotiere)" = "" "checksum gzip-header 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0131feb3d3bb2a5a238d8a4d09f6353b7ebfdc52e77bccbf4ea6eaa751dde639" "checksum hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353" "checksum hashbrown 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bcea5b597dd98e6d1f1ec171744cc5dee1a30d1c23c5b98e3cf9d4fbdf8a526" diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 50e957e61b..bd28c7ceea 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -48,7 +48,7 @@ image = "0.22.0" serde = "1.0.98" serde_derive = "1.0.98" ron = "0.5.1" -guillotiere = "0.4.2" +guillotiere = { git = "https://github.com/Imberflur/guillotiere" } simplelog = "0.6.0" msgbox = { version = "0.4.0", optional = true } directories = "2.0.2" diff --git a/voxygen/src/ui/cache.rs b/voxygen/src/ui/cache.rs index 06c4140007..51727c39e2 100644 --- a/voxygen/src/ui/cache.rs +++ b/voxygen/src/ui/cache.rs @@ -8,7 +8,6 @@ use vek::*; // Multiplied by current window size const GLYPH_CACHE_SIZE: u16 = 1; -const GRAPHIC_CACHE_SIZE: u16 = 2; // Glyph cache tolerances const SCALE_TOLERANCE: f32 = 0.1; const POSITION_TOLERANCE: f32 = 0.1; @@ -17,7 +16,6 @@ pub struct Cache { glyph_cache: GlyphCache<'static>, glyph_cache_tex: Texture, graphic_cache: GraphicCache, - graphic_cache_tex: Texture, } // TODO: Should functions be returning UiError instead of Error? @@ -27,11 +25,6 @@ impl Cache { let max_texture_size = renderer.max_texture_size(); - let graphic_cache_dims = Vec2::new(w, h).map(|e| { - (e * GRAPHIC_CACHE_SIZE) - .min(max_texture_size as u16) - .max(512) - }); let glyph_cache_dims = Vec2::new(w, h).map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size as u16).max(512)); @@ -42,8 +35,7 @@ impl Cache { .position_tolerance(POSITION_TOLERANCE) .build(), glyph_cache_tex: renderer.create_dynamic_texture(glyph_cache_dims.map(|e| e as u16))?, - graphic_cache: GraphicCache::new(graphic_cache_dims), - graphic_cache_tex: renderer.create_dynamic_texture(graphic_cache_dims)?, + graphic_cache: GraphicCache::new(renderer), }) } pub fn glyph_cache_tex(&self) -> &Texture { @@ -52,11 +44,11 @@ impl Cache { pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture) { (&mut self.glyph_cache, &self.glyph_cache_tex) } - pub fn graphic_cache_tex(&self) -> &Texture { - &self.graphic_cache_tex + pub fn graphic_cache(&self) -> &GraphicCache { + &self.graphic_cache } - pub fn graphic_cache_mut_and_tex(&mut self) -> (&mut GraphicCache, &Texture) { - (&mut self.graphic_cache, &self.graphic_cache_tex) + pub fn graphic_cache_mut(&mut self) -> &mut GraphicCache { + &mut self.graphic_cache } pub fn add_graphic(&mut self, graphic: Graphic) -> GraphicId { self.graphic_cache.add_graphic(graphic) @@ -65,16 +57,8 @@ impl Cache { self.graphic_cache.replace_graphic(id, graphic) } // Resizes and clears the GraphicCache - pub fn resize_graphic_cache(&mut self, renderer: &mut Renderer) -> Result<(), Error> { - let max_texture_size = renderer.max_texture_size(); - let cache_dims = renderer.get_resolution().map(|e| { - (e * GRAPHIC_CACHE_SIZE) - .min(max_texture_size as u16) - .max(512) - }); - self.graphic_cache.clear_cache(cache_dims); - self.graphic_cache_tex = renderer.create_dynamic_texture(cache_dims)?; - Ok(()) + pub fn resize_graphic_cache(&mut self, renderer: &mut Renderer) { + self.graphic_cache.clear_cache(renderer); } // Resizes and clears the GlyphCache pub fn resize_glyph_cache(&mut self, renderer: &mut Renderer) -> Result<(), Error> { diff --git a/voxygen/src/ui/graphic/mod.rs b/voxygen/src/ui/graphic/mod.rs index 8c90e350bf..7bdb59c3db 100644 --- a/voxygen/src/ui/graphic/mod.rs +++ b/voxygen/src/ui/graphic/mod.rs @@ -1,10 +1,11 @@ mod renderer; +use crate::render::{Renderer, Texture, UiPipeline}; use dot_vox::DotVoxData; -use guillotiere::{size2, AllocId, Allocation, AtlasAllocator}; +use guillotiere::{size2, SimpleAtlasAllocator}; use hashbrown::HashMap; use image::{DynamicImage, RgbaImage}; -use log::{error, warn}; +use log::warn; use std::sync::Arc; use vek::*; @@ -43,44 +44,60 @@ pub enum Rotation { Cw270, } +/// Images larger than this are stored in individual textures +/// Fraction of the total graphic cache size +const ATLAS_CUTTOFF_FRAC: f32 = 0.2; +/// Multiplied by current window size +const GRAPHIC_CACHE_RELATIVE_SIZE: u16 = 1; + #[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct Id(u32); -type Parameters = (Id, Vec2, Aabr); +// TODO these can become invalid when clearing the cache +#[derive(PartialEq, Eq, Hash, Copy, Clone)] +pub struct TexId(usize); +type Parameters = (Id, Vec2, Aabr); +type GraphicMap = HashMap; + +enum CacheLoc { + Atlas { + // Index of the atlas this is cached in + atlas_idx: usize, + // Where in the cache texture this is + aabr: Aabr, + }, + Texture { + index: usize, + }, +} struct CachedDetails { - // Id used by AtlasAllocator - alloc_id: AllocId, - // Last frame this was used on - frame: u32, - // Where in the cache texture this is - aabr: Aabr, + location: CacheLoc, + valid: bool, } +// Caches graphics, only deallocates when changing screen resolution (completely cleared) pub struct GraphicCache { - graphic_map: HashMap, + graphic_map: GraphicMap, + // Next id to use when a new graphic is added next_id: u32, - atlas: AtlasAllocator, + // Atlases with the index of their texture in the textures vec + atlases: Vec<(SimpleAtlasAllocator, usize)>, + textures: Vec>, + // Stores the location of graphics rendered at a particular resolution and cached on the cpu cache_map: HashMap, - // The current frame - current_frame: u32, - unused_entries_this_frame: Option>>, - - soft_cache: HashMap, - transfer_ready: Vec<(Parameters, Aabr)>, } impl GraphicCache { - pub fn new(size: Vec2) -> Self { + pub fn new(renderer: &mut Renderer) -> Self { + let (atlas, texture) = create_atlas_texture(renderer); + Self { graphic_map: HashMap::default(), next_id: 0, - atlas: AtlasAllocator::new(size2(i32::from(size.x), i32::from(size.y))), + atlases: vec![(atlas, 0)], + textures: vec![texture], cache_map: HashMap::default(), - current_frame: 0, - unused_entries_this_frame: None, - soft_cache: HashMap::default(), - transfer_ready: Vec::new(), } } pub fn add_graphic(&mut self, graphic: Graphic) -> Id { @@ -97,38 +114,42 @@ impl GraphicCache { // Remove from caches // Maybe make this more efficient if replace graphic is used more often - self.transfer_ready.retain(|(p, _)| p.0 != id); let uses = self - .soft_cache + .cache_map .keys() .filter(|k| k.0 == id) .copied() .collect::>(); for p in uses { - self.soft_cache.remove(&p); - if let Some(details) = self.cache_map.remove(&p) { - // Deallocate - self.atlas.deallocate(details.alloc_id); + if let Some(details) = self.cache_map.get_mut(&p) { + // Reuse allocation + details.valid = false; } } } pub fn get_graphic(&self, id: Id) -> Option<&Graphic> { self.graphic_map.get(&id) } - pub fn clear_cache(&mut self, new_size: Vec2) { - self.soft_cache.clear(); - self.transfer_ready.clear(); + /// Used to aquire textures for rendering + pub fn get_tex(&self, id: TexId) -> &Texture { + self.textures.get(id.0).expect("Invalid TexId used") + } + pub fn clear_cache(&mut self, renderer: &mut Renderer) { self.cache_map.clear(); - self.atlas = AtlasAllocator::new(size2(i32::from(new_size.x), i32::from(new_size.y))); + + let (atlas, texture) = create_atlas_texture(renderer); + self.atlases = vec![(atlas, 0)]; + self.textures = vec![texture]; } - pub fn queue_res( + pub fn cache_res( &mut self, + renderer: &mut Renderer, graphic_id: Id, dims: Vec2, source: Aabr, rotation: Rotation, - ) -> Option> { + ) -> Option<(Aabr, TexId)> { let dims = match rotation { Rotation::Cw90 | Rotation::Cw270 => Vec2::new(dims.y, dims.x), Rotation::None | Rotation::Cw180 => dims, @@ -148,170 +169,177 @@ impl GraphicCache { }, }; - if let Some(details) = self.cache_map.get_mut(&key) { - // Update frame - details.frame = self.current_frame; + if let Some(details) = self.cache_map.get(&key) { + let (idx, aabr) = match details.location { + CacheLoc::Atlas { + atlas_idx, aabr, .. + } => (self.atlases[atlas_idx].1, aabr), + CacheLoc::Texture { index } => { + ( + index, + Aabr { + min: Vec2::new(0, 0), + // Note texture should always match the cached dimensions + max: dims, + }, + ) + } + }; - Some(rotated_aabr(details.aabr)) - } else { - // Create image if it doesn't already exist - if !self.soft_cache.contains_key(&key) { - self.soft_cache.insert( - key, - match self.graphic_map.get(&graphic_id) { - Some(Graphic::Blank) => return None, - // Render image at requested resolution - // TODO: Use source aabr. - Some(Graphic::Image(ref image)) => image - .resize_exact( - u32::from(dims.x), - u32::from(dims.y), - image::FilterType::Nearest, - ) - .to_rgba(), - Some(Graphic::Voxel(ref vox, trans, min_samples)) => renderer::draw_vox( - &vox.as_ref().into(), - dims, - trans.clone(), - *min_samples, - ), - None => { - warn!("A graphic was requested via an id which is not in use"); - return None; - } - }, - ); + // Check if the cached version has been invalidated by replacing the underlying graphic + if !details.valid { + // Create image + let image = draw_graphic(&self.graphic_map, graphic_id, dims)?; + // Transfer to the gpu + upload_image(renderer, aabr, &self.textures[idx], &image); } - let aabr_from_alloc_rect = |rect: guillotiere::Rectangle| { - let (min, max) = (rect.min, rect.max); - Aabr { - min: Vec2::new(min.x as u16, min.y as u16), - max: Vec2::new(max.x as u16, max.y as u16), - } - }; + Some((rotated_aabr(aabr), TexId(idx))) + } else { + // Create image + let image = draw_graphic(&self.graphic_map, graphic_id, dims)?; - // Allocate rectangle. - let (alloc_id, aabr) = match self - .atlas - .allocate(size2(i32::from(dims.x), i32::from(dims.y))) + // Allocate space on the gpu + // Check size of graphic + // Graphics over a particular size are sent to their own textures + let location = if Vec2::::from(self.atlases[0].0.size().to_tuple()) + .map(|e| e as u16) + .map2(dims, |a, d| a as f32 * ATLAS_CUTTOFF_FRAC >= d as f32) + .reduce_and() { - Some(Allocation { id, rectangle }) => (id, aabr_from_alloc_rect(rectangle)), - // Out of room. - // 1) Remove unused allocations - // TODO: Make more room. - // 2) Rearrange rectangles (see comments below) - // 3) Expand cache size - None => { - // 1) Remove unused allocations - if self.unused_entries_this_frame.is_none() { - self.unused_entries_this_frame = { - let mut unused = self - .cache_map - .iter() - .filter_map(|(key, details)| { - if details.frame < self.current_frame - 1 { - Some(Some((details.frame, *key))) - } else { - None - } - }) - .collect::>(); - unused - .sort_unstable_by(|a, b| a.map(|(f, _)| f).cmp(&b.map(|(f, _)| f))); - Some(unused) - }; - } - - let mut allocation = None; - // Fight the checker! - let current_frame = self.current_frame; - // Will always be Some - if let Some(ref mut unused_entries) = self.unused_entries_this_frame { - // Deallocate from oldest to newest - for key in unused_entries - .iter_mut() - .filter_map(|e| e.take().map(|(_, key)| key)) - { - // Check if still in cache map and it has not been used since the vec was built - if self - .cache_map - .get(&key) - .filter(|d| d.frame != current_frame) - .is_some() - { - if let Some(alloc_id) = - self.cache_map.remove(&key).map(|d| d.alloc_id) - { - // Deallocate - self.atlas.deallocate(alloc_id); - // Try to allocate - if let Some(alloc) = self - .atlas - .allocate(size2(i32::from(dims.x), i32::from(dims.y))) - { - allocation = Some(alloc); - break; - } - } - } - } - // 2) Rearrange rectangles - // This needs to be done infrequently and be based on whether rectangles have been removed - // Maybe find a way to calculate whether there is a significant amount of fragmentation - // Or consider dropping the use of an atlas and moving to a hashmap of individual textures :/ - // if allocation.is_none() { - // - // } - } - - match allocation { - Some(Allocation { id, rectangle }) => (id, aabr_from_alloc_rect(rectangle)), - None => { - warn!("Can't find space for an image in the graphic cache"); - return None; - } + // Fit into an atlas + let mut loc = None; + for (atlas_idx, (ref mut atlas, _)) in self.atlases.iter_mut().enumerate() { + if let Some(rectangle) = + atlas.allocate(size2(i32::from(dims.x), i32::from(dims.y))) + { + let aabr = aabr_from_alloc_rect(rectangle); + loc = Some(CacheLoc::Atlas { atlas_idx, aabr }); + break; } } - }; - self.transfer_ready.push((key, aabr)); - // Insert area into map for retrieval. + match loc { + Some(loc) => loc, + // Create a new atlas + None => { + let (mut atlas, texture) = create_atlas_texture(renderer); + let aabr = atlas + .allocate(size2(i32::from(dims.x), i32::from(dims.y))) + .map(aabr_from_alloc_rect) + .unwrap(); + let tex_idx = self.textures.len(); + let atlas_idx = self.atlases.len(); + self.textures.push(texture); + self.atlases.push((atlas, tex_idx)); + CacheLoc::Atlas { atlas_idx, aabr } + } + } + } else { + // Create a texture just for this + let texture = renderer.create_dynamic_texture(dims).unwrap(); + let index = self.textures.len(); + self.textures.push(texture); + CacheLoc::Texture { index } + }; + + let (idx, aabr) = match location { + CacheLoc::Atlas { + atlas_idx, aabr, .. + } => (self.atlases[atlas_idx].1, aabr), + CacheLoc::Texture { index } => { + ( + index, + Aabr { + min: Vec2::new(0, 0), + // Note texture should always match the cached dimensions + max: dims, + }, + ) + } + }; + // Upload + upload_image(renderer, aabr, &self.textures[idx], &image); + // Insert into cached map self.cache_map.insert( key, CachedDetails { - alloc_id, - frame: self.current_frame, - aabr, + location, + valid: true, }, ); - Some(rotated_aabr(aabr)) + Some((rotated_aabr(aabr), TexId(idx))) } } - - // Anything not queued since the last call to this will be removed if there is not enough space in the cache - pub fn cache_queued(&mut self, mut cacher: F) - where - F: FnMut(Aabr, &[[u8; 4]]), - { - // Cached queued - // TODO: combine nearby transfers - for (key, target_aarb) in self.transfer_ready.drain(..) { - if let Some(image) = self.soft_cache.get(&key) { - cacher( - target_aarb, - &image.pixels().map(|p| p.0).collect::>(), - ); - } else { - error!("Image queued for transfer to gpu cache but it doesn't exist (this should never occur)"); - } - } - - // Increment frame - self.current_frame += 1; - - // Reset unused entries - self.unused_entries_this_frame = None; - } +} + +// Draw a graphic at the specified dimensions +fn draw_graphic(graphic_map: &GraphicMap, graphic_id: Id, dims: Vec2) -> Option { + match graphic_map.get(&graphic_id) { + Some(Graphic::Blank) => None, + // Render image at requested resolution + // TODO: Use source aabr. + Some(Graphic::Image(ref image)) => Some( + image + .resize_exact( + u32::from(dims.x), + u32::from(dims.y), + image::FilterType::Nearest, + ) + .to_rgba(), + ), + Some(Graphic::Voxel(ref vox, trans, min_samples)) => Some(renderer::draw_vox( + &vox.as_ref().into(), + dims, + trans.clone(), + *min_samples, + )), + None => { + warn!("A graphic was requested via an id which is not in use"); + None + } + } +} + +fn create_atlas_texture(renderer: &mut Renderer) -> (SimpleAtlasAllocator, Texture) { + let (w, h) = renderer.get_resolution().into_tuple(); + + let max_texture_size = renderer.max_texture_size(); + + let size = Vec2::new(w, h).map(|e| { + (e * GRAPHIC_CACHE_RELATIVE_SIZE) + .max(512) + .min(max_texture_size as u16) + }); + + let atlas = SimpleAtlasAllocator::new(size2(i32::from(size.x), i32::from(size.y))); + let texture = renderer.create_dynamic_texture(size).unwrap(); + (atlas, texture) +} + +fn aabr_from_alloc_rect(rect: guillotiere::Rectangle) -> Aabr { + let (min, max) = (rect.min, rect.max); + Aabr { + min: Vec2::new(min.x as u16, min.y as u16), + max: Vec2::new(max.x as u16, max.y as u16), + } +} + +fn upload_image( + renderer: &mut Renderer, + aabr: Aabr, + tex: &Texture, + image: &RgbaImage, +) { + let offset = aabr.min.into_array(); + let size = aabr.size().into_array(); + if let Err(err) = renderer.update_texture( + tex, + offset, + size, + &image.pixels().map(|p| p.0).collect::>(), + ) { + warn!("Failed to update texture: {:?}", err); + } } diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 62f3ae9dd2..e5e581adf8 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -40,7 +40,7 @@ use conrod_core::{ widget::{self, id::Generator}, Rect, UiBuilder, UiCell, }; -use graphic::Rotation; +use graphic::{Rotation, TexId}; use log::{error, warn}; use std::{ fs::File, @@ -57,7 +57,7 @@ pub enum UiError { } enum DrawKind { - Image, + Image(TexId), // Text and non-textured geometry Plain, } @@ -67,9 +67,9 @@ enum DrawCommand { WorldPos(Option), } impl DrawCommand { - fn image(verts: Range) -> DrawCommand { + fn image(verts: Range, id: TexId) -> DrawCommand { DrawCommand::Draw { - kind: DrawKind::Image, + kind: DrawKind::Image(id), verts, } } @@ -270,7 +270,7 @@ impl Ui { if self.need_cache_resize { // Resize graphic cache - self.cache.resize_graphic_cache(renderer).unwrap(); + self.cache.resize_graphic_cache(renderer); // Resize glyph cache self.cache.resize_glyph_cache(renderer).unwrap(); @@ -289,10 +289,8 @@ impl Ui { ) }; - // TODO: this could be removed entirely if the draw call just used both textures, - // however this allows for flexibility if we want to interweave other draw calls later. enum State { - Image, + Image(TexId), Plain, }; @@ -318,9 +316,9 @@ impl Ui { // `Plain` state. macro_rules! switch_to_plain_state { () => { - if let State::Image = current_state { + if let State::Image(id) = current_state { self.draw_commands - .push(DrawCommand::image(start..mesh.vertices().len())); + .push(DrawCommand::image(start..mesh.vertices().len(), id)); start = mesh.vertices().len(); current_state = State::Plain; } @@ -360,7 +358,7 @@ impl Ui { // Finish the current command. self.draw_commands.push(match current_state { State::Plain => DrawCommand::plain(start..mesh.vertices().len()), - State::Image => DrawCommand::image(start..mesh.vertices().len()), + State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id), }); start = mesh.vertices().len(); @@ -377,7 +375,7 @@ impl Ui { // Finish current state self.draw_commands.push(match current_state { State::Plain => DrawCommand::plain(start..mesh.vertices().len()), - State::Image => DrawCommand::image(start..mesh.vertices().len()), + State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id), }); start = mesh.vertices().len(); // Push new position command @@ -425,21 +423,13 @@ impl Ui { .image_map .get(&image_id) .expect("Image does not exist in image map"); - let (graphic_cache, cache_tex) = self.cache.graphic_cache_mut_and_tex(); + let graphic_cache = self.cache.graphic_cache_mut(); match graphic_cache.get_graphic(*graphic_id) { Some(Graphic::Blank) | None => continue, _ => {} } - // Switch to the image state if we are not in it already. - if let State::Plain = current_state { - self.draw_commands - .push(DrawCommand::plain(start..mesh.vertices().len())); - start = mesh.vertices().len(); - current_state = State::Image; - } - let color = srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into()); @@ -467,30 +457,46 @@ impl Ui { max: Vec2::new(uv_r, uv_t), } }; - // TODO: get dims from graphic_cache (or have it return floats directly) - let (cache_w, cache_h) = - cache_tex.get_dimensions().map(|e| e as f32).into_tuple(); // Cache graphic at particular resolution. - let uv_aabr = match graphic_cache.queue_res( + let (uv_aabr, tex_id) = match graphic_cache.cache_res( + renderer, *graphic_id, resolution, source_aabr, *rotation, ) { - Some(aabr) => Aabr { - min: Vec2::new( - (aabr.min.x as f32) / cache_w, - (aabr.max.y as f32) / cache_h, - ), - max: Vec2::new( - (aabr.max.x as f32) / cache_w, - (aabr.min.y as f32) / cache_h, - ), - }, + // TODO: get dims from graphic_cache (or have it return floats directly) + Some((aabr, tex_id)) => { + let cache_dims = graphic_cache + .get_tex(tex_id) + .get_dimensions() + .map(|e| e as f32); + let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims; + let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims; + (Aabr { min, max }, tex_id) + } None => continue, }; + match current_state { + // Switch to the image state if we are not in it already. + State::Plain => { + self.draw_commands + .push(DrawCommand::plain(start..mesh.vertices().len())); + start = mesh.vertices().len(); + current_state = State::Image(tex_id); + } + // If the image is cached in a different texture switch to the new one + State::Image(id) if id != tex_id => { + self.draw_commands + .push(DrawCommand::image(start..mesh.vertices().len(), id)); + start = mesh.vertices().len(); + current_state = State::Image(tex_id); + } + State::Image(_) => {} + } + mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image)); } PrimitiveKind::Text { @@ -624,8 +630,8 @@ impl Ui { State::Plain => { DrawCommand::plain(start..mesh.vertices().len()) } - State::Image => { - DrawCommand::image(start..mesh.vertices().len()) + State::Image(id) => { + DrawCommand::image(start..mesh.vertices().len(), id) } }); start = mesh.vertices().len(); @@ -672,7 +678,7 @@ impl Ui { // Enter the final command. self.draw_commands.push(match current_state { State::Plain => DrawCommand::plain(start..mesh.vertices().len()), - State::Image => DrawCommand::image(start..mesh.vertices().len()), + State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id), }); // Draw glyph cache (use for debugging). @@ -703,16 +709,6 @@ impl Ui { // Update model with new mesh. renderer.update_model(&self.model, &mesh, 0).unwrap(); - // Move cached graphics to the gpu - let (graphic_cache, cache_tex) = self.cache.graphic_cache_mut_and_tex(); - graphic_cache.cache_queued(|aabr, data| { - let offset = aabr.min.into_array(); - let size = aabr.size().into_array(); - if let Err(err) = renderer.update_texture(cache_tex, offset, size, data) { - warn!("Failed to update texture: {:?}", err); - } - }); - // Handle window resizing. if let Some(new_dims) = self.window_resized.take() { let (old_w, old_h) = self.scale.scaled_window_size().into_tuple(); @@ -742,11 +738,11 @@ impl Ui { } DrawCommand::Draw { kind, verts } => { let tex = match kind { - DrawKind::Image => self.cache.graphic_cache_tex(), + DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id), DrawKind::Plain => self.cache.glyph_cache_tex(), }; let model = self.model.submodel(verts.clone()); - renderer.render_ui_element(&model, &tex, scissor, globals, locals); + renderer.render_ui_element(&model, tex, scissor, globals, locals); } } }