Added support for block kinds in shaders

This commit is contained in:
Joshua Barretto 2023-05-15 17:31:21 +01:00
parent 3afeca67c5
commit aeb72bcf22
20 changed files with 718 additions and 420 deletions

View File

@ -651,8 +651,14 @@ vec3 greedy_extract_col_light_attr(texture2D t_col_light, sampler s_col_light, v
return srgb_to_linear(f_col);
}
vec3 greedy_extract_col_light_terrain(texture2D t_col_light, sampler s_col_light, vec2 f_uv_pos, out float f_light, out float f_glow, out float f_ao, out float f_sky_exposure) {
vec3 greedy_extract_col_light_kind_terrain(
texture2D t_col_light, sampler s_col_light,
texture2D t_kind, sampler s_kind,
vec2 f_uv_pos,
out float f_light, out float f_glow, out float f_ao, out float f_sky_exposure, out uint f_kind
) {
float _f_attr;
f_kind = uint(texelFetch(sampler2D(t_kind, s_kind), ivec2(f_uv_pos), 0).r * 256);
return greedy_extract_col_light_attr(t_col_light, s_col_light, f_uv_pos, f_light, f_glow, f_ao, _f_attr, f_sky_exposure);
}

View File

@ -49,6 +49,10 @@ layout(set = 2, binding = 0)
uniform texture2D t_col_light;
layout(set = 2, binding = 1)
uniform sampler s_col_light;
layout(set = 2, binding = 2)
uniform texture2D t_kind;
layout(set = 2, binding = 3)
uniform sampler s_kind;
layout (std140, set = 3, binding = 0)
uniform u_locals {
@ -86,7 +90,8 @@ void main() {
// vec4 f_col_light = textureProj(t_col_light, vec3(f_uv_pos + 0.5, textureSize(t_col_light, 0)));//(f_uv_pos/* + 0.5*/) / texSize);
// float f_light = textureProj(t_col_light, vec3(f_uv_pos + 0.5, textureSize(t_col_light, 0))).a;//1.0;//f_col_light.a * 4.0;// f_light = float(v_col_light & 0x3Fu) / 64.0;
float f_light, f_glow, f_ao, f_sky_exposure;
vec3 f_col = greedy_extract_col_light_terrain(t_col_light, s_col_light, f_uv_pos, f_light, f_glow, f_ao, f_sky_exposure);
uint f_kind;
vec3 f_col = greedy_extract_col_light_kind_terrain(t_col_light, s_col_light, t_kind, s_kind, f_uv_pos, f_light, f_glow, f_ao, f_sky_exposure, f_kind);
#ifdef EXPERIMENTAL_BAREMINIMUM
tgt_color = vec4(simple_lighting(f_pos.xyz, f_col, f_light), 1);

View File

@ -10,7 +10,8 @@
option_get_or_insert_default,
map_try_insert,
slice_as_chunks,
let_chains
let_chains,
generic_const_exprs
)]
#![recursion_limit = "2048"]

View File

@ -1,4 +1,4 @@
use crate::render::{mesh::Quad, ColLightInfo, TerrainVertex, Vertex};
use crate::render::{mesh::Quad, pipelines::AtlasData, Vertex};
use common_base::{prof_span, span};
use vek::*;
@ -78,7 +78,7 @@ pub struct GreedyConfig<D, FA, FL, FG, FO, FS, FP, FT> {
/// coloring part as a continuation. When called with a final tile size and
/// vector, the continuation will consume the color data and write it to the
/// vector.
pub type SuspendedMesh<'a> = dyn for<'r> FnOnce(&'r mut ColLightInfo) + 'a;
pub type SuspendedMesh<'a, A> = dyn for<'r> FnOnce(&'r mut A, Vec2<u16>) + 'a;
/// Abstraction over different atlas allocators. Useful to swap out the
/// allocator implementation for specific cases (e.g. sprites).
@ -316,15 +316,19 @@ pub type SpriteAtlasAllocator = GuillotiereTiled;
/// Shared state for a greedy mesh, potentially passed along to multiple models.
///
/// For an explanation of why we want this, see `SuspendedMesh`.
pub struct GreedyMesh<'a, Allocator: AtlasAllocator = guillotiere::SimpleAtlasAllocator> {
pub struct GreedyMesh<
'a,
A: AtlasData,
Allocator: AtlasAllocator = guillotiere::SimpleAtlasAllocator,
> {
//atlas: guillotiere::SimpleAtlasAllocator,
atlas: Allocator,
col_lights_size: Vec2<u16>,
tex_size: Vec2<u16>,
max_size: Vec2<u16>,
suspended: Vec<Box<SuspendedMesh<'a>>>,
suspended: Vec<Box<SuspendedMesh<'a, A>>>,
}
impl<'a, Allocator: AtlasAllocator> GreedyMesh<'a, Allocator> {
impl<'a, A: AtlasData, Allocator: AtlasAllocator> GreedyMesh<'a, A, Allocator> {
/// Construct a new greedy mesher.
///
/// Takes as input the maximum allowable size of the texture atlas used to
@ -350,10 +354,10 @@ impl<'a, Allocator: AtlasAllocator> GreedyMesh<'a, Allocator> {
max_size
);
let atlas = Allocator::with_max_size(max_size, config);
let col_lights_size = Vec2::new(1, 1);
let tex_size = Vec2::new(1, 1);
Self {
atlas,
col_lights_size,
tex_size,
max_size,
suspended: Vec::new(),
}
@ -380,12 +384,13 @@ impl<'a, Allocator: AtlasAllocator> GreedyMesh<'a, Allocator> {
FO: for<'r> FnMut(&'r mut D, Vec3<i32>) -> bool + 'a,
FS: for<'r> FnMut(&'r mut D, Vec3<i32>, Vec3<i32>, Vec2<Vec3<i32>>) -> Option<(bool, M)>,
FP: FnMut(Vec2<u16>, Vec2<Vec2<u16>>, Vec3<f32>, Vec2<Vec3<f32>>, Vec3<f32>, &M),
FT: for<'r> FnMut(&'r mut D, Vec3<i32>, u8, u8, bool) -> [u8; 4] + 'a,
FT: for<'r> FnMut(<A::SliceMut<'_> as Iterator>::Item, &'r mut D, Vec3<i32>, u8, u8, bool)
+ 'a,
{
span!(_guard, "push", "GreedyMesh::push");
let cont = greedy_mesh(
let cont = greedy_mesh::<_, _, _, _, _, _, _, _, _, A, _>(
&mut self.atlas,
&mut self.col_lights_size,
&mut self.tex_size,
self.max_size,
config,
);
@ -401,24 +406,32 @@ impl<'a, Allocator: AtlasAllocator> GreedyMesh<'a, Allocator> {
/// potentially use a single staged upload to the GPU.
///
/// Returns the ColLightsInfo corresponding to the constructed atlas.
pub fn finalize(self) -> ColLightInfo {
pub fn finalize(self) -> (A, Vec2<u16>) {
span!(_guard, "finalize", "GreedyMesh::finalize");
let cur_size = self.col_lights_size;
let col_lights = vec![
TerrainVertex::make_col_light(254, 0, Rgb::broadcast(254), true);
cur_size.x as usize * cur_size.y as usize
];
let mut col_lights_info = (col_lights, cur_size);
let mut atlas_texture_data = A::blank_with_size(self.tex_size);
self.suspended.into_iter().for_each(|cont| {
cont(&mut col_lights_info);
cont(&mut atlas_texture_data, self.tex_size);
});
col_lights_info
(atlas_texture_data, self.tex_size)
}
pub fn max_size(&self) -> Vec2<u16> { self.max_size }
}
fn greedy_mesh<'a, M: PartialEq, D: 'a, FA, FL, FG, FO, FS, FP, FT, Allocator: AtlasAllocator>(
fn greedy_mesh<
'a,
M: PartialEq,
D: 'a,
FA,
FL,
FG,
FO,
FS,
FP,
FT,
A: AtlasData,
Allocator: AtlasAllocator,
>(
atlas: &mut Allocator,
col_lights_size: &mut Vec2<u16>,
max_size: Vec2<u16>,
@ -435,7 +448,7 @@ fn greedy_mesh<'a, M: PartialEq, D: 'a, FA, FL, FG, FO, FS, FP, FT, Allocator: A
mut push_quad,
make_face_texel,
}: GreedyConfig<D, FA, FL, FG, FO, FS, FP, FT>,
) -> Box<SuspendedMesh<'a>>
) -> Box<SuspendedMesh<'a, A>>
where
FA: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
FL: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
@ -443,7 +456,7 @@ where
FO: for<'r> FnMut(&'r mut D, Vec3<i32>) -> bool + 'a,
FS: for<'r> FnMut(&'r mut D, Vec3<i32>, Vec3<i32>, Vec2<Vec3<i32>>) -> Option<(bool, M)>,
FP: FnMut(Vec2<u16>, Vec2<Vec2<u16>>, Vec3<f32>, Vec2<Vec3<f32>>, Vec3<f32>, &M),
FT: for<'r> FnMut(&'r mut D, Vec3<i32>, u8, u8, bool) -> [u8; 4] + 'a,
FT: for<'r> FnMut(<A::SliceMut<'_> as Iterator>::Item, &'r mut D, Vec3<i32>, u8, u8, bool) + 'a,
{
span!(_guard, "greedy_mesh");
// TODO: Collect information to see if we can choose a good value here.
@ -572,10 +585,11 @@ where
},
);
Box::new(move |col_lights_info| {
Box::new(move |atlas_texture_data, cur_size| {
let mut data = data;
draw_col_lights(
col_lights_info,
draw_col_lights::<_, A>(
atlas_texture_data,
cur_size,
&mut data,
todo_rects,
draw_delta,
@ -727,8 +741,9 @@ fn add_to_atlas<Allocator: AtlasAllocator>(
// to provide builtin support for what we're doing here.
//
// TODO: See if we can speed this up using SIMD.
fn draw_col_lights<D>(
(col_lights, cur_size): &mut ColLightInfo,
fn draw_col_lights<D, A: AtlasData>(
atlas_texture_data: &mut A,
cur_size: Vec2<u16>,
data: &mut D,
todo_rects: Vec<TodoRect>,
draw_delta: Vec3<i32>,
@ -736,7 +751,14 @@ fn draw_col_lights<D>(
mut get_light: impl FnMut(&mut D, Vec3<i32>) -> f32,
mut get_glow: impl FnMut(&mut D, Vec3<i32>) -> f32,
mut get_opacity: impl FnMut(&mut D, Vec3<i32>) -> bool,
mut make_face_texel: impl FnMut(&mut D, Vec3<i32>, u8, u8, bool) -> [u8; 4],
mut make_face_texel: impl FnMut(
<A::SliceMut<'_> as Iterator>::Item,
&mut D,
Vec3<i32>,
u8,
u8,
bool,
),
) {
todo_rects.into_iter().for_each(|(pos, uv, rect, delta)| {
// NOTE: Conversions are safe because width, height, and offset must be
@ -751,8 +773,8 @@ fn draw_col_lights<D>(
(0..height).for_each(|v| {
let start = cur_size.x as usize * usize::from(top + v) + usize::from(left);
(0..width)
.zip(&mut col_lights[start..start + usize::from(width)])
.for_each(|(u, col_light)| {
.zip(atlas_texture_data.slice_mut(start..start + usize::from(width)))
.for_each(|(u, texel)| {
let pos = pos + uv.x * i32::from(u) + uv.y * i32::from(v);
// TODO: Consider optimizing to take advantage of the fact that this whole
// face should be facing nothing but air (this is not currently true, but
@ -822,7 +844,7 @@ fn draw_col_lights<D>(
let light = (darkness * 31.5) as u8;
let glow = (glowiness * 31.5) as u8;
let ao = ao > 0.7;
*col_light = make_face_texel(data, pos, light, glow, ao);
make_face_texel(texel, data, pos, light, glow, ao);
});
});
});

View File

@ -4,7 +4,7 @@ use crate::{
terrain::FaceKind,
MeshGen,
},
render::{Mesh, ParticleVertex, SpriteVertex, TerrainVertex},
render::{pipelines::FigureSpriteAtlasData, Mesh, ParticleVertex, SpriteVertex, TerrainVertex},
scene::math,
};
use common::{
@ -21,7 +21,7 @@ use vek::*;
pub fn generate_mesh_base_vol_figure<'a: 'b, 'b, V: 'a>(
vol: V,
(greedy, opaque_mesh, offs, scale, bone_idx): (
&'b mut GreedyMesh<'a>,
&'b mut GreedyMesh<'a, FigureSpriteAtlasData>,
&'b mut Mesh<TerrainVertex>,
Vec3<f32>,
Vec3<f32>,
@ -91,7 +91,7 @@ where
|atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm),
));
},
make_face_texel: |vol: &mut V, pos, light, _, _| {
make_face_texel: |col_light: &mut [u8; 4], vol: &mut V, pos, light, _, _| {
let cell = vol.get(pos).ok();
let (glowy, shiny) = cell
.map(|c| (c.is_glowy(), c.is_shiny()))
@ -99,7 +99,7 @@ where
let col = cell
.and_then(|vox| vox.get_color())
.unwrap_or_else(Rgb::zero);
TerrainVertex::make_col_light_figure(light, glowy, shiny, col)
*col_light = TerrainVertex::make_col_light_figure(light, glowy, shiny, col);
},
});
let bounds = math::Aabb {
@ -118,7 +118,7 @@ where
pub fn generate_mesh_base_vol_terrain<'a: 'b, 'b, V: 'a>(
vol: V,
(greedy, opaque_mesh, offs, scale, bone_idx): (
&'b mut GreedyMesh<'a>,
&'b mut GreedyMesh<'a, FigureSpriteAtlasData>,
&'b mut Mesh<TerrainVertex>,
Vec3<f32>,
Vec3<f32>,
@ -201,13 +201,13 @@ where
},
FaceKind::Fluid => {},
},
make_face_texel: |vol: &mut V, pos, light, _, _| {
make_face_texel: |col_light: &mut [u8; 4], vol: &mut V, pos, light, _, _| {
let block = vol.get(pos).ok();
let glowy = block.map(|c| c.get_glow().is_some()).unwrap_or_default();
let col = block
.and_then(|vox| vox.get_color())
.unwrap_or_else(Rgb::zero);
TerrainVertex::make_col_light_figure(light, glowy, false, col)
*col_light = TerrainVertex::make_col_light_figure(light, glowy, false, col);
},
});
let bounds = math::Aabb {
@ -223,7 +223,7 @@ where
pub fn generate_mesh_base_vol_sprite<'a: 'b, 'b, V: 'a>(
vol: V,
(greedy, opaque_mesh, vertical_stripes): (
&'b mut GreedyMesh<'a, greedy::SpriteAtlasAllocator>,
&'b mut GreedyMesh<'a, FigureSpriteAtlasData, greedy::SpriteAtlasAllocator>,
&'b mut Mesh<SpriteVertex>,
bool,
),
@ -336,10 +336,11 @@ where
|atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta),
));
},
make_face_texel: move |flat: &mut _, pos, light, _glow, _ao| {
make_face_texel: move |col_light: &mut [u8; 4], flat: &mut _, pos, light, _glow, _ao| {
let cell = flat_get(flat, pos);
let (glowy, shiny) = (cell.is_glowy(), cell.is_shiny());
TerrainVertex::make_col_light_figure(light, glowy, shiny, get_color(flat, pos))
*col_light =
TerrainVertex::make_col_light_figure(light, glowy, shiny, get_color(flat, pos));
},
});
@ -348,7 +349,7 @@ where
pub fn generate_mesh_base_vol_particle<'a: 'b, 'b, V: 'a>(
vol: V,
greedy: &'b mut GreedyMesh<'a>,
greedy: &'b mut GreedyMesh<'a, FigureSpriteAtlasData>,
) -> MeshGen<ParticleVertex, ParticleVertex, TerrainVertex, ()>
where
V: BaseVol<Vox = Cell> + ReadVol + SizedVol,
@ -421,8 +422,8 @@ where
|atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm),
));
},
make_face_texel: move |vol: &mut V, pos, light, glow, ao| {
TerrainVertex::make_col_light(light, glow, get_color(vol, pos), ao)
make_face_texel: move |col_light: &mut [u8; 4], vol: &mut V, pos, light, glow, ao| {
*col_light = TerrainVertex::make_col_light(light, glow, get_color(vol, pos), ao);
},
});

View File

@ -5,7 +5,7 @@ use crate::{
greedy::{self, GreedyConfig, GreedyMesh},
MeshGen,
},
render::{AltIndices, ColLightInfo, FluidVertex, Mesh, TerrainVertex, Vertex},
render::{AltIndices, FluidVertex, Mesh, TerrainAtlasData, TerrainVertex, Vertex},
scene::terrain::{BlocksOfInterest, DEEP_ALT, SHALLOW_ALT},
};
use common::{
@ -235,7 +235,8 @@ pub fn generate_mesh<'a>(
TerrainVertex,
(
Aabb<f32>,
ColLightInfo,
TerrainAtlasData,
Vec2<u16>,
Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
Arc<dyn Fn(Vec3<i32>) -> f32 + Send + Sync>,
AltIndices,
@ -390,6 +391,7 @@ pub fn generate_mesh<'a>(
let get_glow = |_: &mut (), pos: Vec3<i32>| glow(pos + range.min);
let get_color =
|_: &mut (), pos: Vec3<i32>| flat_get(pos).get_color().unwrap_or_else(Rgb::zero);
let get_kind = |_: &mut (), pos: Vec3<i32>| flat_get(pos).kind() as u8;
let get_opacity = |_: &mut (), pos: Vec3<i32>| !flat_get(pos).is_opaque();
let should_draw = |_: &mut (), pos: Vec3<i32>, delta: Vec3<i32>, _uv| {
should_draw_greedy(pos, delta, &flat_get)
@ -430,8 +432,10 @@ pub fn generate_mesh<'a>(
FluidVertex::new(pos + mesh_delta, norm, vel.xy())
};
let mut greedy =
GreedyMesh::<guillotiere::SimpleAtlasAllocator>::new(max_size, greedy::general_config());
let mut greedy = GreedyMesh::<TerrainAtlasData, guillotiere::SimpleAtlasAllocator>::new(
max_size,
greedy::general_config(),
);
let mut opaque_deep = Vec::new();
let mut opaque_shallow = Vec::new();
let mut opaque_surface = Vec::new();
@ -486,8 +490,14 @@ pub fn generate_mesh<'a>(
));
},
},
make_face_texel: |data: &mut (), pos, light, glow, ao| {
TerrainVertex::make_col_light(light, glow, get_color(data, pos), ao)
make_face_texel: |(col_light, kind): (&mut [u8; 4], &mut u8),
data: &mut (),
pos,
light,
glow,
ao| {
*col_light = TerrainVertex::make_col_light(light, glow, get_color(data, pos), ao);
*kind = get_kind(data, pos);
},
});
@ -496,7 +506,7 @@ pub fn generate_mesh<'a>(
min: min_bounds,
max: max_bounds + min_bounds,
};
let (col_lights, col_lights_size) = greedy.finalize();
let (atlas_data, atlas_size) = greedy.finalize();
let deep_end = opaque_deep.len()
* if TerrainVertex::QUADS_INDEX.is_some() {
@ -526,7 +536,8 @@ pub fn generate_mesh<'a>(
Mesh::new(),
(
bounds,
(col_lights, col_lights_size),
atlas_data,
atlas_size,
Arc::new(light),
Arc::new(glow),
alt_indices,

View File

@ -46,7 +46,8 @@ pub use self::{
TextureBindGroup as UiTextureBindGroup, UploadBatchId as UiUploadBatchId,
Vertex as UiVertex,
},
GlobalModel, Globals, GlobalsBindGroup, GlobalsLayouts, Light, Shadow,
FigureSpriteAtlasData, GlobalModel, Globals, GlobalsBindGroup, GlobalsLayouts, Light,
Shadow, TerrainAtlasData,
},
renderer::{
drawer::{
@ -55,7 +56,7 @@ pub use self::{
TerrainDrawer, TerrainShadowDrawer, ThirdPassDrawer, TrailDrawer,
TransparentPassDrawer, UiDrawer, VolumetricPassDrawer, UI_PREMULTIPLY_PASS,
},
AltIndices, ColLightInfo, CullingMode, Renderer,
AltIndices, CullingMode, Renderer,
},
texture::Texture,
};

View File

@ -1,6 +1,7 @@
use super::{
super::{AaMode, Bound, Consts, GlobalsLayouts, Mesh, Model},
terrain::Vertex,
AtlasData,
};
use crate::mesh::greedy::GreedyMesh;
use bytemuck::{Pod, Zeroable};
@ -87,7 +88,7 @@ pub struct FigureModel {
impl FigureModel {
/// Start a greedy mesh designed for figure bones.
pub fn make_greedy<'a>() -> GreedyMesh<'a> {
pub fn make_greedy<'a>() -> GreedyMesh<'a, FigureSpriteAtlasData> {
// NOTE: Required because we steal two bits from the normal in the shadow uint
// in order to store the bone index. The two bits are instead taken out
// of the atlas coordinates, which is why we "only" allow 1 << 15 per
@ -185,7 +186,7 @@ impl FigurePipeline {
bind_group_layouts: &[
&global_layout.globals,
&global_layout.shadow_textures,
&global_layout.col_light,
global_layout.figure_sprite_atlas_layout.layout(),
&layout.locals,
],
});
@ -253,3 +254,57 @@ impl FigurePipeline {
}
}
}
/// Represents texture that can be converted into texture atlases for figures
/// and sprites.
pub struct FigureSpriteAtlasData {
pub col_lights: Vec<[u8; 4]>,
}
impl AtlasData for FigureSpriteAtlasData {
type SliceMut<'a> = std::slice::IterMut<'a, [u8; 4]>;
const TEXTURES: usize = 1;
fn blank_with_size(sz: Vec2<u16>) -> Self {
let col_lights =
vec![Vertex::make_col_light(254, 0, Rgb::broadcast(254), true); sz.as_().product()];
Self { col_lights }
}
fn as_texture_data(&self) -> [(wgpu::TextureFormat, &[u8]); Self::TEXTURES] {
[(
wgpu::TextureFormat::Rgba8Unorm,
bytemuck::cast_slice(self.col_lights.as_slice()),
)]
}
fn layout() -> Vec<wgpu::BindGroupLayoutEntry> {
vec![
// col lights
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler {
filtering: true,
comparison: false,
},
count: None,
},
]
}
fn slice_mut(&mut self, range: std::ops::Range<usize>) -> Self::SliceMut<'_> {
self.col_lights[range].iter_mut()
}
}

