mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Enable allocation of new textures on atlas allocation failure.
This solves the problem of not being able to set the view distance too high, especially in pathological cases like giant trees. For simplicity, we just freeze any atlas where allocation failed and start allocating to a new texture and atlas, letting reference counting destroy the old one when there are no more references to it. Because of the spatial locality of chunk allocations, chunks allocated together will virtually always have similar lifetimes, so the odds of this causing significant fragmentation are very low, meaning this simple solution should not do much worse than a much fancier one.
This commit is contained in:
parent
ac4d753fc9
commit
866cc79d2e
@ -55,7 +55,7 @@ impl Default for Settings {
|
||||
max_players: 100,
|
||||
start_time: 9.0 * 3600.0,
|
||||
map_file: None,
|
||||
max_view_distance: Some(30),
|
||||
max_view_distance: Some(65),
|
||||
banned_words_files: Vec::new(),
|
||||
max_player_group_size: 6,
|
||||
client_timeout: Duration::from_secs(40),
|
||||
|
@ -1857,8 +1857,7 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
1,
|
||||
// FIXME: Move back to 64 once we support multiple texture atlases, or figure out a
|
||||
// way to increase the size of the terrain atlas.
|
||||
25,
|
||||
// 65,
|
||||
65,
|
||||
self.imgs.slider_indicator,
|
||||
self.imgs.slider,
|
||||
)
|
||||
|
@ -6,6 +6,7 @@ use vek::Vec2;
|
||||
type DefaultShaderFormat = (gfx::format::R8_G8_B8_A8, gfx::format::Srgb);
|
||||
|
||||
/// Represents an image that has been uploaded to the GPU.
|
||||
#[derive(Clone)]
|
||||
pub struct Texture<F: gfx::format::Formatted = DefaultShaderFormat>
|
||||
where
|
||||
F::Surface: gfx::format::TextureSurface,
|
||||
|
@ -1,5 +1,4 @@
|
||||
mod watcher;
|
||||
|
||||
pub use self::watcher::BlocksOfInterest;
|
||||
|
||||
use crate::{
|
||||
@ -61,7 +60,16 @@ pub struct TerrainChunkData {
|
||||
load_time: f32,
|
||||
opaque_model: Model<TerrainPipeline>,
|
||||
fluid_model: Option<Model<FluidPipeline>>,
|
||||
col_lights: guillotiere::AllocId,
|
||||
/// If this is `None`, this texture is not allocated in the current atlas,
|
||||
/// and therefore there is no need to free its allocation.
|
||||
col_lights: Option<guillotiere::AllocId>,
|
||||
/// The actual backing texture for this chunk. Use this for rendering
|
||||
/// purposes. The texture is reference-counted, so it will be
|
||||
/// automatically freed when no chunks are left that need it (though
|
||||
/// shadow chunks will still keep it alive; we could deal with this by
|
||||
/// making this an `Option`, but it probably isn't worth it since they
|
||||
/// shouldn't be that much more nonlocal than regular chunks).
|
||||
texture: Texture<ColLightFmt>,
|
||||
light_map: Box<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
|
||||
glow_map: Box<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
|
||||
sprite_instances: HashMap<(SpriteKind, usize), Instances<SpriteInstance>>,
|
||||
@ -235,6 +243,19 @@ struct SpriteData {
|
||||
}
|
||||
|
||||
pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
|
||||
/// This is always the *current* atlas into which data is being allocated.
|
||||
/// Once an atlas is too full to allocate the next texture, we always
|
||||
/// allocate a fresh texture and start allocating into that. Trying to
|
||||
/// keep more than one texture available for allocation doesn't seem
|
||||
/// worth it, because our allocation patterns are heavily spatial (so all
|
||||
/// data allocated around the same time should have a very similar lifetime,
|
||||
/// even in pathological cases). As a result, fragmentation effects
|
||||
/// should be minimal.
|
||||
///
|
||||
/// TODO: Consider "moving GC" style allocation to deal with spatial
|
||||
/// fragmentation effects due to odd texture sizes, which in some cases
|
||||
/// might significantly reduce the number of textures we need for
|
||||
/// particularly difficult locations.
|
||||
atlas: AtlasAllocator,
|
||||
/// FIXME: This could possibly become an `AssetHandle<SpriteSpec>`, to get
|
||||
/// hot-reloading for free, but I am not sure if sudden changes of this
|
||||
@ -248,7 +269,9 @@ pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
|
||||
/// first).
|
||||
///
|
||||
/// Note that these chunks are not complete; for example, they are missing
|
||||
/// texture data.
|
||||
/// texture data (they still currently hold onto a reference to their
|
||||
/// backing texture, but it generally can't be trusted for rendering
|
||||
/// purposes).
|
||||
shadow_chunks: Vec<(Vec2<i32>, TerrainChunkData)>,
|
||||
/* /// Secondary index into the terrain chunk table, used to sort through chunks by z index from
|
||||
/// the top down.
|
||||
@ -267,6 +290,10 @@ pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
|
||||
// GPU data
|
||||
sprite_data: Arc<HashMap<(SpriteKind, usize), Vec<SpriteData>>>,
|
||||
sprite_col_lights: Texture<ColLightFmt>,
|
||||
/// As stated previously, this is always the very latest texture into which
|
||||
/// we allocate. Code cannot assume that this is the assigned texture
|
||||
/// for any particular chunk; look at the `texture` field in
|
||||
/// `TerrainChunkData` for that.
|
||||
col_lights: Texture<ColLightFmt>,
|
||||
waves: Texture,
|
||||
|
||||
@ -459,7 +486,11 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
}
|
||||
|
||||
fn remove_chunk_meta(&mut self, _pos: Vec2<i32>, chunk: &TerrainChunkData) {
|
||||
self.atlas.deallocate(chunk.col_lights);
|
||||
// No need to free the allocation if the chunk is not allocated in the current
|
||||
// atlas, since we don't bother tracking it at that point.
|
||||
if let Some(col_lights) = chunk.col_lights {
|
||||
self.atlas.deallocate(col_lights);
|
||||
}
|
||||
/* let (zmin, zmax) = chunk.z_bounds;
|
||||
self.z_index_up.remove(Vec3::from(zmin, pos.x, pos.y));
|
||||
self.z_index_down.remove(Vec3::from(zmax, pos.x, pos.y)); */
|
||||
@ -809,19 +840,46 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
// TODO: Allocate new atlas on allocation failure.
|
||||
let (tex, tex_size) = response.col_lights_info;
|
||||
let atlas = &mut self.atlas;
|
||||
let chunks = &mut self.chunks;
|
||||
let col_lights = &mut self.col_lights;
|
||||
let allocation = atlas
|
||||
.allocate(guillotiere::Size::new(
|
||||
i32::from(tex_size.x),
|
||||
i32::from(tex_size.y),
|
||||
))
|
||||
.expect("Not yet implemented: allocate new atlas on allocation failure.");
|
||||
.unwrap_or_else(|| {
|
||||
// Atlas allocation failure: try allocating a new texture and atlas.
|
||||
let (new_atlas, new_col_lights) =
|
||||
Self::make_atlas(renderer).expect("Failed to create atlas texture");
|
||||
|
||||
// We reset the atlas and clear allocations from existing chunks, even
|
||||
// though we haven't yet checked whether the new allocation can fit in
|
||||
// the texture. This is reasonable because we don't have a fallback
|
||||
// if a single chunk can't fit in an empty atlas of maximum size.
|
||||
//
|
||||
// TODO: Consider attempting defragmentation first rather than just
|
||||
// always moving everything into the new chunk.
|
||||
chunks.iter_mut().for_each(|(_, chunk)| {
|
||||
chunk.col_lights = None;
|
||||
});
|
||||
*atlas = new_atlas;
|
||||
*col_lights = new_col_lights;
|
||||
|
||||
atlas
|
||||
.allocate(guillotiere::Size::new(
|
||||
i32::from(tex_size.x),
|
||||
i32::from(tex_size.y),
|
||||
))
|
||||
.expect("Chunk data does not fit in a texture of maximum size.")
|
||||
});
|
||||
|
||||
// NOTE: Cast is safe since the origin was a u16.
|
||||
let atlas_offs = Vec2::new(
|
||||
allocation.rectangle.min.x as u16,
|
||||
allocation.rectangle.min.y as u16,
|
||||
);
|
||||
if let Err(err) = renderer.update_texture(
|
||||
&self.col_lights,
|
||||
col_lights,
|
||||
atlas_offs.into_array(),
|
||||
tex_size.into_array(),
|
||||
&tex,
|
||||
@ -843,7 +901,8 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
col_lights: allocation.id,
|
||||
col_lights: Some(allocation.id),
|
||||
texture: self.col_lights.clone(),
|
||||
light_map: response.light_map,
|
||||
glow_map: response.glow_map,
|
||||
sprite_instances: response
|
||||
@ -1170,7 +1229,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
if chunk.visible.is_visible() {
|
||||
renderer.render_terrain_chunk(
|
||||
&chunk.opaque_model,
|
||||
&self.col_lights,
|
||||
&chunk.texture,
|
||||
global,
|
||||
&chunk.locals,
|
||||
lod,
|
||||
|
Loading…
Reference in New Issue
Block a user