mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Pixel perfection, vox orientation, image rotation
This commit is contained in:
parent
66ebdb2355
commit
e9bedf529d
BIN
assets/voxygen/element/frames/tt_test_corner_tr.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/tt_test_corner_tr.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/frames/tt_test_edge.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/frames/tt_test_edge.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -3,7 +3,7 @@ use crate::{
|
||||
ui::{
|
||||
self,
|
||||
img_ids::{BlankGraphic, ImageGraphic, VoxelGraphic},
|
||||
Tooltip, Tooltipable, Ui,
|
||||
ImageFrame, Tooltip, Tooltipable, Ui,
|
||||
},
|
||||
GlobalState,
|
||||
};
|
||||
@ -58,6 +58,11 @@ widget_ids! {
|
||||
error_frame,
|
||||
button_ok,
|
||||
version,
|
||||
|
||||
|
||||
// TODO remove
|
||||
frame_test,
|
||||
test2,
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +83,16 @@ image_ids! {
|
||||
<BlankGraphic>
|
||||
nothing: (),
|
||||
}
|
||||
}
|
||||
|
||||
rotation_image_ids! {
|
||||
pub struct ImgsRot {
|
||||
<VoxelGraphic>
|
||||
|
||||
// Tooltip Test
|
||||
tt_side: "voxygen/element/frames/tt_test_edge.vox",
|
||||
tt_corner: "voxygen/element/frames/tt_test_corner_tr.vox",
|
||||
}
|
||||
}
|
||||
|
||||
font_ids! {
|
||||
@ -104,6 +118,7 @@ pub struct MainMenuUi {
|
||||
ui: Ui,
|
||||
ids: Ids,
|
||||
imgs: Imgs,
|
||||
rot_imgs: ImgsRot,
|
||||
fonts: Fonts,
|
||||
username: String,
|
||||
password: String,
|
||||
@ -126,6 +141,7 @@ impl MainMenuUi {
|
||||
let ids = Ids::new(ui.id_generator());
|
||||
// Load images
|
||||
let imgs = Imgs::load(&mut ui).expect("Failed to load images");
|
||||
let rot_imgs = ImgsRot::load(&mut ui).expect("Failed to load images!");
|
||||
// Load fonts
|
||||
let fonts = Fonts::load(&mut ui).expect("Failed to load fonts");
|
||||
|
||||
@ -133,6 +149,7 @@ impl MainMenuUi {
|
||||
ui,
|
||||
ids,
|
||||
imgs,
|
||||
rot_imgs,
|
||||
fonts,
|
||||
username: networking.username.clone(),
|
||||
password: "".to_owned(),
|
||||
@ -158,6 +175,27 @@ impl MainMenuUi {
|
||||
.w_h(123.0 * 3.0, 35.0 * 3.0)
|
||||
.top_right_with_margins(30.0, 30.0)
|
||||
.set(self.ids.v_logo, ui_widgets);
|
||||
|
||||
Image::new(self.rot_imgs.tt_side.cw90)
|
||||
.w_h(50.0, 50.0)
|
||||
.top_left_with_margins_on(ui_widgets.window, 25.0, 25.0)
|
||||
.set(self.ids.test2, ui_widgets);
|
||||
// TODO: remove me
|
||||
// Test image frame
|
||||
// Edge images [t, b, r, l]
|
||||
// Corner images [tr, tl, br, bl]
|
||||
let edge = &self.rot_imgs.tt_side;
|
||||
let corner = &self.rot_imgs.tt_corner;
|
||||
ImageFrame::new(
|
||||
[edge.cw180, edge.none, edge.cw270, edge.cw90],
|
||||
[corner.none, corner.cw270, corner.cw90, corner.cw180],
|
||||
Color::Rgba(0.8, 0.7, 0.4, 1.0),
|
||||
30.0,
|
||||
)
|
||||
.w_h(250.0, 200.0)
|
||||
.down(5.0)
|
||||
.set(self.ids.frame_test, ui_widgets);
|
||||
|
||||
Text::new(version)
|
||||
.top_right_with_margins_on(ui_widgets.window, 5.0, 5.0)
|
||||
.font_size(14)
|
||||
|
@ -97,12 +97,27 @@ pub fn create_quad(
|
||||
|
||||
let (l, b, r, t) = aabr_to_lbrt(rect);
|
||||
let (uv_l, uv_b, uv_r, uv_t) = aabr_to_lbrt(uv_rect);
|
||||
Quad::new(
|
||||
v([r, t], [uv_r, uv_t]),
|
||||
v([l, t], [uv_l, uv_t]),
|
||||
v([l, b], [uv_l, uv_b]),
|
||||
v([r, b], [uv_r, uv_b]),
|
||||
)
|
||||
|
||||
match (uv_b > uv_t, uv_l > uv_r) {
|
||||
(true, true) => Quad::new(
|
||||
v([r, t], [uv_l, uv_b]),
|
||||
v([l, t], [uv_l, uv_t]),
|
||||
v([l, b], [uv_r, uv_t]),
|
||||
v([r, b], [uv_r, uv_b]),
|
||||
),
|
||||
(false, false) => Quad::new(
|
||||
v([r, t], [uv_l, uv_b]),
|
||||
v([l, t], [uv_l, uv_t]),
|
||||
v([l, b], [uv_r, uv_t]),
|
||||
v([r, b], [uv_r, uv_b]),
|
||||
),
|
||||
_ => Quad::new(
|
||||
v([r, t], [uv_r, uv_t]),
|
||||
v([l, t], [uv_l, uv_t]),
|
||||
v([l, b], [uv_l, uv_b]),
|
||||
v([r, b], [uv_r, uv_b]),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_tri(
|
||||
|
@ -186,7 +186,7 @@ impl Renderer {
|
||||
)
|
||||
}
|
||||
|
||||
/// Queue the clearing of the color and depth targets ready for a new frame to be rendered.
|
||||
/// Queue the clearing of the depth target ready for a new frame to be rendered.
|
||||
pub fn clear(&mut self) {
|
||||
self.encoder.clear_depth(&self.tgt_depth_view, 1.0);
|
||||
self.encoder.clear_depth(&self.win_depth_view, 1.0);
|
||||
|
@ -1,246 +0,0 @@
|
||||
use dot_vox::DotVoxData;
|
||||
use guillotiere::{size2, AllocId, Allocation, AtlasAllocator};
|
||||
use hashbrown::HashMap;
|
||||
use image::{DynamicImage, RgbaImage};
|
||||
use log::{error, warn};
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Graphic {
|
||||
Image(Arc<DynamicImage>),
|
||||
Voxel(Arc<DotVoxData>, Option<u8>),
|
||||
Blank,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub struct Id(u32);
|
||||
|
||||
type Parameters = (Id, Vec2<u16>, Aabr<u64>);
|
||||
|
||||
pub 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>,
|
||||
}
|
||||
|
||||
pub struct GraphicCache {
|
||||
graphic_map: HashMap<Id, Graphic>,
|
||||
next_id: u32,
|
||||
|
||||
atlas: AtlasAllocator,
|
||||
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 {
|
||||
Self {
|
||||
graphic_map: HashMap::default(),
|
||||
next_id: 0,
|
||||
atlas: AtlasAllocator::new(size2(i32::from(size.x), i32::from(size.y))),
|
||||
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 {
|
||||
let id = self.next_id;
|
||||
self.next_id = id.wrapping_add(1);
|
||||
|
||||
let id = Id(id);
|
||||
self.graphic_map.insert(id, graphic);
|
||||
|
||||
id
|
||||
}
|
||||
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();
|
||||
self.cache_map.clear();
|
||||
self.atlas = AtlasAllocator::new(size2(i32::from(new_size.x), i32::from(new_size.y)));
|
||||
}
|
||||
|
||||
pub fn queue_res(
|
||||
&mut self,
|
||||
graphic_id: Id,
|
||||
dims: Vec2<u16>,
|
||||
source: Aabr<f64>,
|
||||
) -> Option<Aabr<u16>> {
|
||||
let key = (graphic_id, dims, source.map(|e| e.to_bits())); // TODO: Replace this with rounded representation of source
|
||||
|
||||
if let Some(details) = self.cache_map.get_mut(&key) {
|
||||
// Update frame
|
||||
details.frame = self.current_frame;
|
||||
|
||||
Some(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, min_samples)) => {
|
||||
super::renderer::draw_vox(&vox.as_ref().into(), dims, *min_samples)
|
||||
}
|
||||
None => {
|
||||
warn!("A graphic was requested via an id which is not in use");
|
||||
return None;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
};
|
||||
|
||||
// Allocate rectangle.
|
||||
let (alloc_id, aabr) = match self
|
||||
.atlas
|
||||
.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.transfer_ready.push((key, aabr));
|
||||
|
||||
// Insert area into map for retrieval.
|
||||
self.cache_map.insert(
|
||||
key,
|
||||
CachedDetails {
|
||||
alloc_id,
|
||||
frame: self.current_frame,
|
||||
aabr,
|
||||
},
|
||||
);
|
||||
|
||||
Some(aabr)
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
@ -1,4 +1,274 @@
|
||||
mod graphic;
|
||||
mod renderer;
|
||||
|
||||
pub use graphic::{Graphic, GraphicCache, Id};
|
||||
use dot_vox::DotVoxData;
|
||||
use guillotiere::{size2, AllocId, Allocation, AtlasAllocator};
|
||||
use hashbrown::HashMap;
|
||||
use image::{DynamicImage, RgbaImage};
|
||||
use log::{error, warn};
|
||||
use std::sync::Arc;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Graphic {
|
||||
Image(Arc<DynamicImage>),
|
||||
Voxel(Arc<DotVoxData>, Option<Quaternion<f32>>, Option<u8>),
|
||||
Blank,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Rotation {
|
||||
None,
|
||||
Cw90,
|
||||
Cw180,
|
||||
Cw270,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub struct Id(u32);
|
||||
|
||||
type Parameters = (Id, Vec2<u16>, Aabr<u64>);
|
||||
|
||||
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>,
|
||||
}
|
||||
|
||||
pub struct GraphicCache {
|
||||
graphic_map: HashMap<Id, Graphic>,
|
||||
next_id: u32,
|
||||
|
||||
atlas: AtlasAllocator,
|
||||
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 {
|
||||
Self {
|
||||
graphic_map: HashMap::default(),
|
||||
next_id: 0,
|
||||
atlas: AtlasAllocator::new(size2(i32::from(size.x), i32::from(size.y))),
|
||||
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 {
|
||||
let id = self.next_id;
|
||||
self.next_id = id.wrapping_add(1);
|
||||
|
||||
let id = Id(id);
|
||||
self.graphic_map.insert(id, graphic);
|
||||
|
||||
id
|
||||
}
|
||||
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();
|
||||
self.cache_map.clear();
|
||||
self.atlas = AtlasAllocator::new(size2(i32::from(new_size.x), i32::from(new_size.y)));
|
||||
}
|
||||
|
||||
pub fn queue_res(
|
||||
&mut self,
|
||||
graphic_id: Id,
|
||||
dims: Vec2<u16>,
|
||||
source: Aabr<f64>,
|
||||
rotation: Rotation,
|
||||
) -> Option<Aabr<u16>> {
|
||||
let dims = match rotation {
|
||||
Rotation::Cw90 | Rotation::Cw270 => Vec2::new(dims.y, dims.x),
|
||||
Rotation::None | Rotation::Cw180 => dims,
|
||||
};
|
||||
let key = (graphic_id, dims, source.map(|e| e.to_bits())); // TODO: Replace this with rounded representation of source
|
||||
|
||||
let rotated_aabr = |Aabr { min, max }| match rotation {
|
||||
Rotation::None => Aabr { min, max },
|
||||
Rotation::Cw90 => Aabr {
|
||||
min: Vec2::new(min.x, max.y),
|
||||
max: Vec2::new(max.x, min.y),
|
||||
},
|
||||
Rotation::Cw180 => Aabr { min: max, max: min },
|
||||
Rotation::Cw270 => Aabr {
|
||||
min: Vec2::new(max.x, min.y),
|
||||
max: Vec2::new(min.x, max.y),
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(details) = self.cache_map.get_mut(&key) {
|
||||
// Update frame
|
||||
details.frame = self.current_frame;
|
||||
|
||||
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, ori, min_samples)) => {
|
||||
renderer::draw_vox(&vox.as_ref().into(), dims, *ori, *min_samples)
|
||||
}
|
||||
None => {
|
||||
warn!("A graphic was requested via an id which is not in use");
|
||||
return None;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
};
|
||||
|
||||
// Allocate rectangle.
|
||||
let (alloc_id, aabr) = match self
|
||||
.atlas
|
||||
.allocate(size2(i32::from(dims.x), i32::from(dims.y)))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.transfer_ready.push((key, aabr));
|
||||
|
||||
// Insert area into map for retrieval.
|
||||
self.cache_map.insert(
|
||||
key,
|
||||
CachedDetails {
|
||||
alloc_id,
|
||||
frame: self.current_frame,
|
||||
aabr,
|
||||
},
|
||||
);
|
||||
|
||||
Some(rotated_aabr(aabr))
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,12 @@ impl<'a> Pipeline for Voxel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_vox(segment: &Segment, output_size: Vec2<u16>, min_samples: Option<u8>) -> RgbaImage {
|
||||
pub fn draw_vox(
|
||||
segment: &Segment,
|
||||
output_size: Vec2<u16>,
|
||||
ori: Option<Quaternion<f32>>,
|
||||
min_samples: Option<u8>,
|
||||
) -> RgbaImage {
|
||||
let scale = min_samples.map_or(1.0, |s| s as f32).sqrt().ceil() as usize;
|
||||
let dims = output_size.map(|e| e as usize * scale).into_array();
|
||||
let mut color = Buffer2d::new(dims, [0; 4]);
|
||||
@ -73,7 +78,8 @@ pub fn draw_vox(segment: &Segment, output_size: Vec2<u16>, min_samples: Option<u
|
||||
top: 1.0,
|
||||
near: 0.0,
|
||||
far: 1.0,
|
||||
}) * Mat4::rotation_x(-std::f32::consts::PI / 2.0)
|
||||
}) * Mat4::from(ori.unwrap_or(Quaternion::identity()))
|
||||
* Mat4::rotation_x(-std::f32::consts::PI / 2.0) // TODO: remove
|
||||
* Mat4::scaling_3d([2.0 / w, 2.0 / h, 2.0 / d])
|
||||
* Mat4::translation_3d([-w / 2.0, -h / 2.0, -d / 2.0]);
|
||||
Voxel { mvp }.draw::<rasterizer::Triangles<_>, _>(
|
||||
|
@ -31,7 +31,7 @@ pub enum VoxelMs9Graphic {}
|
||||
impl<'a> GraphicCreator<'a> for VoxelGraphic {
|
||||
type Specifier = &'a str;
|
||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, None))
|
||||
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, None, None))
|
||||
}
|
||||
}
|
||||
impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
|
||||
@ -39,6 +39,7 @@ impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
|
||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||
Ok(Graphic::Voxel(
|
||||
load::<DotVoxData>(specifier.0)?,
|
||||
None,
|
||||
Some(specifier.1),
|
||||
))
|
||||
}
|
||||
@ -46,16 +47,31 @@ impl<'a> GraphicCreator<'a> for VoxelMsGraphic {
|
||||
impl<'a> GraphicCreator<'a> for VoxelMs4Graphic {
|
||||
type Specifier = &'a str;
|
||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, Some(4)))
|
||||
Ok(Graphic::Voxel(
|
||||
load::<DotVoxData>(specifier)?,
|
||||
None,
|
||||
Some(4),
|
||||
))
|
||||
}
|
||||
}
|
||||
impl<'a> GraphicCreator<'a> for VoxelMs9Graphic {
|
||||
type Specifier = &'a str;
|
||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||
Ok(Graphic::Voxel(load::<DotVoxData>(specifier)?, Some(9)))
|
||||
Ok(Graphic::Voxel(
|
||||
load::<DotVoxData>(specifier)?,
|
||||
None,
|
||||
Some(9),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rotations {
|
||||
pub none: conrod_core::image::Id,
|
||||
pub cw90: conrod_core::image::Id,
|
||||
pub cw180: conrod_core::image::Id,
|
||||
pub cw270: conrod_core::image::Id,
|
||||
}
|
||||
|
||||
/// This macro will automatically load all specified assets, get the corresponding ImgIds and
|
||||
/// create a struct with all of them.
|
||||
///
|
||||
@ -80,7 +96,7 @@ macro_rules! image_ids {
|
||||
($($v:vis struct $Ids:ident { $( <$T:ty> $( $name:ident: $specifier:expr ),* $(,)? )* })*) => {
|
||||
$(
|
||||
$v struct $Ids {
|
||||
$($( $v $name: conrod_core::image::Id, )*)*
|
||||
$($( $v $name: conrod_core::image::Id, )*)*
|
||||
}
|
||||
|
||||
impl $Ids {
|
||||
@ -94,3 +110,24 @@ macro_rules! image_ids {
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: combine with the img_ids macro above using a marker for specific fields that should be `Rotations` instead of `widget::Id`
|
||||
#[macro_export]
|
||||
macro_rules! rotation_image_ids {
|
||||
($($v:vis struct $Ids:ident { $( <$T:ty> $( $name:ident: $specifier:expr ),* $(,)? )* })*) => {
|
||||
$(
|
||||
$v struct $Ids {
|
||||
$($( $v $name: crate::ui::img_ids::Rotations, )*)*
|
||||
}
|
||||
|
||||
impl $Ids {
|
||||
pub fn load(ui: &mut crate::ui::Ui) -> Result<Self, common::assets::Error> {
|
||||
use crate::ui::img_ids::GraphicCreator;
|
||||
Ok(Self {
|
||||
$($( $name: ui.add_graphic_with_rotations(<$T as GraphicCreator>::new_graphic($specifier)?), )*)*
|
||||
})
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ use conrod_core::{
|
||||
widget::{self, id::Generator},
|
||||
Rect, UiBuilder, UiCell,
|
||||
};
|
||||
use graphic::Id as GraphicId;
|
||||
use graphic::Rotation;
|
||||
use log::warn;
|
||||
use std::{
|
||||
fs::File,
|
||||
@ -93,7 +93,7 @@ impl assets::Asset for Font {
|
||||
|
||||
pub struct Ui {
|
||||
ui: conrod_core::Ui,
|
||||
image_map: Map<GraphicId>,
|
||||
image_map: Map<(graphic::Id, Rotation)>,
|
||||
cache: Cache,
|
||||
// Draw commands for the next render
|
||||
draw_commands: Vec<DrawCommand>,
|
||||
@ -133,7 +133,7 @@ impl Ui {
|
||||
ui,
|
||||
image_map: Map::new(),
|
||||
cache: Cache::new(renderer)?,
|
||||
draw_commands: vec![],
|
||||
draw_commands: Vec::new(),
|
||||
model: renderer.create_dynamic_model(100)?,
|
||||
interface_locals: renderer.create_consts(&[UiLocals::default()])?,
|
||||
default_globals: renderer.create_consts(&[Globals::default()])?,
|
||||
@ -161,7 +161,18 @@ impl Ui {
|
||||
}
|
||||
|
||||
pub fn add_graphic(&mut self, graphic: Graphic) -> image::Id {
|
||||
self.image_map.insert(self.cache.add_graphic(graphic))
|
||||
self.image_map
|
||||
.insert((self.cache.add_graphic(graphic), Rotation::None))
|
||||
}
|
||||
|
||||
pub fn add_graphic_with_rotations(&mut self, graphic: Graphic) -> img_ids::Rotations {
|
||||
let graphic_id = self.cache.add_graphic(graphic);
|
||||
img_ids::Rotations {
|
||||
none: self.image_map.insert((graphic_id, Rotation::None)),
|
||||
cw90: self.image_map.insert((graphic_id, Rotation::Cw90)),
|
||||
cw180: self.image_map.insert((graphic_id, Rotation::Cw180)),
|
||||
cw270: self.image_map.insert((graphic_id, Rotation::Cw270)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_font(&mut self, font: Arc<Font>) -> font::Id {
|
||||
@ -258,6 +269,15 @@ impl Ui {
|
||||
self.draw_commands.clear();
|
||||
let mut mesh = Mesh::new();
|
||||
|
||||
let (half_res, x_align, y_align) = {
|
||||
let res = renderer.get_resolution();
|
||||
(
|
||||
res.map(|e| e as f32 / 2.0),
|
||||
(res.x & 1) as f32 * 0.5,
|
||||
(res.y & 1) as f32 * 0.5,
|
||||
)
|
||||
};
|
||||
|
||||
// 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 {
|
||||
@ -373,10 +393,15 @@ impl Ui {
|
||||
let vy = |y: f64| (y / ui_win_h * 2.0) as f32;
|
||||
let gl_aabr = |rect: Rect| {
|
||||
let (l, r, b, t) = rect.l_r_b_t();
|
||||
Aabr {
|
||||
min: Vec2::new(vx(l), vy(b)),
|
||||
max: Vec2::new(vx(r), vy(t)),
|
||||
}
|
||||
let min = Vec2::new(
|
||||
((vx(l) * half_res.x + x_align).round() - x_align) / half_res.x,
|
||||
((vy(b) * half_res.y + y_align).round() - y_align) / half_res.y,
|
||||
);
|
||||
let max = Vec2::new(
|
||||
((vx(r) * half_res.x + x_align).round() - x_align) / half_res.x,
|
||||
((vy(t) * half_res.y + y_align).round() - y_align) / half_res.y,
|
||||
);
|
||||
Aabr { min, max }
|
||||
};
|
||||
|
||||
match kind {
|
||||
@ -385,7 +410,7 @@ impl Ui {
|
||||
color,
|
||||
source_rect: _, // TODO: <-- use this
|
||||
} => {
|
||||
let graphic_id = self
|
||||
let (graphic_id, rotation) = self
|
||||
.image_map
|
||||
.get(&image_id)
|
||||
.expect("Image does not exist in image map");
|
||||
@ -407,9 +432,10 @@ impl Ui {
|
||||
let color =
|
||||
srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into());
|
||||
|
||||
let gl_aabr = gl_aabr(rect);
|
||||
let resolution = Vec2::new(
|
||||
(rect.w() * p_scale_factor).round() as u16,
|
||||
(rect.h() * p_scale_factor).round() as u16,
|
||||
(gl_aabr.size().w * half_res.x).round() as u16,
|
||||
(gl_aabr.size().h * half_res.y).round() as u16,
|
||||
);
|
||||
// Transform the source rectangle into uv coordinate.
|
||||
// TODO: Make sure this is right.
|
||||
@ -435,22 +461,26 @@ impl Ui {
|
||||
cache_tex.get_dimensions().map(|e| e as f32).into_tuple();
|
||||
|
||||
// Cache graphic at particular resolution.
|
||||
let uv_aabr =
|
||||
match graphic_cache.queue_res(*graphic_id, resolution, source_aabr) {
|
||||
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,
|
||||
),
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
let uv_aabr = match graphic_cache.queue_res(
|
||||
*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,
|
||||
),
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
|
||||
mesh.push_quad(create_ui_quad(gl_aabr(rect), uv_aabr, color, UiMode::Image));
|
||||
mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, UiMode::Image));
|
||||
}
|
||||
PrimitiveKind::Text {
|
||||
color,
|
||||
|
@ -1,8 +1,6 @@
|
||||
//! A widget for selecting a single value along some linear range.
|
||||
use conrod_core::{
|
||||
builder_methods, image,
|
||||
position::Range,
|
||||
utils,
|
||||
widget::{self, Image, Rectangle},
|
||||
widget_ids, Color, Colorable, Positionable, Rect, Sizeable, UiCell, Widget, WidgetCommon,
|
||||
};
|
||||
@ -34,7 +32,7 @@ pub struct ImageFrame {
|
||||
// TODO: would it be useful to have an optional close button be a part of this?
|
||||
}
|
||||
|
||||
enum Center {
|
||||
pub enum Center {
|
||||
Plain(Color),
|
||||
Image(image::Id, Option<Rect>),
|
||||
}
|
||||
@ -54,19 +52,19 @@ impl From<(image::Id, Rect)> for Center {
|
||||
}
|
||||
}
|
||||
|
||||
struct BorderSize {
|
||||
pub struct BorderSize {
|
||||
top: f64,
|
||||
bottom: f64,
|
||||
right: f64,
|
||||
left: f64,
|
||||
}
|
||||
impl From<f64> for BorderSize {
|
||||
fn from(width: f64) -> Self {
|
||||
fn from(thickness: f64) -> Self {
|
||||
BorderSize {
|
||||
top: width,
|
||||
bottom: width,
|
||||
right: width,
|
||||
left: width,
|
||||
top: thickness,
|
||||
bottom: thickness,
|
||||
right: thickness,
|
||||
left: thickness,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,7 +110,7 @@ pub struct State {
|
||||
}
|
||||
|
||||
impl ImageFrame {
|
||||
fn new(
|
||||
pub fn new(
|
||||
edges: [image::Id; 4],
|
||||
corners: [image::Id; 4],
|
||||
center: impl Into<Center>,
|
||||
@ -222,6 +220,7 @@ impl Widget for ImageFrame {
|
||||
.wh(rect.dim())
|
||||
.parent(id)
|
||||
.graphics_for(id)
|
||||
.and_then(maybe_src_rect, |w, r| w.source_rectangle(r))
|
||||
.color(maybe_color)
|
||||
.set(widget_id, ui);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user