mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Mesh sprites in the background.
This makes the delay afetr selecting a character before logging into the game much shorter, in the common case. It still doesn't handle things perfectly (it blocks creating Terrain::new if it's not finished, and it could be faster due to working in the background), but it's still a lot better than it was before. To improve sprite meshing performance, we also apply the terrain flat_get optimizations to sprites. Though I didn't initially know how much of an impact it would have, it feels significantly faster to me, though being able to parallelize it would be ideal.
This commit is contained in:
parent
12c3ee440e
commit
1bdf3b13a8
@ -48,6 +48,7 @@ opt-level = 2
|
|||||||
[profile.dev.package."veloren-server-cli"]
|
[profile.dev.package."veloren-server-cli"]
|
||||||
opt-level = 2
|
opt-level = 2
|
||||||
[profile.dev.package."veloren-voxygen"]
|
[profile.dev.package."veloren-voxygen"]
|
||||||
|
incremental = true
|
||||||
opt-level = 2
|
opt-level = 2
|
||||||
[profile.dev.package."veloren-world"]
|
[profile.dev.package."veloren-world"]
|
||||||
opt-level = 2
|
opt-level = 2
|
||||||
|
@ -2,7 +2,14 @@
|
|||||||
#![allow(incomplete_features)]
|
#![allow(incomplete_features)]
|
||||||
#![allow(clippy::option_map_unit_fn)]
|
#![allow(clippy::option_map_unit_fn)]
|
||||||
#![deny(clippy::clone_on_ref_ptr)]
|
#![deny(clippy::clone_on_ref_ptr)]
|
||||||
#![feature(array_map, bool_to_option, const_generics, drain_filter, or_patterns)]
|
#![feature(
|
||||||
|
array_map,
|
||||||
|
bool_to_option,
|
||||||
|
const_generics,
|
||||||
|
drain_filter,
|
||||||
|
once_cell,
|
||||||
|
or_patterns
|
||||||
|
)]
|
||||||
#![recursion_limit = "2048"]
|
#![recursion_limit = "2048"]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -47,6 +54,7 @@ pub struct GlobalState {
|
|||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
pub profile: Profile,
|
pub profile: Profile,
|
||||||
pub window: Window,
|
pub window: Window,
|
||||||
|
pub lazy_init: scene::terrain::SpriteRenderContextLazy,
|
||||||
pub audio: AudioFrontend,
|
pub audio: AudioFrontend,
|
||||||
pub info_message: Option<String>,
|
pub info_message: Option<String>,
|
||||||
pub clock: Clock,
|
pub clock: Clock,
|
||||||
|
@ -8,6 +8,7 @@ use veloren_voxygen::{
|
|||||||
i18n::{self, i18n_asset_key, Localization},
|
i18n::{self, i18n_asset_key, Localization},
|
||||||
profile::Profile,
|
profile::Profile,
|
||||||
run,
|
run,
|
||||||
|
scene::terrain::SpriteRenderContext,
|
||||||
settings::{get_fps, AudioOutput, Settings},
|
settings::{get_fps, AudioOutput, Settings},
|
||||||
window::Window,
|
window::Window,
|
||||||
GlobalState,
|
GlobalState,
|
||||||
@ -175,14 +176,17 @@ fn main() {
|
|||||||
i18n.read().log_missing_entries();
|
i18n.read().log_missing_entries();
|
||||||
|
|
||||||
// Create window
|
// Create window
|
||||||
let (window, event_loop) = Window::new(&settings).expect("Failed to create window!");
|
let (mut window, event_loop) = Window::new(&settings).expect("Failed to create window!");
|
||||||
|
|
||||||
let clipboard = iced_winit::Clipboard::new(window.window());
|
let clipboard = iced_winit::Clipboard::new(window.window());
|
||||||
|
|
||||||
|
let lazy_init = SpriteRenderContext::new(window.renderer_mut());
|
||||||
|
|
||||||
let global_state = GlobalState {
|
let global_state = GlobalState {
|
||||||
audio,
|
audio,
|
||||||
profile,
|
profile,
|
||||||
window,
|
window,
|
||||||
|
lazy_init,
|
||||||
clock: Clock::new(std::time::Duration::from_secs_f64(
|
clock: Clock::new(std::time::Duration::from_secs_f64(
|
||||||
1.0 / get_fps(settings.graphics.max_fps) as f64,
|
1.0 / get_fps(settings.graphics.max_fps) as f64,
|
||||||
)),
|
)),
|
||||||
|
@ -13,6 +13,7 @@ use common::{
|
|||||||
figure::Cell,
|
figure::Cell,
|
||||||
vol::{BaseVol, ReadVol, SizedVol, Vox},
|
vol::{BaseVol, ReadVol, SizedVol, Vox},
|
||||||
};
|
};
|
||||||
|
use core::convert::TryFrom;
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
type SpriteVertex = <SpritePipeline as render::Pipeline>::Vertex;
|
type SpriteVertex = <SpritePipeline as render::Pipeline>::Vertex;
|
||||||
@ -79,10 +80,10 @@ where
|
|||||||
};
|
};
|
||||||
let get_glow = |_vol: &mut V, _pos: Vec3<i32>| 0.0;
|
let get_glow = |_vol: &mut V, _pos: Vec3<i32>| 0.0;
|
||||||
let get_opacity =
|
let get_opacity =
|
||||||
|vol: &mut V, pos: Vec3<i32>| vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true);
|
|vol: &mut V, pos: Vec3<i32>| vol.get(pos).map_or(true, |vox| vox.is_empty());
|
||||||
let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
|
let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
|
||||||
should_draw_greedy(pos, delta, uv, |vox| {
|
should_draw_greedy(pos, delta, uv, |vox| {
|
||||||
vol.get(vox).map(|vox| *vox).unwrap_or(Vox::empty())
|
vol.get(vox).map(|vox| *vox).unwrap_or(Cell::empty())
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let create_opaque = |atlas_pos, pos, norm| {
|
let create_opaque = |atlas_pos, pos, norm| {
|
||||||
@ -161,6 +162,14 @@ where
|
|||||||
&& lower_bound.y <= upper_bound.y
|
&& lower_bound.y <= upper_bound.y
|
||||||
&& lower_bound.z <= upper_bound.z
|
&& lower_bound.z <= upper_bound.z
|
||||||
);
|
);
|
||||||
|
// Lower bound coordinates must fit in an i16 (which means upper bound
|
||||||
|
// coordinates fit as integers in a f23).
|
||||||
|
assert!(
|
||||||
|
i16::try_from(lower_bound.x).is_ok()
|
||||||
|
&& i16::try_from(lower_bound.y).is_ok()
|
||||||
|
&& i16::try_from(lower_bound.z).is_ok(),
|
||||||
|
"Sprite offsets should fit in i16",
|
||||||
|
);
|
||||||
let greedy_size = upper_bound - lower_bound + 1;
|
let greedy_size = upper_bound - lower_bound + 1;
|
||||||
// TODO: Should this be 16, 16, 64?
|
// TODO: Should this be 16, 16, 64?
|
||||||
assert!(
|
assert!(
|
||||||
@ -168,39 +177,69 @@ where
|
|||||||
"Sprite size out of bounds: {:?} ≤ (31, 31, 63)",
|
"Sprite size out of bounds: {:?} ≤ (31, 31, 63)",
|
||||||
greedy_size - 1
|
greedy_size - 1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let (flat, flat_get) = {
|
||||||
|
let (w, h, d) = (greedy_size + 2).into_tuple();
|
||||||
|
let flat = {
|
||||||
|
let vol = self;
|
||||||
|
|
||||||
|
let mut flat = vec![Cell::empty(); (w * h * d) as usize];
|
||||||
|
let mut i = 0;
|
||||||
|
for x in -1..greedy_size.x + 1 {
|
||||||
|
for y in -1..greedy_size.y + 1 {
|
||||||
|
for z in -1..greedy_size.z + 1 {
|
||||||
|
let wpos = lower_bound + Vec3::new(x, y, z);
|
||||||
|
let block = vol.get(wpos).map(|b| *b).unwrap_or(Cell::empty());
|
||||||
|
flat[i] = block;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flat
|
||||||
|
};
|
||||||
|
|
||||||
|
let flat_get = move |flat: &Vec<Cell>, Vec3 { x, y, z }| match flat
|
||||||
|
.get((x * h * d + y * d + z) as usize)
|
||||||
|
.copied()
|
||||||
|
{
|
||||||
|
Some(b) => b,
|
||||||
|
None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h),
|
||||||
|
};
|
||||||
|
|
||||||
|
(flat, flat_get)
|
||||||
|
};
|
||||||
|
|
||||||
// NOTE: Cast to usize is safe because of previous check, since all values fit
|
// NOTE: Cast to usize is safe because of previous check, since all values fit
|
||||||
// into u16 which is safe to cast to usize.
|
// into u16 which is safe to cast to usize.
|
||||||
let greedy_size = greedy_size.as_::<usize>();
|
let greedy_size = greedy_size.as_::<usize>();
|
||||||
|
|
||||||
let greedy_size_cross = greedy_size;
|
let greedy_size_cross = greedy_size;
|
||||||
let draw_delta = lower_bound;
|
let draw_delta = Vec3::new(1, 1, 1);
|
||||||
|
|
||||||
let get_light = |vol: &mut V, pos: Vec3<i32>| {
|
let get_light = move |flat: &mut _, pos: Vec3<i32>| {
|
||||||
if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) {
|
if flat_get(flat, pos).is_empty() {
|
||||||
1.0
|
1.0
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let get_glow = |_vol: &mut V, _pos: Vec3<i32>| 0.0;
|
let get_glow = |_flat: &mut _, _pos: Vec3<i32>| 0.0;
|
||||||
let get_color = |vol: &mut V, pos: Vec3<i32>| {
|
let get_color = move |flat: &mut _, pos: Vec3<i32>| {
|
||||||
vol.get(pos)
|
flat_get(flat, pos).get_color().unwrap_or(Rgb::zero())
|
||||||
.ok()
|
|
||||||
.and_then(|vox| vox.get_color())
|
|
||||||
.unwrap_or(Rgb::zero())
|
|
||||||
};
|
};
|
||||||
let get_opacity =
|
let get_opacity = move |flat: &mut _, pos: Vec3<i32>| flat_get(flat, pos).is_empty();
|
||||||
|vol: &mut V, pos: Vec3<i32>| vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true);
|
let should_draw = move |flat: &mut _, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
|
||||||
let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
|
should_draw_greedy_ao(vertical_stripes, pos, delta, uv, |vox| flat_get(flat, vox))
|
||||||
should_draw_greedy_ao(vertical_stripes, pos, delta, uv, |vox| {
|
};
|
||||||
vol.get(vox).map(|vox| *vox).unwrap_or(Vox::empty())
|
// NOTE: Fits in i16 (much lower actually) so f32 is no problem (and the final
|
||||||
})
|
// position, pos + mesh_delta, is guaranteed to fit in an f32).
|
||||||
|
let mesh_delta = lower_bound.as_::<f32>();
|
||||||
|
let create_opaque = |atlas_pos, pos: Vec3<f32>, norm, _meta| {
|
||||||
|
SpriteVertex::new(atlas_pos, pos + mesh_delta, norm)
|
||||||
};
|
};
|
||||||
let create_opaque =
|
|
||||||
|atlas_pos, pos: Vec3<f32>, norm, _meta| SpriteVertex::new(atlas_pos, pos, norm);
|
|
||||||
|
|
||||||
greedy.push(GreedyConfig {
|
greedy.push(GreedyConfig {
|
||||||
data: self,
|
data: flat,
|
||||||
draw_delta,
|
draw_delta,
|
||||||
greedy_size,
|
greedy_size,
|
||||||
greedy_size_cross,
|
greedy_size_cross,
|
||||||
@ -219,8 +258,8 @@ where
|
|||||||
|atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta),
|
|atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
make_face_texel: move |vol: &mut V, pos, light, glow| {
|
make_face_texel: move |flat: &mut _, pos, light, glow| {
|
||||||
TerrainVertex::make_col_light(light, glow, get_color(vol, pos))
|
TerrainVertex::make_col_light(light, glow, get_color(flat, pos))
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -288,10 +327,10 @@ where
|
|||||||
.unwrap_or(Rgb::zero())
|
.unwrap_or(Rgb::zero())
|
||||||
};
|
};
|
||||||
let get_opacity =
|
let get_opacity =
|
||||||
|vol: &mut V, pos: Vec3<i32>| vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true);
|
|vol: &mut V, pos: Vec3<i32>| vol.get(pos).map_or(true, |vox| vox.is_empty());
|
||||||
let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
|
let should_draw = |vol: &mut V, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
|
||||||
should_draw_greedy(pos, delta, uv, |vox| {
|
should_draw_greedy(pos, delta, uv, |vox| {
|
||||||
vol.get(vox).map(|vox| *vox).unwrap_or(Vox::empty())
|
vol.get(vox).map(|vox| *vox).unwrap_or(Cell::empty())
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let create_opaque = |_atlas_pos, pos: Vec3<f32>, norm| ParticleVertex::new(pos, norm);
|
let create_opaque = |_atlas_pos, pos: Vec3<f32>, norm| ParticleVertex::new(pos, norm);
|
||||||
|
@ -67,7 +67,7 @@ pub struct ShadowPipeline;
|
|||||||
impl ShadowPipeline {
|
impl ShadowPipeline {
|
||||||
pub fn create_col_lights(
|
pub fn create_col_lights(
|
||||||
renderer: &mut Renderer,
|
renderer: &mut Renderer,
|
||||||
(col_lights, col_lights_size): ColLightInfo,
|
(col_lights, col_lights_size): &ColLightInfo,
|
||||||
) -> Result<Texture<ColLightFmt>, RenderError> {
|
) -> Result<Texture<ColLightFmt>, RenderError> {
|
||||||
renderer.create_texture_immutable_raw(
|
renderer.create_texture_immutable_raw(
|
||||||
gfx::texture::Kind::D2(
|
gfx::texture::Kind::D2(
|
||||||
@ -76,7 +76,7 @@ impl ShadowPipeline {
|
|||||||
gfx::texture::AaMode::Single,
|
gfx::texture::AaMode::Single,
|
||||||
),
|
),
|
||||||
gfx::texture::Mipmap::Provided,
|
gfx::texture::Mipmap::Provided,
|
||||||
&[&col_lights],
|
&[col_lights],
|
||||||
gfx::texture::SamplerInfo::new(
|
gfx::texture::SamplerInfo::new(
|
||||||
gfx::texture::FilterMethod::Bilinear,
|
gfx::texture::FilterMethod::Bilinear,
|
||||||
gfx::texture::WrapMode::Clamp,
|
gfx::texture::WrapMode::Clamp,
|
||||||
|
@ -4904,7 +4904,7 @@ impl FigureColLights {
|
|||||||
i32::from(tex_size.y),
|
i32::from(tex_size.y),
|
||||||
))
|
))
|
||||||
.expect("Not yet implemented: allocate new atlas on allocation failure.");
|
.expect("Not yet implemented: allocate new atlas on allocation failure.");
|
||||||
let col_lights = ShadowPipeline::create_col_lights(renderer, (tex, tex_size))?;
|
let col_lights = ShadowPipeline::create_col_lights(renderer, &(tex, tex_size))?;
|
||||||
let model_len = u32::try_from(opaque.vertices().len())
|
let model_len = u32::try_from(opaque.vertices().len())
|
||||||
.expect("The model size for this figure does not fit in a u32!");
|
.expect("The model size for this figure does not fit in a u32!");
|
||||||
let model = renderer.create_model(&opaque)?;
|
let model = renderer.create_model(&opaque)?;
|
||||||
|
@ -11,7 +11,7 @@ pub use self::{
|
|||||||
figure::FigureMgr,
|
figure::FigureMgr,
|
||||||
lod::Lod,
|
lod::Lod,
|
||||||
particle::ParticleMgr,
|
particle::ParticleMgr,
|
||||||
terrain::Terrain,
|
terrain::{SpriteRenderContextLazy, Terrain},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
audio::{ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend},
|
audio::{ambient::AmbientMgr, music::MusicMgr, sfx::SfxMgr, AudioFrontend},
|
||||||
@ -267,8 +267,14 @@ fn compute_warping_parameter_perspective<F: Float + FloatConst>(
|
|||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
/// Create a new `Scene` with default parameters.
|
/// Create a new `Scene` with default parameters.
|
||||||
pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self {
|
pub fn new(
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
lazy_init: &mut SpriteRenderContextLazy,
|
||||||
|
client: &Client,
|
||||||
|
settings: &Settings,
|
||||||
|
) -> Self {
|
||||||
let resolution = renderer.get_resolution().map(|e| e as f32);
|
let resolution = renderer.get_resolution().map(|e| e as f32);
|
||||||
|
let sprite_render_context = lazy_init(renderer);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
data: GlobalModel {
|
data: GlobalModel {
|
||||||
@ -301,7 +307,7 @@ impl Scene {
|
|||||||
.create_consts(&[PostProcessLocals::default()])
|
.create_consts(&[PostProcessLocals::default()])
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
},
|
},
|
||||||
terrain: Terrain::new(renderer),
|
terrain: Terrain::new(renderer, sprite_render_context),
|
||||||
lod: Lod::new(renderer, client, settings),
|
lod: Lod::new(renderer, client, settings),
|
||||||
loaded_distance: 0.0,
|
loaded_distance: 0.0,
|
||||||
map_bounds: Vec2::new(
|
map_bounds: Vec2::new(
|
||||||
|
@ -304,13 +304,211 @@ impl TerrainChunkData {
|
|||||||
pub fn can_shadow_sun(&self) -> bool { self.visible.is_visible() || self.can_shadow_sun }
|
pub fn can_shadow_sun(&self) -> bool { self.visible.is_visible() || self.can_shadow_sun }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V: RectRasterableVol> Terrain<V> {
|
#[derive(Clone)]
|
||||||
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
pub struct SpriteRenderContext {
|
||||||
pub fn new(renderer: &mut Renderer) -> Self {
|
sprite_config: Arc<SpriteSpec>,
|
||||||
// Load all the sprite config data.
|
sprite_data: Arc<HashMap<(SpriteKind, usize), Vec<SpriteData>>>,
|
||||||
let sprite_config =
|
sprite_col_lights: Texture<ColLightFmt>,
|
||||||
Arc::<SpriteSpec>::load_expect("voxygen.voxel.sprite_manifest").cloned();
|
}
|
||||||
|
|
||||||
|
pub type SpriteRenderContextLazy = Box<dyn FnMut(&mut Renderer) -> SpriteRenderContext>;
|
||||||
|
|
||||||
|
impl SpriteRenderContext {
|
||||||
|
#[allow(clippy::float_cmp)] // TODO: Pending review in #587
|
||||||
|
pub fn new(renderer: &mut Renderer) -> SpriteRenderContextLazy {
|
||||||
|
let max_texture_size = renderer.max_texture_size();
|
||||||
|
|
||||||
|
struct SpriteDataResponse {
|
||||||
|
locals: [SpriteLocals; 8],
|
||||||
|
model: Mesh<SpritePipeline>,
|
||||||
|
offset: Vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpriteWorkerResponse {
|
||||||
|
sprite_config: Arc<SpriteSpec>,
|
||||||
|
sprite_data: HashMap<(SpriteKind, usize), Vec<SpriteDataResponse>>,
|
||||||
|
sprite_col_lights: ColLightInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
let join_handle = std::thread::spawn(move || {
|
||||||
|
// Load all the sprite config data.
|
||||||
|
let sprite_config =
|
||||||
|
Arc::<SpriteSpec>::load_expect("voxygen.voxel.sprite_manifest").cloned();
|
||||||
|
|
||||||
|
let max_size =
|
||||||
|
guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size));
|
||||||
|
let mut greedy = GreedyMesh::new(max_size);
|
||||||
|
let mut locals_buffer = [SpriteLocals::default(); 8];
|
||||||
|
let sprite_config_ = &sprite_config;
|
||||||
|
// NOTE: Tracks the start vertex of the next model to be meshed.
|
||||||
|
|
||||||
|
let sprite_data: HashMap<(SpriteKind, usize), _> = SpriteKind::into_enum_iter()
|
||||||
|
.filter_map(|kind| Some((kind, kind.elim_case_pure(&sprite_config_.0).as_ref()?)))
|
||||||
|
.flat_map(|(kind, sprite_config)| {
|
||||||
|
let wind_sway = sprite_config.wind_sway;
|
||||||
|
sprite_config.variations.iter().enumerate().map(
|
||||||
|
move |(
|
||||||
|
variation,
|
||||||
|
SpriteModelConfig {
|
||||||
|
model,
|
||||||
|
offset,
|
||||||
|
lod_axes,
|
||||||
|
},
|
||||||
|
)| {
|
||||||
|
let scaled = [1.0, 0.8, 0.6, 0.4, 0.2];
|
||||||
|
let offset = Vec3::from(*offset);
|
||||||
|
let lod_axes = Vec3::from(*lod_axes);
|
||||||
|
let model = DotVoxAsset::load_expect(model);
|
||||||
|
let zero = Vec3::zero();
|
||||||
|
let model_size = model
|
||||||
|
.read()
|
||||||
|
.0
|
||||||
|
.models
|
||||||
|
.first()
|
||||||
|
.map(
|
||||||
|
|&dot_vox::Model {
|
||||||
|
size: dot_vox::Size { x, y, z },
|
||||||
|
..
|
||||||
|
}| Vec3::new(x, y, z),
|
||||||
|
)
|
||||||
|
.unwrap_or(zero);
|
||||||
|
let max_model_size = Vec3::new(31.0, 31.0, 63.0);
|
||||||
|
let model_scale = max_model_size.map2(model_size, |max_sz: f32, cur_sz| {
|
||||||
|
let scale = max_sz / max_sz.max(cur_sz as f32);
|
||||||
|
if scale < 1.0 && (cur_sz as f32 * scale).ceil() > max_sz {
|
||||||
|
scale - 0.001
|
||||||
|
} else {
|
||||||
|
scale
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let sprite_mat: Mat4<f32> =
|
||||||
|
Mat4::translation_3d(offset).scaled_3d(SPRITE_SCALE);
|
||||||
|
move |greedy: &mut GreedyMesh| {
|
||||||
|
(
|
||||||
|
(kind, variation),
|
||||||
|
scaled
|
||||||
|
.iter()
|
||||||
|
.map(|&lod_scale_orig| {
|
||||||
|
let lod_scale = model_scale
|
||||||
|
* if lod_scale_orig == 1.0 {
|
||||||
|
Vec3::broadcast(1.0)
|
||||||
|
} else {
|
||||||
|
lod_axes * lod_scale_orig
|
||||||
|
+ lod_axes
|
||||||
|
.map(|e| if e == 0.0 { 1.0 } else { 0.0 })
|
||||||
|
};
|
||||||
|
// Mesh generation exclusively acts using side effects; it
|
||||||
|
// has no
|
||||||
|
// interesting return value, but updates the mesh.
|
||||||
|
let mut opaque_mesh = Mesh::new();
|
||||||
|
Meshable::<SpritePipeline, &mut GreedyMesh>::generate_mesh(
|
||||||
|
Segment::from(&model.read().0).scaled_by(lod_scale),
|
||||||
|
(greedy, &mut opaque_mesh, false),
|
||||||
|
);
|
||||||
|
|
||||||
|
let sprite_scale = Vec3::one() / lod_scale;
|
||||||
|
let sprite_mat: Mat4<f32> =
|
||||||
|
sprite_mat * Mat4::scaling_3d(sprite_scale);
|
||||||
|
locals_buffer.iter_mut().enumerate().for_each(
|
||||||
|
|(ori, locals)| {
|
||||||
|
let sprite_mat = sprite_mat
|
||||||
|
.rotated_z(f32::consts::PI * 0.25 * ori as f32);
|
||||||
|
*locals = SpriteLocals::new(
|
||||||
|
sprite_mat,
|
||||||
|
sprite_scale,
|
||||||
|
offset,
|
||||||
|
wind_sway,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
SpriteDataResponse {
|
||||||
|
model: opaque_mesh,
|
||||||
|
offset,
|
||||||
|
locals: locals_buffer,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|mut f| f(&mut greedy))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let sprite_col_lights = greedy.finalize();
|
||||||
|
|
||||||
|
SpriteWorkerResponse {
|
||||||
|
sprite_config,
|
||||||
|
sprite_data,
|
||||||
|
sprite_col_lights,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let init = core::lazy::OnceCell::new();
|
||||||
|
let mut join_handle = Some(join_handle);
|
||||||
|
let mut closure = move |renderer: &mut Renderer| {
|
||||||
|
// The second unwrap can only fail if the sprite meshing thread panics, which
|
||||||
|
// implies that our sprite assets either were not found or did not
|
||||||
|
// satisfy the size requirements for meshing, both of which are
|
||||||
|
// considered invariant violations.
|
||||||
|
let SpriteWorkerResponse {
|
||||||
|
sprite_config,
|
||||||
|
sprite_data,
|
||||||
|
sprite_col_lights,
|
||||||
|
} = join_handle
|
||||||
|
.take()
|
||||||
|
.expect(
|
||||||
|
"Closure should only be called once (in a `OnceCell::get_or_init`) in the \
|
||||||
|
absence of caught panics!",
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let sprite_data = sprite_data
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, models)| {
|
||||||
|
(
|
||||||
|
key,
|
||||||
|
models
|
||||||
|
.into_iter()
|
||||||
|
.map(
|
||||||
|
|SpriteDataResponse {
|
||||||
|
locals,
|
||||||
|
model,
|
||||||
|
offset,
|
||||||
|
}| {
|
||||||
|
SpriteData {
|
||||||
|
locals: renderer
|
||||||
|
.create_consts(&locals)
|
||||||
|
.expect("Failed to upload sprite locals to the GPU!"),
|
||||||
|
model: renderer.create_model(&model).expect(
|
||||||
|
"Failed to upload sprite model data to the GPU!",
|
||||||
|
),
|
||||||
|
offset,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let sprite_col_lights = ShadowPipeline::create_col_lights(renderer, &sprite_col_lights)
|
||||||
|
.expect("Failed to upload sprite color and light data to the GPU!");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
sprite_config: Arc::clone(&sprite_config),
|
||||||
|
sprite_data: Arc::new(sprite_data),
|
||||||
|
sprite_col_lights,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Box::new(move |renderer| init.get_or_init(|| closure(renderer)).clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: RectRasterableVol> Terrain<V> {
|
||||||
|
pub fn new(renderer: &mut Renderer, sprite_render_context: SpriteRenderContext) -> Self {
|
||||||
// Create a new mpsc (Multiple Produced, Single Consumer) pair for communicating
|
// Create a new mpsc (Multiple Produced, Single Consumer) pair for communicating
|
||||||
// with worker threads that are meshing chunks.
|
// with worker threads that are meshing chunks.
|
||||||
let (send, recv) = channel::unbounded();
|
let (send, recv) = channel::unbounded();
|
||||||
@ -318,128 +516,17 @@ impl<V: RectRasterableVol> Terrain<V> {
|
|||||||
let (atlas, col_lights) =
|
let (atlas, col_lights) =
|
||||||
Self::make_atlas(renderer).expect("Failed to create atlas texture");
|
Self::make_atlas(renderer).expect("Failed to create atlas texture");
|
||||||
|
|
||||||
let max_texture_size = renderer.max_texture_size();
|
|
||||||
let max_size =
|
|
||||||
guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size));
|
|
||||||
let mut greedy = GreedyMesh::new(max_size);
|
|
||||||
let mut locals_buffer = [SpriteLocals::default(); 8];
|
|
||||||
let sprite_config_ = &sprite_config;
|
|
||||||
// NOTE: Tracks the start vertex of the next model to be meshed.
|
|
||||||
|
|
||||||
let sprite_data: HashMap<(SpriteKind, usize), _> = SpriteKind::into_enum_iter()
|
|
||||||
.filter_map(|kind| Some((kind, kind.elim_case_pure(&sprite_config_.0).as_ref()?)))
|
|
||||||
.flat_map(|(kind, sprite_config)| {
|
|
||||||
let wind_sway = sprite_config.wind_sway;
|
|
||||||
sprite_config.variations.iter().enumerate().map(
|
|
||||||
move |(
|
|
||||||
variation,
|
|
||||||
SpriteModelConfig {
|
|
||||||
model,
|
|
||||||
offset,
|
|
||||||
lod_axes,
|
|
||||||
},
|
|
||||||
)| {
|
|
||||||
let scaled = [1.0, 0.8, 0.6, 0.4, 0.2];
|
|
||||||
let offset = Vec3::from(*offset);
|
|
||||||
let lod_axes = Vec3::from(*lod_axes);
|
|
||||||
let model = DotVoxAsset::load_expect(model);
|
|
||||||
let zero = Vec3::zero();
|
|
||||||
let model_size = model
|
|
||||||
.read()
|
|
||||||
.0
|
|
||||||
.models
|
|
||||||
.first()
|
|
||||||
.map(
|
|
||||||
|&dot_vox::Model {
|
|
||||||
size: dot_vox::Size { x, y, z },
|
|
||||||
..
|
|
||||||
}| Vec3::new(x, y, z),
|
|
||||||
)
|
|
||||||
.unwrap_or(zero);
|
|
||||||
let max_model_size = Vec3::new(31.0, 31.0, 63.0);
|
|
||||||
let model_scale = max_model_size.map2(model_size, |max_sz: f32, cur_sz| {
|
|
||||||
let scale = max_sz / max_sz.max(cur_sz as f32);
|
|
||||||
if scale < 1.0 && (cur_sz as f32 * scale).ceil() > max_sz {
|
|
||||||
scale - 0.001
|
|
||||||
} else {
|
|
||||||
scale
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let sprite_mat: Mat4<f32> =
|
|
||||||
Mat4::translation_3d(offset).scaled_3d(SPRITE_SCALE);
|
|
||||||
move |greedy: &mut GreedyMesh, renderer: &mut Renderer| {
|
|
||||||
(
|
|
||||||
(kind, variation),
|
|
||||||
scaled
|
|
||||||
.iter()
|
|
||||||
.map(|&lod_scale_orig| {
|
|
||||||
let lod_scale = model_scale
|
|
||||||
* if lod_scale_orig == 1.0 {
|
|
||||||
Vec3::broadcast(1.0)
|
|
||||||
} else {
|
|
||||||
lod_axes * lod_scale_orig
|
|
||||||
+ lod_axes
|
|
||||||
.map(|e| if e == 0.0 { 1.0 } else { 0.0 })
|
|
||||||
};
|
|
||||||
// Mesh generation exclusively acts using side effects; it
|
|
||||||
// has no
|
|
||||||
// interesting return value, but updates the mesh.
|
|
||||||
let mut opaque_mesh = Mesh::new();
|
|
||||||
Meshable::<SpritePipeline, &mut GreedyMesh>::generate_mesh(
|
|
||||||
Segment::from(&model.read().0).scaled_by(lod_scale),
|
|
||||||
(greedy, &mut opaque_mesh, false),
|
|
||||||
);
|
|
||||||
let model = renderer.create_model(&opaque_mesh).expect(
|
|
||||||
"Failed to upload sprite model data to the GPU!",
|
|
||||||
);
|
|
||||||
|
|
||||||
let sprite_scale = Vec3::one() / lod_scale;
|
|
||||||
let sprite_mat: Mat4<f32> =
|
|
||||||
sprite_mat * Mat4::scaling_3d(sprite_scale);
|
|
||||||
locals_buffer.iter_mut().enumerate().for_each(
|
|
||||||
|(ori, locals)| {
|
|
||||||
let sprite_mat = sprite_mat
|
|
||||||
.rotated_z(f32::consts::PI * 0.25 * ori as f32);
|
|
||||||
*locals = SpriteLocals::new(
|
|
||||||
sprite_mat,
|
|
||||||
sprite_scale,
|
|
||||||
offset,
|
|
||||||
wind_sway,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
SpriteData {
|
|
||||||
/* vertex_range */ model,
|
|
||||||
offset,
|
|
||||||
locals: renderer.create_consts(&locals_buffer).expect(
|
|
||||||
"Failed to upload sprite locals to the GPU!",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(|mut f| f(&mut greedy, renderer))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let sprite_col_lights = ShadowPipeline::create_col_lights(renderer, greedy.finalize())
|
|
||||||
.expect("Failed to upload sprite color and light data to the GPU!");
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
atlas,
|
atlas,
|
||||||
sprite_config,
|
sprite_config: sprite_render_context.sprite_config,
|
||||||
chunks: HashMap::default(),
|
chunks: HashMap::default(),
|
||||||
shadow_chunks: Vec::default(),
|
shadow_chunks: Vec::default(),
|
||||||
mesh_send_tmp: send,
|
mesh_send_tmp: send,
|
||||||
mesh_recv: recv,
|
mesh_recv: recv,
|
||||||
mesh_todo: HashMap::default(),
|
mesh_todo: HashMap::default(),
|
||||||
mesh_todos_active: Arc::new(AtomicU64::new(0)),
|
mesh_todos_active: Arc::new(AtomicU64::new(0)),
|
||||||
sprite_data: Arc::new(sprite_data),
|
sprite_data: sprite_render_context.sprite_data,
|
||||||
sprite_col_lights,
|
sprite_col_lights: sprite_render_context.sprite_col_lights,
|
||||||
waves: renderer
|
waves: renderer
|
||||||
.create_texture(
|
.create_texture(
|
||||||
&assets::Image::load_expect("voxygen.texture.waves").read().0,
|
&assets::Image::load_expect("voxygen.texture.waves").read().0,
|
||||||
|
@ -84,6 +84,7 @@ impl SessionState {
|
|||||||
// game world.
|
// game world.
|
||||||
let mut scene = Scene::new(
|
let mut scene = Scene::new(
|
||||||
global_state.window.renderer_mut(),
|
global_state.window.renderer_mut(),
|
||||||
|
&mut global_state.lazy_init,
|
||||||
&*client.borrow(),
|
&*client.borrow(),
|
||||||
&global_state.settings,
|
&global_state.settings,
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user