mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Fix map image artifacts and remove unneeded allocations.
Specifically, we address three concerns (the image stretching during rotation, artifacts around the image due to clamping to the nearest border color when the image is drawn to a larger space than the image itself takes up, and potential artifacts around a rotated image which accidentally ended up in an atlas and didn't have enough extra space to guarantee the rotation would work). The first concern was addressed by fixing the dimensions of the map images drawn from the UI (so that we always use a square source rectangle, rather than a rectangular one according to the dimensions of the map). We also fixed the way rotation was done in the fragment shader for north-facing sources to make it properly handle aspect ratio (this was already done for north-facing targets). Together, these fix rendering issues peculiar to rectangular maps. The second and third concerns were jointly addressed by adding an optional border color to every 2D image drawn by the UI. This turns out not to waste extra space even though we hold a full f32 color (to avoid an extra dependency on gfx's PackedColor), since voxel images already take up more space than Optiion<[f32; 4]> requires. This is then implemented automatically using the "border color" wrapping method in the attached sampler. Since this is implemented in graphics hardware, it only works (at least naively) if the actual image bounds match the texture bounds. Therefore, we altered the way the graphics cache stores images with a border color to guarantee that they are always in their own texture, whose size exactly matches their extent. Since the easiest currently exposed way to set a border color is to do so for an immutable texture, we went a bit further and added a new "immutable" texture storage type used for these cases; currently, it is always and automatically used only when there is a specified border color, but in theory there's no reason we couldn't provide immutable-only images that use the default wrapping mdoe (though clamp to border is admittedly not a great default). To fix the maps case specifically, we set the border color to a translucent version of the ocean border color. This may need tweaking going forward, which shouldn't be hard. As part of this process, we had to modify graphics replacement to make sure immutable images are *removed* when invalidated, rather than just having a validity flag unset (this is normally done by the UI to try to reuse allocations in place if images are updated in benign ways, since the texture atlases used for Ui do not support deallocation; currently this is only used for item images, so there should be no overlap with immutable image replacement, so this was purely precautionary). Since we were already touching the relevant code, we also updated the image dependency to a newer version that provides more ways to avoid allocations, and made a few other changes that should hopefully eliminate redundant most of the intermediate buffer allocations we were performing for what should be zero-cost conversions. This may slightly improve performance in some cases.
This commit is contained in:
parent
ad18ce9399
commit
cf74d55f2e
54
Cargo.lock
generated
54
Cargo.lock
generated
@ -381,6 +381,12 @@ version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db7a1029718df60331e557c9e83a55523c955e5dd2a7bfeffad6bbd50b538ae9"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "0.5.3"
|
||||
@ -1041,9 +1047,9 @@ checksum = "72aa14c04dfae8dd7d8a2b1cb7ca2152618cd01336dbfe704b8dcbf8d41dbd69"
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.7.20"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
|
||||
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder 1.3.4",
|
||||
@ -2053,13 +2059,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.22.5"
|
||||
version = "0.23.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08ed2ada878397b045454ac7cfb011d73132c59f31a955d230bd1f1c2e68eb4a"
|
||||
checksum = "543904170510c1b5fb65140485d84de4a57fddb2ed685481e9020ce3d2c9f64c"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder 1.3.4",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-rational 0.3.0",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
@ -2073,15 +2080,6 @@ dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inflate"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.8.3"
|
||||
@ -2449,6 +2447,15 @@ dependencies = [
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.6.22"
|
||||
@ -2664,7 +2671,7 @@ dependencies = [
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-rational 0.2.4",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
@ -2722,6 +2729,17 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
|
||||
dependencies = [
|
||||
"autocfg 1.0.0",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.12"
|
||||
@ -3069,14 +3087,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.15.3"
|
||||
version = "0.16.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef859a23054bbfee7811284275ae522f0434a3c8e7f4b74bd4a35ae7e1c4a283"
|
||||
checksum = "dfe7f9f1c730833200b134370e1d5098964231af8450bce9b78ee3ab5278b970"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"deflate",
|
||||
"inflate",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -33,19 +33,24 @@ void main() {
|
||||
gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos/* * projected_pos.w*/, -1.0, /*projected_pos.w*/1.0);
|
||||
} else if (v_mode == uint(3)) {
|
||||
// HACK: North facing source rectangle.
|
||||
vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2]));
|
||||
mat2 look_at = mat2(look_at_dir.y, look_at_dir.x, -look_at_dir.x, look_at_dir.y);
|
||||
f_uv = v_center + look_at * (v_uv - v_center);
|
||||
gl_Position = vec4(v_pos, -1.0, 1.0);
|
||||
vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2]));
|
||||
// TODO: Consider cleaning up matrix to something more efficient (e.g. a mat3).
|
||||
vec2 aspect_ratio = textureSize(u_tex, 0).yx;
|
||||
mat2 look_at = mat2(look_at_dir.y, look_at_dir.x, -look_at_dir.x, look_at_dir.y);
|
||||
vec2 v_centered = (v_uv - v_center) / aspect_ratio;
|
||||
vec2 v_rotated = look_at * v_centered;
|
||||
f_uv = aspect_ratio * v_rotated + v_center;
|
||||
} else if (v_mode == uint(5)) {
|
||||
// HACK: North facing target rectangle.
|
||||
f_uv = v_uv;
|
||||
float aspect_ratio = screen_res.x / screen_res.y;
|
||||
vec2 look_at_dir = normalize(vec2(-view_mat[0][2], -view_mat[1][2]));
|
||||
// TODO: Consider cleaning up matrix to something more efficient (e.g. a mat3).
|
||||
vec2 aspect_ratio = screen_res.yx;
|
||||
mat2 look_at = mat2(look_at_dir.y, -look_at_dir.x, look_at_dir.x, look_at_dir.y);
|
||||
vec2 v_len = v_pos - v_center;
|
||||
vec2 v_proj = look_at * vec2(v_len.x, v_len.y / aspect_ratio);
|
||||
gl_Position = vec4(v_center + vec2(v_proj.x, v_proj.y * aspect_ratio), -1.0, 1.0);
|
||||
vec2 v_centered = (v_pos - v_center) / aspect_ratio;
|
||||
vec2 v_rotated = look_at * v_centered;
|
||||
gl_Position = vec4(aspect_ratio * v_rotated + v_center, -1.0, 1.0);
|
||||
} else {
|
||||
// Interface element
|
||||
f_uv = v_uv;
|
||||
|
@ -13,7 +13,7 @@ uvth = "3.1.1"
|
||||
futures-util = "0.3"
|
||||
futures-executor = "0.3"
|
||||
futures-timer = "2.0"
|
||||
image = { version = "0.22.5", default-features = false, features = ["png"] }
|
||||
image = { version = "0.23.8", default-features = false, features = ["png"] }
|
||||
num = "0.2.0"
|
||||
num_cpus = "1.10.1"
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
|
@ -15,7 +15,7 @@ roots = "0.0.5"
|
||||
specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control"], rev = "7a2e348ab2223818bad487695c66c43db88050a5" }
|
||||
vek = { version = "0.11.0", features = ["serde"] }
|
||||
dot_vox = "4.0"
|
||||
image = { version = "0.22.5", default-features = false, features = ["png"] }
|
||||
image = { version = "0.23.8", default-features = false, features = ["png"] }
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
serde_json = "1.0.50"
|
||||
ron = { version = "0.6", default-features = false }
|
||||
|
@ -53,7 +53,7 @@ server = { package = "veloren-server", path = "../server", optional = true }
|
||||
glsl-include = "0.3.1"
|
||||
failure = "0.1.6"
|
||||
dot_vox = "4.0"
|
||||
image = { version = "0.22.5", default-features = false, features = ["ico", "png"] }
|
||||
image = { version = "0.23.8", default-features = false, features = ["ico", "png"] }
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
ron = { version = "0.6", default-features = false }
|
||||
|
@ -52,7 +52,7 @@ enum ImageSpec {
|
||||
impl ImageSpec {
|
||||
fn create_graphic(&self) -> Graphic {
|
||||
match self {
|
||||
ImageSpec::Png(specifier) => Graphic::Image(graceful_load_img(&specifier)),
|
||||
ImageSpec::Png(specifier) => Graphic::Image(graceful_load_img(&specifier), None),
|
||||
ImageSpec::Vox(specifier) => Graphic::Voxel(
|
||||
graceful_load_segment_no_skin(&specifier),
|
||||
Transform {
|
||||
|
@ -197,8 +197,10 @@ impl<'a> Widget for Map<'a> {
|
||||
.read_storage::<comp::Pos>()
|
||||
.get(self.client.entity())
|
||||
.map_or(Vec3::zero(), |pos| pos.0);
|
||||
let w_src = worldsize.x / TerrainChunkSize::RECT_SIZE.x as f64 / zoom;
|
||||
let h_src = worldsize.y / TerrainChunkSize::RECT_SIZE.y as f64 / zoom;
|
||||
let max_zoom = (worldsize / TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
|
||||
.reduce_partial_max()/*.min(f64::MAX)*/;
|
||||
let w_src = max_zoom / zoom;
|
||||
let h_src = max_zoom / zoom;
|
||||
let rect_src = position::Rect::from_xy_dim(
|
||||
[
|
||||
player_pos.x as f64 / TerrainChunkSize::RECT_SIZE.x as f64,
|
||||
|
@ -134,7 +134,7 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
// somehow if you zoom in too far. Or both.
|
||||
let min_zoom = 1.0;
|
||||
let max_zoom = (worldsize / TerrainChunkSize::RECT_SIZE.map(|e| e as f64))
|
||||
.reduce_partial_min()/*.min(f64::MAX)*/;
|
||||
.reduce_partial_max()/*.min(f64::MAX)*/;
|
||||
|
||||
// NOTE: Not sure if a button can be clicked while disabled, but we still double
|
||||
// check for both kinds of zoom to make sure that not only was the
|
||||
@ -190,8 +190,8 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
.map_or(Vec3::zero(), |pos| pos.0);
|
||||
|
||||
// Get map image source rectangle dimensons.
|
||||
let w_src = worldsize.x / TerrainChunkSize::RECT_SIZE.x as f64 / zoom;
|
||||
let h_src = worldsize.y / TerrainChunkSize::RECT_SIZE.y as f64 / zoom;
|
||||
let w_src = max_zoom / zoom;
|
||||
let h_src = max_zoom / zoom;
|
||||
|
||||
// Set map image to be centered around player coordinates.
|
||||
let rect_src = position::Rect::from_xy_dim(
|
||||
|
@ -44,7 +44,10 @@ use crate::{
|
||||
ecs::comp as vcomp,
|
||||
i18n::{i18n_asset_key, LanguageMetadata, VoxygenLocalization},
|
||||
render::{Consts, Globals, RenderMode, Renderer},
|
||||
scene::camera::{self, Camera},
|
||||
scene::{
|
||||
camera::{self, Camera},
|
||||
lod,
|
||||
},
|
||||
ui::{fonts::ConrodVoxygenFonts, slot, Graphic, Ingameable, ScaleMode, Ui},
|
||||
window::{Event as WinEvent, GameInput},
|
||||
GlobalState,
|
||||
@ -542,9 +545,16 @@ impl Hud {
|
||||
ui.set_scaling_mode(settings.gameplay.ui_scale);
|
||||
// Generate ids.
|
||||
let ids = Ids::new(ui.id_generator());
|
||||
// NOTE: Use a border the same color as the LOD ocean color (but with a
|
||||
// translucent alpha since UI have transparency and LOD doesn't).
|
||||
let mut water_color = lod::water_color();
|
||||
water_color.a = 0.5;
|
||||
// Load world map
|
||||
let world_map = (
|
||||
ui.add_graphic_with_rotations(Graphic::Image(client.world_map.0.clone())),
|
||||
ui.add_graphic_with_rotations(Graphic::Image(
|
||||
client.world_map.0.clone(),
|
||||
Some(water_color),
|
||||
)),
|
||||
client.world_map.1.map(u32::from),
|
||||
);
|
||||
// Load images.
|
||||
|
@ -204,9 +204,10 @@ impl<'a> MainMenuUi {
|
||||
// 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!");
|
||||
let bg_img_id = ui.add_graphic(Graphic::Image(load_expect(
|
||||
bg_imgs.choose(&mut rng).unwrap(),
|
||||
)));
|
||||
let bg_img_id = ui.add_graphic(Graphic::Image(
|
||||
load_expect(bg_imgs.choose(&mut rng).unwrap()),
|
||||
None,
|
||||
));
|
||||
//let chosen_tip = *tips.choose(&mut rng).unwrap();
|
||||
// Load language
|
||||
let voxygen_i18n = load_expect::<VoxygenLocalization>(&i18n_asset_key(
|
||||
|
@ -33,6 +33,12 @@ where
|
||||
wrap_mode: Option<gfx::texture::WrapMode>,
|
||||
border: Option<gfx::texture::PackedColor>,
|
||||
) -> Result<Self, RenderError> {
|
||||
// TODO: Actualy handle images that aren't in rgba format properly.
|
||||
let buffer = image.as_flat_samples_u8().ok_or_else(|| {
|
||||
RenderError::CustomError(
|
||||
"We currently do not support color formats using more than 4 bytes / pixel.".into(),
|
||||
)
|
||||
})?;
|
||||
let (tex, srv) = factory
|
||||
.create_texture_immutable_u8::<F>(
|
||||
gfx::texture::Kind::D2(
|
||||
@ -41,7 +47,11 @@ where
|
||||
gfx::texture::AaMode::Single,
|
||||
),
|
||||
gfx::texture::Mipmap::Provided,
|
||||
&[&image.raw_pixels()],
|
||||
// Guarenteed to be correct, since all the conversions from DynamicImage to
|
||||
// FlatSamples<u8> go through the underlying ImageBuffer's implementation of
|
||||
// as_flat_samples(), which guarantees that the resulting FlatSamples is
|
||||
// well-formed.
|
||||
&[buffer.as_slice()],
|
||||
)
|
||||
.map_err(RenderError::CombinedError)?;
|
||||
|
||||
|
@ -80,9 +80,14 @@ impl LodData {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make constant when possible.
|
||||
pub fn water_color() -> Rgba<f32> {
|
||||
/* Rgba::new(0.2, 0.5, 1.0, 0.0) */
|
||||
srgba_to_linear(Rgba::new(0.0, 0.25, 0.5, 0.0)/* * 0.5*/)
|
||||
}
|
||||
|
||||
impl Lod {
|
||||
pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self {
|
||||
let water_color = /*Rgba::new(0.2, 0.5, 1.0, 0.0)*/srgba_to_linear(Rgba::new(0.0, 0.25, 0.5, 0.0)/* * 0.5*/);
|
||||
Self {
|
||||
model: None,
|
||||
locals: renderer.create_consts(&[Locals::default()]).unwrap(),
|
||||
@ -93,7 +98,7 @@ impl Lod {
|
||||
&client.lod_alt,
|
||||
&client.lod_horizon,
|
||||
settings.graphics.lod_detail.max(100).min(2500),
|
||||
[water_color.r, water_color.g, water_color.b, water_color.a].into(),
|
||||
water_color().into_array().into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ mod renderer;
|
||||
|
||||
pub use renderer::{SampleStrat, Transform};
|
||||
|
||||
use crate::render::{Renderer, Texture};
|
||||
use crate::render::{RenderError, Renderer, Texture};
|
||||
use common::figure::Segment;
|
||||
use guillotiere::{size2, SimpleAtlasAllocator};
|
||||
use hashbrown::{hash_map::Entry, HashMap};
|
||||
@ -15,7 +15,14 @@ use vek::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Graphic {
|
||||
Image(Arc<DynamicImage>),
|
||||
/// NOTE: The second argument is an optional border color. If this is set,
|
||||
/// we force the image into its own texture and use the border color
|
||||
/// whenever we sample beyond the image extent. This can be useful, for
|
||||
/// example, for the map and minimap, which both rotate and may be
|
||||
/// non-square (meaning if we want to display the whole map and render to a
|
||||
/// square, we may render out of bounds unless we perform proper
|
||||
/// clipping).
|
||||
Image(Arc<DynamicImage>, Option<Rgba<f32>>),
|
||||
// Note: none of the users keep this Arc currently
|
||||
Voxel(Arc<Segment>, Transform, SampleStrat),
|
||||
Blank,
|
||||
@ -53,20 +60,73 @@ pub struct TexId(usize);
|
||||
type Parameters = (Id, Vec2<u16>);
|
||||
type GraphicMap = HashMap<Id, Graphic>;
|
||||
|
||||
enum CacheLoc {
|
||||
enum CachedDetails {
|
||||
Atlas {
|
||||
// Index of the atlas this is cached in
|
||||
atlas_idx: usize,
|
||||
// Whether this texture is valid.
|
||||
valid: bool,
|
||||
// Where in the cache texture this is
|
||||
aabr: Aabr<u16>,
|
||||
},
|
||||
Texture {
|
||||
// Index of the (unique, non-atlas) texture this is cached in.
|
||||
index: usize,
|
||||
// Whether this texture is valid.
|
||||
valid: bool,
|
||||
},
|
||||
Immutable {
|
||||
// Index of the (unique, immutable, non-atlas) texture this is cached in.
|
||||
index: usize,
|
||||
},
|
||||
}
|
||||
struct CachedDetails {
|
||||
location: CacheLoc,
|
||||
valid: bool,
|
||||
|
||||
impl CachedDetails {
|
||||
/// Get information about this cache entry: texture index,
|
||||
/// whether the entry is valid, and its bounding box in the referenced
|
||||
/// texture.
|
||||
fn info(
|
||||
&self,
|
||||
atlases: &[(SimpleAtlasAllocator, usize)],
|
||||
dims: Vec2<u16>,
|
||||
) -> (usize, bool, Aabr<u16>) {
|
||||
match *self {
|
||||
CachedDetails::Atlas {
|
||||
atlas_idx,
|
||||
valid,
|
||||
aabr,
|
||||
} => (atlases[atlas_idx].1, valid, aabr),
|
||||
CachedDetails::Texture { index, valid } => {
|
||||
(index, valid, Aabr {
|
||||
min: Vec2::zero(),
|
||||
// Note texture should always match the cached dimensions
|
||||
max: dims,
|
||||
})
|
||||
},
|
||||
CachedDetails::Immutable { index } => {
|
||||
(index, true, Aabr {
|
||||
min: Vec2::zero(),
|
||||
// Note texture should always match the cached dimensions
|
||||
max: dims,
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to invalidate this cache entry.
|
||||
pub fn invalidate(&mut self) -> Result<(), ()> {
|
||||
match self {
|
||||
Self::Atlas { ref mut valid, .. } => {
|
||||
*valid = false;
|
||||
Ok(())
|
||||
},
|
||||
Self::Texture { ref mut valid, .. } => {
|
||||
*valid = false;
|
||||
Ok(())
|
||||
},
|
||||
Self::Immutable { .. } => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Caches graphics, only deallocates when changing screen resolution (completely
|
||||
@ -106,22 +166,18 @@ impl GraphicCache {
|
||||
}
|
||||
|
||||
pub fn replace_graphic(&mut self, id: Id, graphic: Graphic) {
|
||||
self.graphic_map.insert(id, graphic);
|
||||
if self.graphic_map.insert(id, graphic).is_none() {
|
||||
// This was not an update, so no need to search for keys.
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove from caches
|
||||
// Maybe make this more efficient if replace graphic is used more often
|
||||
let uses = self
|
||||
.cache_map
|
||||
.keys()
|
||||
.filter(|k| k.0 == id)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
for p in uses {
|
||||
if let Some(details) = self.cache_map.get_mut(&p) {
|
||||
// Reuse allocation
|
||||
details.valid = false;
|
||||
}
|
||||
}
|
||||
self.cache_map.retain(|&(key_id, _key_dims), details| {
|
||||
// If the entry does not reference id, or it does but we can successfully
|
||||
// invalidate, retain the entry; otherwise, discard this entry completely.
|
||||
key_id != id || details.invalidate().is_ok()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_graphic(&self, id: Id) -> Option<&Graphic> { self.graphic_map.get(&id) }
|
||||
@ -182,29 +238,31 @@ impl GraphicCache {
|
||||
// TODO: Verify rotation is being applied correctly.
|
||||
let transformed_aabr = |aabr| rotated_aabr(scaled_aabr(aabr));
|
||||
|
||||
let details = match self.cache_map.entry(key) {
|
||||
let Self {
|
||||
textures,
|
||||
atlases,
|
||||
cache_map,
|
||||
graphic_map,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let details = match cache_map.entry(key) {
|
||||
Entry::Occupied(details) => {
|
||||
let details = details.get();
|
||||
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,
|
||||
})
|
||||
},
|
||||
};
|
||||
let (idx, valid, aabr) = details.info(atlases, dims);
|
||||
|
||||
// Check if the cached version has been invalidated by replacing the underlying
|
||||
// graphic
|
||||
if !details.valid {
|
||||
if !valid {
|
||||
// Create image
|
||||
let image = draw_graphic(&self.graphic_map, graphic_id, dims)?;
|
||||
let (image, border) = draw_graphic(graphic_map, graphic_id, dims)?;
|
||||
// If the cache location is invalid, we know the underlying texture is mutable,
|
||||
// so we should be able to replace the graphic. However, we still want to make
|
||||
// sure that we are not reusing textures for images that specify a border
|
||||
// color.
|
||||
assert!(border.is_none());
|
||||
// Transfer to the gpu
|
||||
upload_image(renderer, aabr, &self.textures[idx], &image);
|
||||
upload_image(renderer, aabr, &textures[idx], &image);
|
||||
}
|
||||
|
||||
return Some((transformed_aabr(aabr.map(|e| e as f64)), TexId(idx)));
|
||||
@ -212,25 +270,39 @@ impl GraphicCache {
|
||||
Entry::Vacant(details) => details,
|
||||
};
|
||||
|
||||
// Create image
|
||||
let image = draw_graphic(&self.graphic_map, graphic_id, dims)?;
|
||||
// Construct image
|
||||
let (image, border_color) = draw_graphic(graphic_map, graphic_id, dims)?;
|
||||
|
||||
// Upload
|
||||
let atlas_size = atlas_size(renderer);
|
||||
|
||||
// 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)
|
||||
let location = if let Some(border_color) = border_color {
|
||||
// Create a new immutable texture.
|
||||
let texture = create_image(renderer, image, border_color).unwrap();
|
||||
// NOTE: All mutations happen only after the upload succeeds!
|
||||
let index = textures.len();
|
||||
textures.push(texture);
|
||||
CachedDetails::Immutable { index }
|
||||
} else if atlas_size
|
||||
.map2(dims, |a, d| a as f32 * ATLAS_CUTTOFF_FRAC >= d as f32)
|
||||
.reduce_and()
|
||||
{
|
||||
// Fit into an atlas
|
||||
let mut loc = None;
|
||||
for (atlas_idx, (ref mut atlas, _)) in self.atlases.iter_mut().enumerate() {
|
||||
for (atlas_idx, &mut (ref mut atlas, texture_idx)) in atlases.iter_mut().enumerate() {
|
||||
let dims = dims.map(|e| e.max(1));
|
||||
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 });
|
||||
loc = Some(CachedDetails::Atlas {
|
||||
atlas_idx,
|
||||
valid: true,
|
||||
aabr,
|
||||
});
|
||||
upload_image(renderer, aabr, &textures[texture_idx], &image);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -245,61 +317,65 @@ impl GraphicCache {
|
||||
.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 }
|
||||
// NOTE: All mutations happen only after the texture creation succeeds!
|
||||
let tex_idx = textures.len();
|
||||
let atlas_idx = atlases.len();
|
||||
textures.push(texture);
|
||||
atlases.push((atlas, tex_idx));
|
||||
upload_image(renderer, aabr, &textures[tex_idx], &image);
|
||||
CachedDetails::Atlas {
|
||||
atlas_idx,
|
||||
valid: true,
|
||||
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: All mutations happen only after the texture creation succeeds!
|
||||
let index = textures.len();
|
||||
textures.push(texture);
|
||||
upload_image(
|
||||
renderer,
|
||||
Aabr {
|
||||
min: Vec2::zero(),
|
||||
// Note texture should always match the cached dimensions
|
||||
max: dims,
|
||||
})
|
||||
},
|
||||
},
|
||||
&textures[index],
|
||||
&image,
|
||||
);
|
||||
CachedDetails::Texture { index, valid: true }
|
||||
};
|
||||
// Upload
|
||||
upload_image(renderer, aabr, &self.textures[idx], &image);
|
||||
|
||||
// Extract information from cache entry.
|
||||
let (idx, _, aabr) = location.info(atlases, dims);
|
||||
|
||||
// Insert into cached map
|
||||
details.insert(CachedDetails {
|
||||
location,
|
||||
valid: true,
|
||||
});
|
||||
details.insert(location);
|
||||
|
||||
Some((transformed_aabr(aabr.map(|e| e as f64)), TexId(idx)))
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a graphic at the specified dimensions
|
||||
fn draw_graphic(graphic_map: &GraphicMap, graphic_id: Id, dims: Vec2<u16>) -> Option<RgbaImage> {
|
||||
fn draw_graphic(
|
||||
graphic_map: &GraphicMap,
|
||||
graphic_id: Id,
|
||||
dims: Vec2<u16>,
|
||||
) -> Option<(RgbaImage, Option<Rgba<f32>>)> {
|
||||
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(resize_pixel_art(
|
||||
&image.to_rgba(),
|
||||
u32::from(dims.x),
|
||||
u32::from(dims.y),
|
||||
Some(&Graphic::Image(ref image, border_color)) => Some((
|
||||
resize_pixel_art(&image.to_rgba(), u32::from(dims.x), u32::from(dims.y)),
|
||||
border_color,
|
||||
)),
|
||||
Some(Graphic::Voxel(ref segment, trans, sample_strat)) => Some(renderer::draw_vox(
|
||||
&segment,
|
||||
dims,
|
||||
trans.clone(),
|
||||
*sample_strat,
|
||||
Some(Graphic::Voxel(ref segment, trans, sample_strat)) => Some((
|
||||
renderer::draw_vox(&segment, dims, trans.clone(), *sample_strat),
|
||||
None,
|
||||
)),
|
||||
None => {
|
||||
warn!(
|
||||
@ -311,17 +387,18 @@ fn draw_graphic(graphic_map: &GraphicMap, graphic_id: Id, dims: Vec2<u16>) -> Op
|
||||
}
|
||||
}
|
||||
|
||||
fn create_atlas_texture(renderer: &mut Renderer) -> (SimpleAtlasAllocator, Texture) {
|
||||
let (w, h) = renderer.get_resolution().into_tuple();
|
||||
|
||||
fn atlas_size(renderer: &Renderer) -> Vec2<u16> {
|
||||
let max_texture_size = renderer.max_texture_size();
|
||||
|
||||
let size = Vec2::new(w, h).map(|e| {
|
||||
renderer.get_resolution().map(|e| {
|
||||
(e * GRAPHIC_CACHE_RELATIVE_SIZE)
|
||||
.max(512)
|
||||
.min(max_texture_size)
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
fn create_atlas_texture(renderer: &mut Renderer) -> (SimpleAtlasAllocator, Texture) {
|
||||
let size = atlas_size(renderer);
|
||||
let atlas = SimpleAtlasAllocator::new(size2(i32::from(size.x), i32::from(size.y)));
|
||||
let texture = renderer.create_dynamic_texture(size).unwrap();
|
||||
(atlas, texture)
|
||||
@ -342,8 +419,23 @@ fn upload_image(renderer: &mut Renderer, aabr: Aabr<u16>, tex: &Texture, image:
|
||||
tex,
|
||||
offset,
|
||||
size,
|
||||
&image.pixels().map(|p| p.0).collect::<Vec<[u8; 4]>>(),
|
||||
// NOTE: Rgba texture, so each pixel is 4 bytes, ergo this cannot fail.
|
||||
// We make the cast parameters explicit for clarity.
|
||||
gfx::memory::cast_slice::<u8, [u8; 4]>(&image),
|
||||
) {
|
||||
warn!(?e, "Failed to update texture");
|
||||
}
|
||||
}
|
||||
|
||||
fn create_image(
|
||||
renderer: &mut Renderer,
|
||||
image: RgbaImage,
|
||||
border_color: Rgba<f32>,
|
||||
) -> Result<Texture, RenderError> {
|
||||
renderer.create_texture(
|
||||
&DynamicImage::ImageRgba8(image),
|
||||
None,
|
||||
Some(gfx::texture::WrapMode::Border),
|
||||
Some(border_color.into_array().into()),
|
||||
)
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ pub fn draw_vox(
|
||||
.resize_exact(
|
||||
output_size.x as u32,
|
||||
output_size.y as u32,
|
||||
image::FilterType::Triangle,
|
||||
image::imageops::FilterType::Triangle,
|
||||
)
|
||||
.to_rgba(),
|
||||
SampleStrat::PixelCoverage => super::pixel_art::resize_pixel_art(
|
||||
|
@ -24,7 +24,7 @@ impl<'a> GraphicCreator<'a> for ImageGraphic {
|
||||
type Specifier = &'a str;
|
||||
|
||||
fn new_graphic(specifier: Self::Specifier) -> Result<Graphic, Error> {
|
||||
Ok(Graphic::Image(load::<DynamicImage>(specifier)?))
|
||||
Ok(Graphic::Image(load::<DynamicImage>(specifier)?, None))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -452,7 +452,7 @@ impl Ui {
|
||||
let ((uv_l, uv_r, uv_b, uv_t), gl_size) =
|
||||
match graphic_cache.get_graphic(*graphic_id) {
|
||||
Some(Graphic::Blank) | None => continue,
|
||||
Some(Graphic::Image(image)) => {
|
||||
Some(Graphic::Image(image, ..)) => {
|
||||
source_rect.and_then(|src_rect| {
|
||||
let (image_w, image_h) = image.dimensions();
|
||||
let (source_w, source_h) = src_rect.w_h();
|
||||
|
@ -9,7 +9,7 @@ bincode = "1.2.0"
|
||||
common = { package = "veloren-common", path = "../common" }
|
||||
bitvec = "0.17.4"
|
||||
fxhash = "0.2.1"
|
||||
image = { version = "0.22.5", default-features = false, features = ["png"] }
|
||||
image = { version = "0.23.8", default-features = false, features = ["png"] }
|
||||
itertools = "0.9"
|
||||
vek = { version = "0.11.0", features = ["serde"] }
|
||||
noise = { version = "0.6.0", default-features = false }
|
||||
|
@ -129,7 +129,8 @@ fn main() {
|
||||
let mut gain = /*CONFIG.mountain_scale*/sampler.max_height;
|
||||
// The Z component during normal calculations is multiplied by gain; thus,
|
||||
let mut fov = 1.0;
|
||||
let mut scale = map_size_lg.chunks().x as f64 / W as f64;
|
||||
let mut scale =
|
||||
(map_size_lg.chunks().x as f64 / W as f64).max(map_size_lg.chunks().y as f64 / H as f64);
|
||||
|
||||
// Right-handed coordinate system: light is going left, down, and "backwards"
|
||||
// (i.e. on the map, where we translate the y coordinate on the world map to
|
||||
|
Loading…
Reference in New Issue
Block a user