Rework GraphicCache to use multiple texture atlases and put large images in their own textures.

This commit is contained in:
Imbris 2019-10-19 21:28:30 -04:00
parent baf04e75d9
commit 8bb54976eb
5 changed files with 270 additions and 262 deletions

6
Cargo.lock generated
View File

@ -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)" = "<none>"
"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"

View File

@ -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"

View File

@ -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<UiPipeline>,
graphic_cache: GraphicCache,
graphic_cache_tex: Texture<UiPipeline>,
}
// 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<UiPipeline> {
@ -52,11 +44,11 @@ impl Cache {
pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphCache<'static>, &Texture<UiPipeline>) {
(&mut self.glyph_cache, &self.glyph_cache_tex)
}
pub fn graphic_cache_tex(&self) -> &Texture<UiPipeline> {
&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<UiPipeline>) {
(&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> {

View File

@ -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<u16>, Aabr<u64>);
// TODO these can become invalid when clearing the cache
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct TexId(usize);
type Parameters = (Id, Vec2<u16>, Aabr<u64>);
type GraphicMap = HashMap<Id, Graphic>;
enum CacheLoc {
Atlas {
// Index of the atlas this is cached in
atlas_idx: usize,
// Where in the cache texture this is
aabr: Aabr<u16>,
},
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<u16>,
location: CacheLoc,
valid: bool,
}
// Caches graphics, only deallocates when changing screen resolution (completely cleared)
pub struct GraphicCache {
graphic_map: HashMap<Id, Graphic>,
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<Texture<UiPipeline>>,
// Stores the location of graphics rendered at a particular resolution and cached on the cpu
cache_map: HashMap<Parameters, CachedDetails>,
// The current frame
current_frame: u32,
unused_entries_this_frame: Option<Vec<Option<(u32, Parameters)>>>,
soft_cache: HashMap<Parameters, RgbaImage>,
transfer_ready: Vec<(Parameters, Aabr<u16>)>,
}
impl GraphicCache {
pub fn new(size: Vec2<u16>) -> 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::<Vec<_>>();
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<u16>) {
self.soft_cache.clear();
self.transfer_ready.clear();
/// Used to aquire textures for rendering
pub fn get_tex(&self, id: TexId) -> &Texture<UiPipeline> {
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<u16>,
source: Aabr<f64>,
rotation: Rotation,
) -> Option<Aabr<u16>> {
) -> Option<(Aabr<u16>, 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::<i32>::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::<Vec<_>>();
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<F>(&mut self, mut cacher: F)
where
F: FnMut(Aabr<u16>, &[[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::<Vec<[u8; 4]>>(),
);
} 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<u16>) -> Option<RgbaImage> {
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<UiPipeline>) {
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<u16> {
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<u16>,
tex: &Texture<UiPipeline>,
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::<Vec<[u8; 4]>>(),
) {
warn!("Failed to update texture: {:?}", err);
}
}

View File

@ -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<usize>),
}
impl DrawCommand {
fn image(verts: Range<usize>) -> DrawCommand {
fn image(verts: Range<usize>, 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);
}
}
}