mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Rework GraphicCache to use multiple texture atlases and put large images in their own textures.
This commit is contained in:
parent
baf04e75d9
commit
8bb54976eb
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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> {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user