View File

@ -16,12 +16,15 @@ pub mod terrain;
pub mod trail;
pub mod ui;
use super::{Consts, Texture};
use super::{Consts, Renderer, Texture};
use crate::scene::camera::CameraMode;
use bytemuck::{Pod, Zeroable};
use common::terrain::BlockKind;
use std::marker::PhantomData;
use vek::*;
pub use self::{figure::FigureSpriteAtlasData, terrain::TerrainAtlasData};
// TODO: auto insert these into shaders
pub const MAX_POINT_LIGHT_COUNT: usize = 20;
pub const MAX_FIGURE_SHADOW_COUNT: usize = 24;
@ -278,16 +281,114 @@ pub struct ShadowTexturesBindGroup {
pub struct GlobalsLayouts {
pub globals: wgpu::BindGroupLayout,
pub col_light: wgpu::BindGroupLayout,
pub figure_sprite_atlas_layout: VoxelAtlasLayout<FigureSpriteAtlasData>,
pub terrain_atlas_layout: VoxelAtlasLayout<TerrainAtlasData>,
pub shadow_textures: wgpu::BindGroupLayout,
}
pub struct ColLights<Locals> {
/// A type representing a set of textures that have the same atlas layout and
/// pertain to a greedy voxel structure.
pub struct AtlasTextures<Locals, S: AtlasData>
where
[(); S::TEXTURES]:,
{
pub(super) bind_group: wgpu::BindGroup,
pub texture: Texture,
pub textures: [Texture; S::TEXTURES],
phantom: std::marker::PhantomData<Locals>,
}
pub struct VoxelAtlasLayout<S: AtlasData>(wgpu::BindGroupLayout, PhantomData<S>);
impl<S: AtlasData> VoxelAtlasLayout<S> {
pub fn new(device: &wgpu::Device) -> Self {
let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &S::layout(),
});
Self(layout, PhantomData)
}
pub fn layout(&self) -> &wgpu::BindGroupLayout { &self.0 }
}
/// A trait implemented by texture atlas groups.
///
/// Terrain, figures, sprites, etc. all use texture atlases but have different
/// requirements, such as that layers provided by each atlas. This trait
/// abstracts over these cases.
pub trait AtlasData {
/// The number of texture channels that this atlas has.
const TEXTURES: usize;
/// Abstracts over a slice into the texture data, as returned by
/// [`AtlasData::slice_mut`].
type SliceMut<'a>: Iterator
where
Self: 'a;
/// Return blank atlas data upon which texels can be applied.
fn blank_with_size(sz: Vec2<u16>) -> Self;
/// Return an array of texture formats and data for each texture layer in
/// the atlas.
fn as_texture_data(&self) -> [(wgpu::TextureFormat, &[u8]); Self::TEXTURES];
/// Return a layout entry that corresponds to the texture layers in the
/// atlas.
fn layout() -> Vec<wgpu::BindGroupLayoutEntry>;
/// Take a sub-slice of the texture data for each layer in the atlas.
fn slice_mut(&mut self, range: std::ops::Range<usize>) -> Self::SliceMut<'_>;
/// Create textures on the GPU corresponding to the layers in the atlas.
fn create_textures(
&self,
renderer: &mut Renderer,
atlas_size: Vec2<u16>,
) -> [Texture; Self::TEXTURES] {
self.as_texture_data().map(|(fmt, data)| {
let texture_info = wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: u32::from(atlas_size.x),
height: u32::from(atlas_size.y),
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: fmt,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
};
let sampler_info = wgpu::SamplerDescriptor {
label: None,
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
border_color: Some(wgpu::SamplerBorderColor::TransparentBlack),
..Default::default()
};
let view_info = wgpu::TextureViewDescriptor {
label: None,
format: Some(fmt),
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
};
renderer.create_texture_with_data_raw(&texture_info, &view_info, &sampler_info, data)
})
}
}
impl GlobalsLayouts {
pub fn base_globals_layout() -> Vec<wgpu::BindGroupLayoutEntry> {
vec![
@ -456,32 +557,6 @@ impl GlobalsLayouts {
entries: &Self::base_globals_layout(),
});
let col_light = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[
// col lights
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler {
filtering: true,
comparison: false,
},
count: None,
},
],
});
let shadow_textures = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[
@ -550,8 +625,9 @@ impl GlobalsLayouts {
Self {
globals,
col_light,
shadow_textures,
terrain_atlas_layout: VoxelAtlasLayout::new(device),
figure_sprite_atlas_layout: VoxelAtlasLayout::new(device),
}
}
@ -691,28 +767,35 @@ impl GlobalsLayouts {
ShadowTexturesBindGroup { bind_group }
}
pub fn bind_col_light<Locals>(
pub fn bind_atlas_textures<Locals, S: AtlasData>(
&self,
device: &wgpu::Device,
col_light: Texture,
) -> ColLights<Locals> {
layout: &VoxelAtlasLayout<S>,
textures: [Texture; S::TEXTURES],
) -> AtlasTextures<Locals, S> {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &self.col_light,
entries: &[
layout: layout.layout(),
entries: &textures
.iter()
.enumerate()
.flat_map(|(i, tex)| {
[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&col_light.view),
binding: i as u32 * 2,
resource: wgpu::BindingResource::TextureView(&tex.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&col_light.sampler),
binding: i as u32 * 2 + 1,
resource: wgpu::BindingResource::Sampler(&tex.sampler),
},
],
]
})
.collect::<Vec<_>>(),
});
ColLights {
texture: col_light,
AtlasTextures {
textures,
bind_group,
phantom: std::marker::PhantomData,
}

View File

@ -1,6 +1,6 @@
use super::super::{
AaMode, Bound, ColLightInfo, Consts, DebugLayout, DebugVertex, FigureLayout, GlobalsLayouts,
Renderer, TerrainLayout, TerrainVertex, Texture,
AaMode, Bound, Consts, DebugLayout, DebugVertex, FigureLayout, GlobalsLayouts, TerrainLayout,
TerrainVertex,
};
use bytemuck::{Pod, Zeroable};
use vek::*;
@ -79,55 +79,6 @@ impl Default for PointLightMatrix {
fn default() -> Self { Self::new(Mat4::identity()) }
}
pub fn create_col_lights(
renderer: &mut Renderer,
(col_lights, col_lights_size): &ColLightInfo,
) -> Texture {
let texture_info = wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: u32::from(col_lights_size.x),
height: u32::from(col_lights_size.y),
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
};
let sampler_info = wgpu::SamplerDescriptor {
label: None,
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
border_color: Some(wgpu::SamplerBorderColor::TransparentBlack),
..Default::default()
};
let view_info = wgpu::TextureViewDescriptor {
label: None,
format: Some(wgpu::TextureFormat::Rgba8Unorm),
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
};
renderer.create_texture_with_data_raw(
&texture_info,
&view_info,
&sampler_info,
bytemuck::cast_slice(col_lights),
)
}
pub struct ShadowFigurePipeline {
pub pipeline: wgpu::RenderPipeline,
}

