diff --git a/server/src/settings.rs b/server/src/settings.rs index d0b1420791..121c7fd4b6 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -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), diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 9d0d3b7c5c..95cd907c50 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -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, ) diff --git a/voxygen/src/render/texture.rs b/voxygen/src/render/texture.rs index cbf5468251..ae9edbced1 100644 --- a/voxygen/src/render/texture.rs +++ b/voxygen/src/render/texture.rs @@ -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 where F::Surface: gfx::format::TextureSurface, diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 5052d1e9bd..85a1db5dce 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -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, fluid_model: Option>, - 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, + /// 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, light_map: Box) -> f32 + Send + Sync>, glow_map: Box) -> f32 + Send + Sync>, sprite_instances: HashMap<(SpriteKind, usize), Instances>, @@ -235,6 +243,19 @@ struct SpriteData { } pub struct Terrain { + /// 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`, to get /// hot-reloading for free, but I am not sure if sudden changes of this @@ -248,7 +269,9 @@ pub struct Terrain { /// 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, 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 { // GPU data sprite_data: Arc>>, sprite_col_lights: Texture, + /// 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, waves: Texture, @@ -459,7 +486,11 @@ impl Terrain { } fn remove_chunk_meta(&mut self, _pos: Vec2, 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 Terrain { // 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 Terrain { } 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 Terrain { if chunk.visible.is_visible() { renderer.render_terrain_chunk( &chunk.opaque_model, - &self.col_lights, + &chunk.texture, global, &chunk.locals, lod,