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:
Joshua Yanovski 2021-04-10 16:53:34 +02:00
parent 12c3ee440e
commit 1bdf3b13a8
9 changed files with 298 additions and 152 deletions

View File

@ -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

View File

@ -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,

View File

@ -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,
)), )),

View File

@ -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);

View File

@ -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,

View File

@ -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)?;

View File

@ -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(

View File

@ -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,

View File

@ -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,
); );