View File

@ -1,8 +1,6 @@
use super::{
super::{
buffer::Buffer, AaMode, GlobalsLayouts, Mesh, TerrainLayout, Texture, Vertex as VertexTrait,
},
lod_terrain, GlobalModel,
super::{buffer::Buffer, AaMode, GlobalsLayouts, Mesh, TerrainLayout, Vertex as VertexTrait},
lod_terrain, GlobalModel, Texture,
};
use bytemuck::{Pod, Zeroable};
use std::mem;
@ -278,7 +276,7 @@ impl SpritePipeline {
&layout.globals,
&global_layout.shadow_textures,
// Note: mergable with globals
&global_layout.col_light,
global_layout.figure_sprite_atlas_layout.layout(),
&terrain_layout.locals,
],
});

View File

@ -1,4 +1,7 @@
use super::super::{AaMode, Bound, Consts, GlobalsLayouts, Vertex as VertexTrait};
use super::{
super::{AaMode, Bound, Consts, GlobalsLayouts, Vertex as VertexTrait},
AtlasData,
};
use bytemuck::{Pod, Zeroable};
use std::mem;
use vek::*;
@ -237,7 +240,7 @@ impl TerrainPipeline {
bind_group_layouts: &[
&global_layout.globals,
&global_layout.shadow_textures,
&global_layout.col_light,
global_layout.terrain_atlas_layout.layout(),
&layout.locals,
],
});
@ -305,3 +308,84 @@ impl TerrainPipeline {
}
}
}
/// Represents texture that can be converted into texture atlases for terrain.
pub struct TerrainAtlasData {
pub col_lights: Vec<[u8; 4]>,
pub kinds: Vec<u8>,
}
impl AtlasData for TerrainAtlasData {
type SliceMut<'a> =
std::iter::Zip<std::slice::IterMut<'a, [u8; 4]>, std::slice::IterMut<'a, u8>>;
const TEXTURES: usize = 2;
fn blank_with_size(sz: Vec2<u16>) -> Self {
let col_lights =
vec![Vertex::make_col_light(254, 0, Rgb::broadcast(254), true); sz.as_().product()];
let kinds = vec![0; sz.as_().product()];
Self { col_lights, kinds }
}
fn as_texture_data(&self) -> [(wgpu::TextureFormat, &[u8]); Self::TEXTURES] {
[
(
wgpu::TextureFormat::Rgba8Unorm,
bytemuck::cast_slice(&self.col_lights),
),
(wgpu::TextureFormat::R8Unorm, &self.kinds),
]
}
fn layout() -> Vec<wgpu::BindGroupLayoutEntry> {
vec![
// col lights
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler {
filtering: true,
comparison: false,
},
count: None,
},
// kind
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
ty: wgpu::BindingType::Sampler {
filtering: true,
comparison: false,
},
count: None,
},
]
}
fn slice_mut(&mut self, range: std::ops::Range<usize>) -> Self::SliceMut<'_> {
self.col_lights[range.clone()]
.iter_mut()
.zip(self.kinds[range].iter_mut())
}
}

View File

@ -40,12 +40,6 @@ use std::sync::Arc;
use tracing::{error, info, warn};
use vek::*;
// TODO: yeet this somewhere else
/// A type representing data that can be converted to an immutable texture map
/// of ColLight data (used for texture atlases created during greedy meshing).
// TODO: revert to u16
pub type ColLightInfo = (Vec<[u8; 4]>, Vec2<u16>);
const QUAD_INDEX_BUFFER_U16_START_VERT_LEN: u16 = 3000;
const QUAD_INDEX_BUFFER_U32_START_VERT_LEN: u32 = 3000;
@ -1427,13 +1421,12 @@ impl Renderer {
/// Update a texture with the provided offset, size, and data.
///
/// Currently only supports Rgba8Srgb
pub fn update_texture(
pub fn update_texture<T: bytemuck::Pod>(
&mut self,
texture: &Texture, /* <T> */
texture: &Texture,
offset: [u32; 2],
size: [u32; 2],
// TODO: be generic over pixel type
data: &[[u8; 4]],
data: &[T],
) {
texture.update(&self.queue, offset, size, bytemuck::cast_slice(data))
}

View File

@ -3,8 +3,8 @@ use crate::render::pipelines::rain_occlusion;
use super::{
super::{
pipelines::{
debug, figure, lod_terrain, shadow, sprite, terrain, ui, ColLights, GlobalModel,
GlobalsBindGroup,
debug, figure, lod_terrain, shadow, sprite, terrain, ui, AtlasTextures,
FigureSpriteAtlasData, GlobalModel, GlobalsBindGroup, TerrainAtlasData,
},
texture::Texture,
},
@ -90,15 +90,37 @@ impl Renderer {
.bind_locals(&self.device, locals)
}
pub fn figure_bind_col_light(&self, col_light: Texture) -> ColLights<figure::Locals> {
self.layouts.global.bind_col_light(&self.device, col_light)
pub fn figure_bind_atlas_textures(
&self,
col_light: Texture,
) -> AtlasTextures<figure::Locals, FigureSpriteAtlasData> {
self.layouts.global.bind_atlas_textures(
&self.device,
&self.layouts.global.figure_sprite_atlas_layout,
[col_light],
)
}
pub fn terrain_bind_col_light(&self, col_light: Texture) -> ColLights<terrain::Locals> {
self.layouts.global.bind_col_light(&self.device, col_light)
pub fn terrain_bind_atlas_textures(
&self,
col_light: Texture,
kinds: Texture,
) -> AtlasTextures<terrain::Locals, TerrainAtlasData> {
self.layouts.global.bind_atlas_textures(
&self.device,
&self.layouts.global.terrain_atlas_layout,
[col_light, kinds],
)
}
pub fn sprite_bind_col_light(&self, col_light: Texture) -> ColLights<sprite::Locals> {
self.layouts.global.bind_col_light(&self.device, col_light)
pub fn sprite_bind_atlas_textures(
&self,
col_light: Texture,
) -> AtlasTextures<sprite::Locals, FigureSpriteAtlasData> {
self.layouts.global.bind_atlas_textures(
&self.device,
&self.layouts.global.figure_sprite_atlas_layout,
[col_light],
)
}
}

View File

@ -7,7 +7,8 @@ use super::{
model::{DynamicModel, Model, SubModel},
pipelines::{
blit, bloom, clouds, debug, figure, fluid, lod_object, lod_terrain, particle, shadow,
skybox, sprite, terrain, trail, ui, ColLights, GlobalsBindGroup,
skybox, sprite, terrain, trail, ui, AtlasTextures, FigureSpriteAtlasData,
GlobalsBindGroup, TerrainAtlasData,
},
AltIndices, CullingMode,
},
@ -950,7 +951,7 @@ impl<'pass> FirstPassDrawer<'pass> {
TerrainDrawer {
render_pass,
col_lights: None,
atlas_textures: None,
}
}
@ -966,14 +967,14 @@ impl<'pass> FirstPassDrawer<'pass> {
pub fn draw_sprites<'data: 'pass>(
&mut self,
globals: &'data sprite::SpriteGlobalsBindGroup,
col_lights: &'data ColLights<sprite::Locals>,
atlas_textures: &'data AtlasTextures<sprite::Locals, FigureSpriteAtlasData>,
) -> SpriteDrawer<'_, 'pass> {
let mut render_pass = self.render_pass.scope("sprites", self.borrow.device);
render_pass.set_pipeline(&self.pipelines.sprite.pipeline);
set_quad_index_buffer::<sprite::Vertex>(&mut render_pass, self.borrow);
render_pass.set_bind_group(0, &globals.bind_group, &[]);
render_pass.set_bind_group(2, &col_lights.bind_group, &[]);
render_pass.set_bind_group(2, &atlas_textures.bind_group, &[]);
SpriteDrawer {
render_pass,
@ -1028,10 +1029,10 @@ impl<'pass_ref, 'pass: 'pass_ref> FigureDrawer<'pass_ref, 'pass> {
model: SubModel<'data, terrain::Vertex>,
locals: &'data figure::BoundLocals,
// TODO: don't rebind this every time once they are shared between figures
col_lights: &'data ColLights<figure::Locals>,
atlas_textures: &'data AtlasTextures<figure::Locals, FigureSpriteAtlasData>,
) {
self.render_pass
.set_bind_group(2, &col_lights.bind_group, &[]);
.set_bind_group(2, &atlas_textures.bind_group, &[]);
self.render_pass.set_bind_group(3, &locals.bind_group, &[]);
self.render_pass.set_vertex_buffer(0, model.buf());
self.render_pass
@ -1042,14 +1043,14 @@ impl<'pass_ref, 'pass: 'pass_ref> FigureDrawer<'pass_ref, 'pass> {
#[must_use]
pub struct TerrainDrawer<'pass_ref, 'pass: 'pass_ref> {
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,
col_lights: Option<&'pass_ref Arc<ColLights<terrain::Locals>>>,
atlas_textures: Option<&'pass_ref Arc<AtlasTextures<terrain::Locals, TerrainAtlasData>>>,
}
impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> {
pub fn draw<'data: 'pass>(
&mut self,
model: &'data Model<terrain::Vertex>,
col_lights: &'data Arc<ColLights<terrain::Locals>>,
atlas_textures: &'data Arc<AtlasTextures<terrain::Locals, TerrainAtlasData>>,
locals: &'data terrain::BoundLocals,
alt_indices: &'data AltIndices,
culling_mode: CullingMode,
@ -1067,15 +1068,15 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainDrawer<'pass_ref, 'pass> {
let submodel = model.submodel(index_range);
if self.col_lights
if self.atlas_textures
// Check if we are still using the same atlas texture as the previous drawn
// chunk
.filter(|current_col_lights| Arc::ptr_eq(current_col_lights, col_lights))
.filter(|current_atlas_textures| Arc::ptr_eq(current_atlas_textures, atlas_textures))
.is_none()
{
self.render_pass
.set_bind_group(2, &col_lights.bind_group, &[]);
self.col_lights = Some(col_lights);
.set_bind_group(2, &atlas_textures.bind_group, &[]);
self.atlas_textures = Some(atlas_textures);
};
self.render_pass.set_bind_group(3, &locals.bind_group, &[]);

View File

@ -8,8 +8,8 @@ use crate::{
segment::{generate_mesh_base_vol_figure, generate_mesh_base_vol_terrain},
},
render::{
BoneMeshes, ColLightInfo, FigureModel, Instances, Mesh, Renderer, SpriteInstance,
TerrainVertex,
pipelines, BoneMeshes, FigureModel, FigureSpriteAtlasData, Instances, Mesh, Renderer,
SpriteInstance, TerrainVertex,
},
scene::{
camera::CameraMode,
@ -42,7 +42,8 @@ use vek::*;
/// A type produced by mesh worker threads corresponding to the information
/// needed to mesh figures.
pub struct MeshWorkerResponse<const N: usize> {
col_light: ColLightInfo,
atlas_texture_data: FigureSpriteAtlasData,
atlas_size: Vec2<u16>,
opaque: Mesh<TerrainVertex>,
bounds: anim::vek::Aabb<f32>,
vertex_range: [Range<u32>; N],
@ -51,7 +52,10 @@ pub struct MeshWorkerResponse<const N: usize> {
/// A type produced by mesh worker threads corresponding to the information
/// needed to mesh figures.
pub struct TerrainMeshWorkerResponse<const N: usize> {
col_light: ColLightInfo,
// TODO: This probably needs fixing to use `TerrainAtlasData`. However, right now, we just
// treat volume entities like regular figures for the sake of rendering.
atlas_texture_data: FigureSpriteAtlasData,
atlas_size: Vec2<u16>,
opaque: Mesh<TerrainVertex>,
bounds: anim::vek::Aabb<f32>,
vertex_range: [Range<u32>; N],
@ -323,7 +327,7 @@ where
pub fn get_model<'b>(
&'b self,
// TODO: If we ever convert to using an atlas here, use this.
_col_lights: &super::FigureColLights,
_atlas: &super::FigureAtlas,
body: Skel::Body,
inventory: Option<&Inventory>,
// TODO: Consider updating the tick by putting it in a Cell.
@ -358,7 +362,7 @@ where
pub fn clear_models(&mut self) { self.models.clear(); }
pub fn clean(&mut self, col_lights: &mut super::FigureColLights, tick: u64)
pub fn clean(&mut self, atlas: &mut super::FigureAtlas, tick: u64)
where
<Skel::Body as BodySpec>::Spec: Clone,
{
@ -370,7 +374,7 @@ where
let alive = *last_used + delta > tick;
if !alive {
if let Some(model_entry) = model_entry.get_done() {
col_lights.atlas.deallocate(model_entry.allocation().id);
atlas.atlas.deallocate(model_entry.allocation().id);
}
}
alive
@ -391,7 +395,7 @@ where
pub fn get_or_create_model<'c>(
&'c mut self,
renderer: &mut Renderer,
col_lights: &mut super::FigureColLights,
atlas: &mut super::FigureAtlas,
body: Skel::Body,
inventory: Option<&Inventory>,
extra: <Skel::Body as BodySpec>::Extra,
@ -428,15 +432,17 @@ where
match model {
FigureModelEntryFuture::Pending(recv) => {
if let Some(MeshWorkerResponse {
col_light,
atlas_texture_data,
atlas_size,
opaque,
bounds,
vertex_range,
}) = Arc::get_mut(recv).take().and_then(|cell| cell.take())
{
let model_entry = col_lights.create_figure(
let model_entry = atlas.create_figure(
renderer,
col_light,
atlas_texture_data,
atlas_size,
(opaque, bounds),
vertex_range,
);
@ -480,7 +486,7 @@ where
// list. Returns the vertex bounds of the meshed model within the opaque
// mesh.
let mut make_model = |generate_mesh: for<'a, 'b> fn(
&mut GreedyMesh<'a>,
&mut GreedyMesh<'a, FigureSpriteAtlasData>,
&'b mut _,
&'a _,
_,
@ -528,7 +534,7 @@ where
};
fn generate_mesh<'a>(
greedy: &mut GreedyMesh<'a>,
greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
opaque_mesh: &mut Mesh<TerrainVertex>,
segment: &'a Segment,
offset: Vec3<f32>,
@ -542,7 +548,7 @@ where
}
fn generate_mesh_lod_mid<'a>(
greedy: &mut GreedyMesh<'a>,
greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
opaque_mesh: &mut Mesh<TerrainVertex>,
segment: &'a Segment,
offset: Vec3<f32>,
@ -563,7 +569,7 @@ where
}
fn generate_mesh_lod_low<'a>(
greedy: &mut GreedyMesh<'a>,
greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
opaque_mesh: &mut Mesh<TerrainVertex>,
segment: &'a Segment,
offset: Vec3<f32>,
@ -589,8 +595,10 @@ where
make_model(generate_mesh_lod_low),
];
let (atlas_texture_data, atlas_size) = greedy.finalize();
slot_.store(Some(MeshWorkerResponse {
col_light: greedy.finalize(),
atlas_texture_data,
atlas_size,
opaque,
bounds: figure_bounds,
vertex_range: models,
@ -619,7 +627,7 @@ where
pub fn get_or_create_terrain_model<'c>(
&'c mut self,
renderer: &mut Renderer,
col_lights: &mut super::FigureColLights,
atlas: &mut super::FigureAtlas,
body: Skel::Body,
extra: <Skel::Body as BodySpec>::Extra,
tick: u64,
@ -647,7 +655,8 @@ where
match model {
TerrainModelEntryFuture::Pending(recv) => {
if let Some(TerrainMeshWorkerResponse {
col_light,
atlas_texture_data,
atlas_size,
opaque,
bounds,
vertex_range,
@ -656,9 +665,10 @@ where
blocks_offset,
}) = Arc::get_mut(recv).take().and_then(|cell| cell.take())
{
let model_entry = col_lights.create_terrain(
let model_entry = atlas.create_terrain(
renderer,
col_light,
atlas_texture_data,
atlas_size,
(opaque, bounds),
vertex_range,
sprite_instances,
@ -707,7 +717,7 @@ where
// list. Returns the vertex bounds of the meshed model within the opaque
// mesh.
let mut make_model = |generate_mesh: for<'a, 'b> fn(
&mut GreedyMesh<'a>,
&mut GreedyMesh<'a, FigureSpriteAtlasData>,
&'b mut _,
&'a _,
_,
@ -755,7 +765,7 @@ where
};
fn generate_mesh<'a>(
greedy: &mut GreedyMesh<'a>,
greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
opaque_mesh: &mut Mesh<TerrainVertex>,
segment: &'a TerrainSegment,
offset: Vec3<f32>,
@ -769,7 +779,7 @@ where
}
fn generate_mesh_lod_mid<'a>(
greedy: &mut GreedyMesh<'a>,
greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
opaque_mesh: &mut Mesh<TerrainVertex>,
segment: &'a TerrainSegment,
offset: Vec3<f32>,
@ -790,7 +800,7 @@ where
}
fn generate_mesh_lod_low<'a>(
greedy: &mut GreedyMesh<'a>,
greedy: &mut GreedyMesh<'a, FigureSpriteAtlasData>,
opaque_mesh: &mut Mesh<TerrainVertex>,
segment: &'a TerrainSegment,
offset: Vec3<f32>,
@ -819,13 +829,15 @@ where
let (dyna, offset) = &meshes[0].as_ref().unwrap();
let block_iter = dyna.vol_iter(Vec3::zero(), dyna.sz.as_()).map(|(pos, block)| (pos, *block));
let (atlas_texture_data, atlas_size) = greedy.finalize();
slot_.store(Some(TerrainMeshWorkerResponse {
col_light: greedy.finalize(),
atlas_texture_data,
atlas_size,
opaque,
bounds: figure_bounds,
vertex_range: models,
sprite_instances: {
let mut instances = from_fn(|_| Vec::new());
let mut instances = from_fn::<Vec<pipelines::sprite::Instance>, SPRITE_LOD_LEVELS, _>(|_| Vec::new());
get_sprite_instances(
&mut instances,
|lod, instance, _| {

View File

@ -12,11 +12,11 @@ use crate::{
pipelines::{
self,
terrain::{BoundLocals as BoundTerrainLocals, Locals as TerrainLocals},
trail, ColLights,
trail, AtlasData, AtlasTextures, FigureSpriteAtlasData,
},
AltIndices, ColLightInfo, CullingMode, FigureBoneData, FigureDrawer, FigureLocals,
FigureModel, FigureShadowDrawer, Instances, Mesh, Quad, RenderError, Renderer,
SpriteDrawer, SpriteInstance, SubModel, TerrainVertex,
AltIndices, CullingMode, FigureBoneData, FigureDrawer, FigureLocals, FigureModel,
FigureShadowDrawer, Instances, Mesh, Quad, RenderError, Renderer, SpriteDrawer,
SpriteInstance, SubModel, TerrainVertex,
},
scene::{
camera::{Camera, CameraMode, Dependents},
@ -80,7 +80,7 @@ pub type CameraData<'a> = (&'a Camera, f32);
pub type FigureModelRef<'a> = (
&'a pipelines::figure::BoundLocals,
SubModel<'a, TerrainVertex>,
&'a ColLights<pipelines::figure::Locals>,
&'a AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData>,
);
pub trait ModelEntry {
@ -88,7 +88,7 @@ pub trait ModelEntry {
fn lod_model(&self, lod: usize) -> Option<SubModel<TerrainVertex>>;
fn col_lights(&self) -> &ColLights<pipelines::figure::Locals>;
fn atlas_textures(&self) -> &AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData>;
}
/// An entry holding enough information to draw or destroy a figure in a
@ -104,7 +104,7 @@ pub struct FigureModelEntry<const N: usize> {
/// Texture used to store color/light information for this figure entry.
/* TODO: Consider using mipmaps instead of storing multiple texture atlases for different
* LOD levels. */
col_lights: ColLights<pipelines::figure::Locals>,
atlas_textures: AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData>,
/// Vertex ranges stored in this figure entry; there may be several for one
/// figure, because of LOD models.
lod_vertex_ranges: [Range<u32>; N],
@ -122,7 +122,9 @@ impl<const N: usize> ModelEntry for FigureModelEntry<N> {
.map(|m| m.submodel(self.lod_vertex_ranges[lod].clone()))
}
fn col_lights(&self) -> &ColLights<pipelines::figure::Locals> { &self.col_lights }
fn atlas_textures(&self) -> &AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData> {
&self.atlas_textures
}
}
/// An entry holding enough information to draw or destroy a figure in a
@ -138,7 +140,7 @@ pub struct TerrainModelEntry<const N: usize> {
/// Texture used to store color/light information for this figure entry.
/* TODO: Consider using mipmaps instead of storing multiple texture atlases for different
* LOD levels. */
col_lights: ColLights<pipelines::figure::Locals>,
atlas_textures: AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData>,
/// Vertex ranges stored in this figure entry; there may be several for one
/// figure, because of LOD models.
lod_vertex_ranges: [Range<u32>; N],
@ -162,7 +164,9 @@ impl<const N: usize> ModelEntry for TerrainModelEntry<N> {
.map(|m| m.submodel(self.lod_vertex_ranges[lod].clone()))
}
fn col_lights(&self) -> &ColLights<pipelines::figure::Locals> { &self.col_lights }
fn atlas_textures(&self) -> &AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData> {
&self.atlas_textures
}
}
#[derive(Clone, Copy)]
@ -179,10 +183,12 @@ impl<'a, const N: usize> ModelEntryRef<'a, N> {
}
}
fn col_lights(&self) -> &'a ColLights<pipelines::figure::Locals> {
fn atlas_textures(
&self,
) -> &'a AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData> {
match self {
ModelEntryRef::Figure(e) => e.col_lights(),
ModelEntryRef::Terrain(e) => e.col_lights(),
ModelEntryRef::Figure(e) => e.atlas_textures(),
ModelEntryRef::Terrain(e) => e.atlas_textures(),
}
}
}
@ -498,7 +504,7 @@ impl FigureMgrStates {
}
pub struct FigureMgr {
col_lights: FigureColLights,
atlas: FigureAtlas,
model_cache: FigureModelCache,
theropod_model_cache: FigureModelCache<TheropodSkeleton>,
quadruped_small_model_cache: FigureModelCache<QuadrupedSmallSkeleton>,
@ -523,7 +529,7 @@ pub struct FigureMgr {
impl FigureMgr {
pub fn new(renderer: &mut Renderer) -> Self {
Self {
col_lights: FigureColLights::new(renderer),
atlas: FigureAtlas::new(renderer),
model_cache: FigureModelCache::new(),
theropod_model_cache: FigureModelCache::new(),
quadruped_small_model_cache: FigureModelCache::new(),
@ -546,7 +552,7 @@ impl FigureMgr {
}
}
pub fn col_lights(&self) -> &FigureColLights { &self.col_lights }
pub fn atlas(&self) -> &FigureAtlas { &self.atlas }
fn any_watcher_reloaded(&mut self) -> bool {
self.model_cache.watcher_reloaded()
@ -573,7 +579,7 @@ impl FigureMgr {
span!(_guard, "clean", "FigureManager::clean");
if self.any_watcher_reloaded() {
self.col_lights.atlas.clear();
self.atlas.atlas.clear();
self.model_cache.clear_models();
self.theropod_model_cache.clear_models();
@ -595,33 +601,26 @@ impl FigureMgr {
self.arthropod_model_cache.clear_models();
}
self.model_cache.clean(&mut self.col_lights, tick);
self.theropod_model_cache.clean(&mut self.col_lights, tick);
self.model_cache.clean(&mut self.atlas, tick);
self.theropod_model_cache.clean(&mut self.atlas, tick);
self.quadruped_small_model_cache
.clean(&mut self.col_lights, tick);
.clean(&mut self.atlas, tick);
self.quadruped_medium_model_cache
.clean(&mut self.col_lights, tick);
self.quadruped_low_model_cache
.clean(&mut self.col_lights, tick);
self.bird_medium_model_cache
.clean(&mut self.col_lights, tick);
self.bird_large_model_cache
.clean(&mut self.col_lights, tick);
self.dragon_model_cache.clean(&mut self.col_lights, tick);
self.fish_medium_model_cache
.clean(&mut self.col_lights, tick);
self.fish_small_model_cache
.clean(&mut self.col_lights, tick);
self.biped_large_model_cache
.clean(&mut self.col_lights, tick);
self.biped_small_model_cache
.clean(&mut self.col_lights, tick);
self.object_model_cache.clean(&mut self.col_lights, tick);
self.item_drop_model_cache.clean(&mut self.col_lights, tick);
self.ship_model_cache.clean(&mut self.col_lights, tick);
self.golem_model_cache.clean(&mut self.col_lights, tick);
self.volume_model_cache.clean(&mut self.col_lights, tick);
self.arthropod_model_cache.clean(&mut self.col_lights, tick);
.clean(&mut self.atlas, tick);
self.quadruped_low_model_cache.clean(&mut self.atlas, tick);
self.bird_medium_model_cache.clean(&mut self.atlas, tick);
self.bird_large_model_cache.clean(&mut self.atlas, tick);
self.dragon_model_cache.clean(&mut self.atlas, tick);
self.fish_medium_model_cache.clean(&mut self.atlas, tick);
self.fish_small_model_cache.clean(&mut self.atlas, tick);
self.biped_large_model_cache.clean(&mut self.atlas, tick);
self.biped_small_model_cache.clean(&mut self.atlas, tick);
self.object_model_cache.clean(&mut self.atlas, tick);
self.item_drop_model_cache.clean(&mut self.atlas, tick);
self.ship_model_cache.clean(&mut self.atlas, tick);
self.golem_model_cache.clean(&mut self.atlas, tick);
self.volume_model_cache.clean(&mut self.atlas, tick);
self.arthropod_model_cache.clean(&mut self.atlas, tick);
}
pub fn update_lighting(&mut self, scene_data: &SceneData) {
@ -1092,7 +1091,7 @@ impl FigureMgr {
Body::Humanoid(body) => {
let (model, skeleton_attr) = self.model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -2212,7 +2211,7 @@ impl FigureMgr {
let (model, skeleton_attr) =
self.quadruped_small_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -2412,7 +2411,7 @@ impl FigureMgr {
let (model, skeleton_attr) =
self.quadruped_medium_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -2789,7 +2788,7 @@ impl FigureMgr {
let (model, skeleton_attr) =
self.quadruped_low_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -3185,7 +3184,7 @@ impl FigureMgr {
Body::BirdMedium(body) => {
let (model, skeleton_attr) = self.bird_medium_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -3515,7 +3514,7 @@ impl FigureMgr {
Body::FishMedium(body) => {
let (model, skeleton_attr) = self.fish_medium_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -3598,7 +3597,7 @@ impl FigureMgr {
Body::BipedSmall(body) => {
let (model, skeleton_attr) = self.biped_small_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -4159,7 +4158,7 @@ impl FigureMgr {
Body::Dragon(body) => {
let (model, skeleton_attr) = self.dragon_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -4246,7 +4245,7 @@ impl FigureMgr {
Body::Theropod(body) => {
let (model, skeleton_attr) = self.theropod_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -4425,7 +4424,7 @@ impl FigureMgr {
Body::Arthropod(body) => {
let (model, skeleton_attr) = self.arthropod_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -4717,7 +4716,7 @@ impl FigureMgr {
Body::BirdLarge(body) => {
let (model, skeleton_attr) = self.bird_large_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -5040,7 +5039,7 @@ impl FigureMgr {
Body::FishSmall(body) => {
let (model, skeleton_attr) = self.fish_small_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -5123,7 +5122,7 @@ impl FigureMgr {
Body::BipedLarge(body) => {
let (model, skeleton_attr) = self.biped_large_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -5828,7 +5827,7 @@ impl FigureMgr {
Body::Golem(body) => {
let (model, skeleton_attr) = self.golem_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -6069,7 +6068,7 @@ impl FigureMgr {
Body::Object(body) => {
let (model, skeleton_attr) = self.object_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -6191,7 +6190,7 @@ impl FigureMgr {
let item_key = item.map(ItemKey::from);
let (model, skeleton_attr) = self.item_drop_model_cache.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
inventory,
(),
@ -6255,7 +6254,7 @@ impl FigureMgr {
let (model, _skeleton_attr) =
self.volume_model_cache.get_or_create_terrain_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
vk,
Arc::clone(vol),
tick,
@ -6282,7 +6281,7 @@ impl FigureMgr {
} else if body.manifest_entry().is_some() {
self.ship_model_cache.get_or_create_terrain_model(
renderer,
&mut self.col_lights,
&mut self.atlas,
body,
(),
tick,
@ -6560,7 +6559,7 @@ impl FigureMgr {
// Don't render player
.filter(|(entity, _, _, _, _, _, _)| *entity != viewpoint_entity)
{
if let Some((bound, model, col_lights)) = self.get_model_for_render(
if let Some((bound, model, atlas)) = self.get_model_for_render(
tick,
camera,
character_state,
@ -6582,7 +6581,7 @@ impl FigureMgr {
None
},
) {
drawer.draw(model, bound, col_lights);
drawer.draw(model, bound, atlas);
}
}
}
@ -6616,7 +6615,7 @@ impl FigureMgr {
let inventory_storage = ecs.read_storage::<Inventory>();
let inventory = inventory_storage.get(viewpoint_entity);
if let Some((bound, model, col_lights)) = self.get_model_for_render(
if let Some((bound, model, atlas)) = self.get_model_for_render(
tick,
camera,
character_state,
@ -6635,10 +6634,10 @@ impl FigureMgr {
None
},
) {
drawer.draw(model, bound, col_lights);
drawer.draw(model, bound, atlas);
/*renderer.render_player_shadow(
model,
&col_lights,
&atlas,
global,
bone_consts,
lod,
@ -6677,7 +6676,7 @@ impl FigureMgr {
let character_state = if is_viewpoint { character_state } else { None };
let FigureMgr {
col_lights: ref col_lights_,
atlas: ref atlas_,
model_cache,
theropod_model_cache,
quadruped_small_model_cache,
@ -6718,7 +6717,7 @@ impl FigureMgr {
arthropod_states,
},
} = self;
let col_lights = col_lights_;
let atlas = atlas_;
if let Some((bound, model_entry)) = match body {
Body::Humanoid(body) => character_states
.get(&entity)
@ -6728,7 +6727,7 @@ impl FigureMgr {
state.bound(),
model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6747,7 +6746,7 @@ impl FigureMgr {
state.bound(),
quadruped_small_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6766,7 +6765,7 @@ impl FigureMgr {
state.bound(),
quadruped_medium_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6785,7 +6784,7 @@ impl FigureMgr {
state.bound(),
quadruped_low_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6804,7 +6803,7 @@ impl FigureMgr {
state.bound(),
bird_medium_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6823,7 +6822,7 @@ impl FigureMgr {
state.bound(),
fish_medium_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6842,7 +6841,7 @@ impl FigureMgr {
state.bound(),
theropod_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6861,7 +6860,7 @@ impl FigureMgr {
state.bound(),
dragon_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6880,7 +6879,7 @@ impl FigureMgr {
state.bound(),
bird_large_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6899,7 +6898,7 @@ impl FigureMgr {
state.bound(),
fish_small_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6918,7 +6917,7 @@ impl FigureMgr {
state.bound(),
biped_large_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6937,7 +6936,7 @@ impl FigureMgr {
state.bound(),
biped_small_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6956,7 +6955,7 @@ impl FigureMgr {
state.bound(),
golem_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6975,7 +6974,7 @@ impl FigureMgr {
state.bound(),
arthropod_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -6994,7 +6993,7 @@ impl FigureMgr {
state.bound(),
object_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -7013,7 +7012,7 @@ impl FigureMgr {
state.bound(),
item_drop_model_cache
.get_model(
col_lights,
atlas,
body,
inventory,
tick,
@ -7034,7 +7033,7 @@ impl FigureMgr {
state.bound(),
volume_model_cache
.get_model(
col_lights,
atlas,
VolumeKey { entity, mut_count },
None,
tick,
@ -7054,7 +7053,7 @@ impl FigureMgr {
state.bound(),
ship_model_cache
.get_model(
col_lights,
atlas,
body,
None,
tick,
@ -7097,7 +7096,7 @@ impl FigureMgr {
model_entry.lod_model(0)
};
Some((bound, model?, col_lights_.texture(model_entry)))
Some((bound, model?, atlas_.texture(model_entry)))
} else {
// trace!("Body has no saved figure");
None
@ -7252,16 +7251,16 @@ impl FigureMgr {
pub fn figure_count_visible(&self) -> usize { self.states.count_visible() }
}
pub struct FigureColLights {
pub struct FigureAtlas {
atlas: AtlasAllocator,
// col_lights: Texture<ColLightFmt>,
// atlas_texture: Texture<ColLightFmt>,
}
impl FigureColLights {
impl FigureAtlas {
pub fn new(renderer: &mut Renderer) -> Self {
let atlas = Self::make_atlas(renderer).expect("Failed to create texture atlas for figures");
Self {
atlas, /* col_lights, */
atlas, /* atlas_texture, */
}
}
@ -7269,9 +7268,9 @@ impl FigureColLights {
pub fn texture<'a, const N: usize>(
&'a self,
model: ModelEntryRef<'a, N>,
) -> &'a ColLights<pipelines::figure::Locals> {
/* &self.col_lights */
model.col_lights()
) -> &'a AtlasTextures<pipelines::figure::Locals, FigureSpriteAtlasData> {
/* &self.atlas_texture */
model.atlas_textures()
}
/// NOTE: Panics if the opaque model's length does not fit in a u32.
@ -7285,17 +7284,21 @@ impl FigureColLights {
pub fn create_figure<const N: usize>(
&mut self,
renderer: &mut Renderer,
(tex, tex_size): ColLightInfo,
atlas_texture_data: FigureSpriteAtlasData,
atlas_size: Vec2<u16>,
(opaque, bounds): (Mesh<TerrainVertex>, math::Aabb<f32>),
vertex_ranges: [Range<u32>; N],
) -> FigureModelEntry<N> {
span!(_guard, "create_figure", "FigureColLights::create_figure");
let atlas = &mut self.atlas;
let allocation = atlas
.allocate(guillotiere::Size::new(tex_size.x as i32, tex_size.y as i32))
.allocate(guillotiere::Size::new(
atlas_size.x as i32,
atlas_size.y as i32,
))
.expect("Not yet implemented: allocate new atlas on allocation failure.");
let col_lights = pipelines::shadow::create_col_lights(renderer, &(tex, tex_size));
let col_lights = renderer.figure_bind_col_light(col_lights);
let [atlas_textures] = atlas_texture_data.create_textures(renderer, atlas_size);
let atlas_textures = renderer.figure_bind_atlas_textures(atlas_textures);
let model_len = u32::try_from(opaque.vertices().len())
.expect("The model size for this figure does not fit in a u32!");
let model = renderer.create_model(&opaque);
@ -7313,7 +7316,7 @@ impl FigureColLights {
FigureModelEntry {
_bounds: bounds,
allocation,
col_lights,
atlas_textures,
lod_vertex_ranges: vertex_ranges,
model: FigureModel { opaque: model },
}
@ -7330,7 +7333,9 @@ impl FigureColLights {
pub fn create_terrain<const N: usize>(
&mut self,
renderer: &mut Renderer,
(tex, tex_size): ColLightInfo,
// TODO: Use `TerrainAtlasData`
atlas_texture_data: FigureSpriteAtlasData,
atlas_size: Vec2<u16>,
(opaque, bounds): (Mesh<TerrainVertex>, math::Aabb<f32>),
vertex_ranges: [Range<u32>; N],
sprite_instances: [Vec<SpriteInstance>; SPRITE_LOD_LEVELS],
@ -7340,10 +7345,14 @@ impl FigureColLights {
span!(_guard, "create_figure", "FigureColLights::create_figure");
let atlas = &mut self.atlas;
let allocation = atlas
.allocate(guillotiere::Size::new(tex_size.x as i32, tex_size.y as i32))
.allocate(guillotiere::Size::new(
atlas_size.x as i32,
atlas_size.y as i32,
))
.expect("Not yet implemented: allocate new atlas on allocation failure.");
let col_lights = pipelines::shadow::create_col_lights(renderer, &(tex, tex_size));
let col_lights = renderer.figure_bind_col_light(col_lights);
let [col_lights] = atlas_texture_data.create_textures(renderer, atlas_size);
// TODO: Use `kinds` texture for volume entities
let atlas_textures = renderer.figure_bind_atlas_textures(col_lights);
let model_len = u32::try_from(opaque.vertices().len())
.expect("The model size for this figure does not fit in a u32!");
let model = renderer.create_model(&opaque);
@ -7364,7 +7373,7 @@ impl FigureColLights {
TerrainModelEntry {
_bounds: bounds,
allocation,
col_lights,
atlas_textures,
lod_vertex_ranges: vertex_ranges,
model: FigureModel { opaque: model },
sprite_instances,

View File

@ -1403,7 +1403,7 @@ impl Scene {
// Draws sprites
let mut sprite_drawer = first_pass.draw_sprites(
&self.terrain.sprite_globals,
&self.terrain.sprite_col_lights,
&self.terrain.sprite_atlas_textures,
);
self.figure_mgr.render_sprites(
&mut sprite_drawer,

View File

@ -1,14 +1,15 @@
use crate::{
mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_figure},
render::{
create_skybox_mesh, BoneMeshes, Consts, FigureModel, FirstPassDrawer, GlobalModel, Globals,
GlobalsBindGroup, Light, LodData, Mesh, Model, PointLightMatrix, RainOcclusionLocals,
Renderer, Shadow, ShadowLocals, SkyboxVertex, TerrainVertex,
create_skybox_mesh, pipelines::FigureSpriteAtlasData, BoneMeshes, Consts, FigureModel,
FirstPassDrawer, GlobalModel, Globals, GlobalsBindGroup, Light, LodData, Mesh, Model,
PointLightMatrix, RainOcclusionLocals, Renderer, Shadow, ShadowLocals, SkyboxVertex,
TerrainVertex,
},
scene::{
camera::{self, Camera, CameraMode},
figure::{
load_mesh, FigureColLights, FigureModelCache, FigureModelEntry, FigureState,
load_mesh, FigureAtlas, FigureModelCache, FigureModelEntry, FigureState,
FigureUpdateCommonParameters,
},
},
@ -46,7 +47,7 @@ impl ReadVol for VoidVol {
}
fn generate_mesh(
greedy: &mut GreedyMesh<'_>,
greedy: &mut GreedyMesh<'_, FigureSpriteAtlasData>,
mesh: &mut Mesh<TerrainVertex>,
segment: Segment,
offset: Vec3<f32>,
@ -70,7 +71,7 @@ pub struct Scene {
lod: LodData,
map_bounds: Vec2<f32>,
col_lights: FigureColLights,
figure_atlas: FigureAtlas,
backdrop: Option<(FigureModelEntry<1>, FigureState<FixtureSkeleton>)>,
figure_model_cache: FigureModelCache,
figure_state: Option<FigureState<CharacterSkeleton>>,
@ -108,7 +109,7 @@ impl Scene {
camera.set_distance(3.4);
camera.set_orientation(Vec3::new(start_angle, 0.0, 0.0));
let mut col_lights = FigureColLights::new(renderer);
let mut figure_atlas = FigureAtlas::new(renderer);
let data = GlobalModel {
globals: renderer.create_consts(&[Globals::default()]),
@ -147,9 +148,14 @@ impl Scene {
// total size is bounded by 2^24 * 3 * 1.5 which is bounded by
// 2^27, which fits in a u32.
let range = 0..opaque_mesh.vertices().len() as u32;
let model =
col_lights
.create_figure(renderer, greedy.finalize(), (opaque_mesh, bounds), [range]);
let (atlas_texture_data, atlas_size) = greedy.finalize();
let model = figure_atlas.create_figure(
renderer,
atlas_texture_data,
atlas_size,
(opaque_mesh, bounds),
[range],
);
let mut buf = [Default::default(); anim::MAX_BONE_COUNT];
let common_params = FigureUpdateCommonParameters {
entity: None,
@ -182,7 +188,7 @@ impl Scene {
);
(model, state)
}),
col_lights,
figure_atlas,
camera,
@ -283,7 +289,7 @@ impl Scene {
)]);
self.figure_model_cache
.clean(&mut self.col_lights, scene_data.tick);
.clean(&mut self.figure_atlas, scene_data.tick);
let item_info = |equip_slot| {
inventory
@ -327,7 +333,7 @@ impl Scene {
.figure_model_cache
.get_or_create_model(
renderer,
&mut self.col_lights,
&mut self.figure_atlas,
body,
inventory,
(),
@ -376,7 +382,7 @@ impl Scene {
let mut figure_drawer = drawer.draw_figures();
if let Some(body) = body {
let model = &self.figure_model_cache.get_model(
&self.col_lights,
&self.figure_atlas,
body,
inventory,
tick,
@ -390,7 +396,7 @@ impl Scene {
figure_drawer.draw(
lod,
figure_state.bound(),
self.col_lights.texture(ModelEntryRef::Figure(model)),
self.figure_atlas.texture(ModelEntryRef::Figure(model)),
);
}
}
@ -401,7 +407,7 @@ impl Scene {
figure_drawer.draw(
lod,
state.bound(),
self.col_lights.texture(ModelEntryRef::Figure(model)),
self.figure_atlas.texture(ModelEntryRef::Figure(model)),
);
}
}

View File

@ -9,11 +9,11 @@ use crate::{
terrain::{generate_mesh, SUNLIGHT, SUNLIGHT_INV},
},
render::{
pipelines::{self, ColLights},
AltIndices, ColLightInfo, CullingMode, FirstPassDrawer, FluidVertex, GlobalModel,
pipelines::{self, AtlasData, AtlasTextures},
AltIndices, CullingMode, FigureSpriteAtlasData, FirstPassDrawer, FluidVertex, GlobalModel,
Instances, LodData, Mesh, Model, RenderError, Renderer, SpriteDrawer,
SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex, SpriteVerts, TerrainLocals,
TerrainShadowDrawer, TerrainVertex, SPRITE_VERT_PAGE_SIZE,
SpriteGlobalsBindGroup, SpriteInstance, SpriteVertex, SpriteVerts, TerrainAtlasData,
TerrainLocals, TerrainShadowDrawer, TerrainVertex, SPRITE_VERT_PAGE_SIZE,
},
};
@ -80,14 +80,14 @@ pub struct TerrainChunkData {
fluid_model: Option<Model<FluidVertex>>,
/// 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_alloc: Option<guillotiere::AllocId>,
atlas_alloc: 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).
col_lights: Arc<ColLights<pipelines::terrain::Locals>>,
atlas_textures: Arc<AtlasTextures<pipelines::terrain::Locals, TerrainAtlasData>>,
light_map: LightMapFn,
glow_map: LightMapFn,
sprite_instances: [(Instances<SpriteInstance>, AltIndices); SPRITE_LOD_LEVELS],
@ -133,7 +133,8 @@ pub struct MeshWorkerResponseMesh {
sun_occluder_z_bounds: (f32, f32),
opaque_mesh: Mesh<TerrainVertex>,
fluid_mesh: Mesh<FluidVertex>,
col_lights_info: ColLightInfo,
atlas_texture_data: TerrainAtlasData,
atlas_size: Vec2<u16>,
light_map: LightMapFn,
glow_map: LightMapFn,
alt_indices: AltIndices,
@ -343,7 +344,15 @@ fn mesh_worker(
opaque_mesh,
fluid_mesh,
_shadow_mesh,
(bounds, col_lights_info, light_map, glow_map, alt_indices, sun_occluder_z_bounds),
(
bounds,
atlas_texture_data,
atlas_size,
light_map,
glow_map,
alt_indices,
sun_occluder_z_bounds,
),
) = generate_mesh(
&volume,
(
@ -358,7 +367,8 @@ fn mesh_worker(
sun_occluder_z_bounds,
opaque_mesh,
fluid_mesh,
col_lights_info,
atlas_texture_data,
atlas_size,
light_map,
glow_map,
alt_indices,
@ -496,12 +506,12 @@ pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
// Maps sprite kind + variant to data detailing how to render it
pub sprite_data: Arc<HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>>,
pub sprite_globals: SpriteGlobalsBindGroup,
pub sprite_col_lights: Arc<ColLights<pipelines::sprite::Locals>>,
pub sprite_atlas_textures: Arc<AtlasTextures<pipelines::sprite::Locals, FigureSpriteAtlasData>>,
/// 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: Arc<ColLights<pipelines::terrain::Locals>>,
atlas_textures: Arc<AtlasTextures<pipelines::terrain::Locals, TerrainAtlasData>>,
phantom: PhantomData<V>,
}
@ -515,7 +525,7 @@ pub struct SpriteRenderContext {
sprite_config: Arc<SpriteSpec>,
// Maps sprite kind + variant to data detailing how to render it
sprite_data: Arc<HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>>,
sprite_col_lights: Arc<ColLights<pipelines::sprite::Locals>>,
sprite_atlas_textures: Arc<AtlasTextures<pipelines::sprite::Locals, FigureSpriteAtlasData>>,
sprite_verts_buffer: Arc<SpriteVerts>,
}
@ -528,7 +538,8 @@ impl SpriteRenderContext {
struct SpriteWorkerResponse {
sprite_config: Arc<SpriteSpec>,
sprite_data: HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>,
sprite_col_lights: ColLightInfo,
sprite_atlas_texture_data: FigureSpriteAtlasData,
sprite_atlas_size: Vec2<u16>,
sprite_mesh: Mesh<SpriteVertex>,
}
@ -539,7 +550,7 @@ impl SpriteRenderContext {
Arc::<SpriteSpec>::load_expect("voxygen.voxel.sprite_manifest").cloned();
let max_size = Vec2::from(u16::try_from(max_texture_size).unwrap_or(u16::MAX));
let mut greedy = GreedyMesh::<SpriteAtlasAllocator>::new(
let mut greedy = GreedyMesh::<FigureSpriteAtlasData, SpriteAtlasAllocator>::new(
max_size,
crate::mesh::greedy::sprite_config(),
);
@ -584,7 +595,10 @@ impl SpriteRenderContext {
scale
}
});
move |greedy: &mut GreedyMesh<SpriteAtlasAllocator>,
move |greedy: &mut GreedyMesh<
FigureSpriteAtlasData,
SpriteAtlasAllocator,
>,
sprite_mesh: &mut Mesh<SpriteVertex>| {
prof_span!("mesh sprite");
let lod_sprite_data = scaled.map(|lod_scale_orig| {
@ -635,7 +649,7 @@ impl SpriteRenderContext {
.map(|f| f(&mut greedy, &mut sprite_mesh))
.collect();
let sprite_col_lights = {
let (sprite_atlas_texture_data, sprite_atlas_size) = {
prof_span!("finalize");
greedy.finalize()
};
@ -643,7 +657,8 @@ impl SpriteRenderContext {
SpriteWorkerResponse {
sprite_config,
sprite_data,
sprite_col_lights,
sprite_atlas_texture_data,
sprite_atlas_size,
sprite_mesh,
}
});
@ -658,7 +673,8 @@ impl SpriteRenderContext {
let SpriteWorkerResponse {
sprite_config,
sprite_data,
sprite_col_lights,
sprite_atlas_texture_data,
sprite_atlas_size,
sprite_mesh,
} = join_handle
.take()
@ -669,9 +685,9 @@ impl SpriteRenderContext {
.join()
.unwrap();
let sprite_col_lights =
pipelines::shadow::create_col_lights(renderer, &sprite_col_lights);
let sprite_col_lights = renderer.sprite_bind_col_light(sprite_col_lights);
let [sprite_col_lights] =
sprite_atlas_texture_data.create_textures(renderer, sprite_atlas_size);
let sprite_atlas_textures = renderer.sprite_bind_atlas_textures(sprite_col_lights);
// Write sprite model to a 1D texture
let sprite_verts_buffer = renderer.create_sprite_verts(sprite_mesh);
@ -680,7 +696,7 @@ impl SpriteRenderContext {
// TODO: these are all Arcs, would it makes sense to factor out the Arc?
sprite_config: Arc::clone(&sprite_config),
sprite_data: Arc::new(sprite_data),
sprite_col_lights: Arc::new(sprite_col_lights),
sprite_atlas_textures: Arc::new(sprite_atlas_textures),
sprite_verts_buffer: Arc::new(sprite_verts_buffer),
}
};
@ -699,7 +715,7 @@ impl<V: RectRasterableVol> Terrain<V> {
// with worker threads that are meshing chunks.
let (send, recv) = channel::unbounded();
let (atlas, col_lights) =
let (atlas, atlas_textures) =
Self::make_atlas(renderer).expect("Failed to create atlas texture");
Self {
@ -713,20 +729,26 @@ impl<V: RectRasterableVol> Terrain<V> {
mesh_todos_active: Arc::new(AtomicU64::new(0)),
mesh_recv_overflow: 0.0,
sprite_data: sprite_render_context.sprite_data,
sprite_col_lights: sprite_render_context.sprite_col_lights,
sprite_atlas_textures: sprite_render_context.sprite_atlas_textures,
sprite_globals: renderer.bind_sprite_globals(
global_model,
lod_data,
&sprite_render_context.sprite_verts_buffer,
),
col_lights: Arc::new(col_lights),
atlas_textures: Arc::new(atlas_textures),
phantom: PhantomData,
}
}
fn make_atlas(
renderer: &mut Renderer,
) -> Result<(AtlasAllocator, ColLights<pipelines::terrain::Locals>), RenderError> {
) -> Result<
(
AtlasAllocator,
AtlasTextures<pipelines::terrain::Locals, TerrainAtlasData>,
),
RenderError,
> {
span!(_guard, "make_atlas", "Terrain::make_atlas");
let max_texture_size = renderer.max_texture_size();
let atlas_size = guillotiere::Size::new(max_texture_size as i32, max_texture_size as i32);
@ -736,9 +758,14 @@ impl<V: RectRasterableVol> Terrain<V> {
large_size_threshold: 1024,
..guillotiere::AllocatorOptions::default()
});
let texture = renderer.create_texture_raw(
let [col_lights, kinds] = [
wgpu::TextureFormat::Rgba8Unorm,
wgpu::TextureFormat::R8Unorm,
]
.map(|fmt| {
renderer.create_texture_raw(
&wgpu::TextureDescriptor {
label: Some("Atlas texture"),
label: Some("Color & lights atlas texture"),
size: wgpu::Extent3d {
width: max_texture_size,
height: max_texture_size,
@ -747,12 +774,12 @@ impl<V: RectRasterableVol> Terrain<V> {
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
format: fmt,
usage: wgpu::TextureUsage::COPY_DST | wgpu::TextureUsage::SAMPLED,
},
&wgpu::TextureViewDescriptor {
label: Some("Atlas texture view"),
format: Some(wgpu::TextureFormat::Rgba8Unorm),
label: Some("Color & lights atlas texture view"),
format: Some(fmt),
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
@ -761,7 +788,7 @@ impl<V: RectRasterableVol> Terrain<V> {
array_layer_count: None,
},
&wgpu::SamplerDescriptor {
label: Some("Atlas sampler"),
label: Some("Color & lights atlas texture sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
@ -770,16 +797,17 @@ impl<V: RectRasterableVol> Terrain<V> {
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
},
);
let col_light = renderer.terrain_bind_col_light(texture);
Ok((atlas, col_light))
)
});
let textures = renderer.terrain_bind_atlas_textures(col_lights, kinds);
Ok((atlas, textures))
}
fn remove_chunk_meta(&mut self, _pos: Vec2<i32>, chunk: &TerrainChunkData) {
// 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_alloc {
self.atlas.deallocate(col_lights);
if let Some(atlas_alloc) = chunk.atlas_alloc {
self.atlas.deallocate(atlas_alloc);
}
/* let (zmin, zmax) = chunk.z_bounds;
self.z_index_up.remove(Vec3::from(zmin, pos.x, pos.y));
@ -1250,16 +1278,17 @@ impl<V: RectRasterableVol> Terrain<V> {
.map(|chunk| chunk.load_time)
.unwrap_or(current_time as f32);
// TODO: Allocate new atlas on allocation failure.
let (tex, tex_size) = mesh.col_lights_info;
let atlas = &mut self.atlas;
let chunks = &mut self.chunks;
let col_lights = &mut self.col_lights;
let alloc_size =
guillotiere::Size::new(i32::from(tex_size.x), i32::from(tex_size.y));
let atlas_textures = &mut self.atlas_textures;
let alloc_size = guillotiere::Size::new(
i32::from(mesh.atlas_size.x),
i32::from(mesh.atlas_size.y),
);
let allocation = atlas.allocate(alloc_size).unwrap_or_else(|| {
// Atlas allocation failure: try allocating a new texture and atlas.
let (new_atlas, new_col_lights) =
let (new_atlas, new_atlas_textures) =
Self::make_atlas(renderer).expect("Failed to create atlas texture");
// We reset the atlas and clear allocations from existing chunks,
@ -1271,10 +1300,10 @@ impl<V: RectRasterableVol> Terrain<V> {
// TODO: Consider attempting defragmentation first rather than just
// always moving everything into the new chunk.
chunks.iter_mut().for_each(|(_, chunk)| {
chunk.col_lights_alloc = None;
chunk.atlas_alloc = None;
});
*atlas = new_atlas;
*col_lights = Arc::new(new_col_lights);
*atlas_textures = Arc::new(new_atlas_textures);
atlas
.allocate(alloc_size)
@ -1286,19 +1315,27 @@ impl<V: RectRasterableVol> Terrain<V> {
allocation.rectangle.min.x as u32,
allocation.rectangle.min.y as u32,
);
// Update col_lights texture
renderer.update_texture(
&col_lights.texture,
&atlas_textures.textures[0],
atlas_offs.into_array(),
tex_size.map(u32::from).into_array(),
&tex,
mesh.atlas_size.as_().into_array(),
&mesh.atlas_texture_data.col_lights,
);
// Update kinds texture
renderer.update_texture(
&atlas_textures.textures[1],
atlas_offs.into_array(),
mesh.atlas_size.as_().into_array(),
&mesh.atlas_texture_data.kinds,
);
self.insert_chunk(response.pos, TerrainChunkData {
load_time,
opaque_model: renderer.create_model(&mesh.opaque_mesh),
fluid_model: renderer.create_model(&mesh.fluid_mesh),
col_lights_alloc: Some(allocation.id),
col_lights: Arc::clone(&self.col_lights),
atlas_alloc: Some(allocation.id),
atlas_textures: Arc::clone(&self.atlas_textures),
light_map: mesh.light_map,
glow_map: mesh.glow_map,
sprite_instances,
@ -1705,19 +1742,19 @@ impl<V: RectRasterableVol> Terrain<V> {
Some((
rpos,
chunk.opaque_model.as_ref()?,
&chunk.col_lights,
&chunk.atlas_textures,
&chunk.locals,
&chunk.alt_indices,
))
})
.for_each(|(rpos, model, col_lights, locals, alt_indices)| {
.for_each(|(rpos, model, atlas_textures, locals, alt_indices)| {
// Always draw all of close chunks to avoid terrain 'popping'
let culling_mode = if rpos.magnitude_squared() < NEVER_CULL_DIST.pow(2) {
CullingMode::None
} else {
culling_mode
};
drawer.draw(model, col_lights, locals, alt_indices, culling_mode)
drawer.draw(model, atlas_textures, locals, alt_indices, culling_mode)
});
}