diff --git a/assets/voxygen/shaders/sprite-frag.glsl b/assets/voxygen/shaders/sprite-frag.glsl index 91f50145c9..5be535c6a2 100644 --- a/assets/voxygen/shaders/sprite-frag.glsl +++ b/assets/voxygen/shaders/sprite-frag.glsl @@ -22,9 +22,9 @@ layout(location = 2) flat in float f_light; layout(location = 3) in vec2 f_uv_pos; layout(location = 4) in vec2 f_inst_light; -layout(set = 4, binding = 0) +layout(set = 3, binding = 0) uniform texture2D t_col_light; -layout(set = 4, binding = 1) +layout(set = 3, binding = 1) uniform sampler s_col_light; layout(location = 0) out vec4 tgt_color; diff --git a/assets/voxygen/shaders/sprite-vert.glsl b/assets/voxygen/shaders/sprite-vert.glsl index fb6de67eba..a0a2551335 100644 --- a/assets/voxygen/shaders/sprite-vert.glsl +++ b/assets/voxygen/shaders/sprite-vert.glsl @@ -16,29 +16,22 @@ #include #include -layout(location = 0) in vec3 v_pos; -layout(location = 1) in uint v_atlas_pos; -layout(location = 2) in uint v_norm_ao; -layout(location = 3) in uint inst_pos_ori; -layout(location = 4) in vec4 inst_mat0; -layout(location = 5) in vec4 inst_mat1; -layout(location = 6) in vec4 inst_mat2; -layout(location = 7) in vec4 inst_mat3; -layout(location = 8) in vec4 inst_light; -layout(location = 9) in float inst_wind_sway; +layout(location = 0) in vec4 inst_mat0; +layout(location = 1) in vec4 inst_mat1; +layout(location = 2) in vec4 inst_mat2; +layout(location = 3) in vec4 inst_mat3; +// TODO: is there a better way to pack the various vertex attributes? +// TODO: ori is unused +layout(location = 4) in uint inst_pos_ori; +layout(location = 5) in uint inst_vert_page; // NOTE: this could fit in less bits +// TODO: do we need this many bits for light and glow? +layout(location = 6) in float inst_light; +layout(location = 7) in float inst_glow; +layout(location = 8) in float model_wind_sway; // NOTE: this only varies per model +layout(location = 9) in float model_z_scale; // NOTE: this only varies per model -struct SpriteLocals { - mat4 mat; - vec4 wind_sway; - vec4 offs; -}; - -layout(std140, set = 3, binding = 0) -uniform u_locals { - mat4 mat; - vec4 wind_sway; - vec4 offs; -}; +layout(set = 0, binding = 12) uniform utexture2D t_sprite_verts; +layout(set = 0, binding = 13) uniform sampler s_sprite_verts; layout (std140, set = 2, binding = 0) uniform u_terrain_locals { @@ -47,6 +40,7 @@ uniform u_terrain_locals { ivec4 atlas_offs; }; +// TODO: consider grouping into vec4's layout(location = 0) out vec3 f_pos; layout(location = 1) flat out vec3 f_norm; layout(location = 2) flat out float f_light; @@ -57,38 +51,63 @@ const float SCALE = 1.0 / 11.0; const float SCALE_FACTOR = pow(SCALE, 1.3) * 0.2; const int EXTRA_NEG_Z = 32768; +const int VERT_EXTRA_NEG_Z = 128; +const int VERT_PAGE_SIZE = 256; void main() { + // Matrix to transform this sprite instance from model space to chunk space mat4 inst_mat; inst_mat[0] = inst_mat0; inst_mat[1] = inst_mat1; inst_mat[2] = inst_mat2; inst_mat[3] = inst_mat3; - vec3 inst_offs = model_offs - focus_off.xyz; - f_inst_light = inst_light.xy; + // Worldpos of the chunk that this sprite is in + vec3 chunk_offs = model_offs - focus_off.xyz; - vec3 v_pos_ = wind_sway.xyz * v_pos; + f_inst_light = vec2(inst_light, inst_glow); - f_pos = (inst_mat * vec4(v_pos_, 1.0)).xyz * SCALE + inst_offs; + // Index of the vertex data in the 1D vertex texture + int vertex_index = int(gl_VertexIndex % VERT_PAGE_SIZE + inst_vert_page); + const int WIDTH = 16384; // TODO: temp + ivec2 tex_coords = ivec2(vertex_index % WIDTH, vertex_index / WIDTH); + uvec2 pos_atlas_pos_norm_ao = texelFetch(usampler2D(t_sprite_verts, s_sprite_verts), tex_coords, 0).xy; + uint v_pos_norm = pos_atlas_pos_norm_ao.x; + uint v_atlas_pos = pos_atlas_pos_norm_ao.y; + + // Expand the model vertex position bits into float values + vec3 v_pos = vec3(ivec3((uvec3(v_pos_norm) >> uvec3(0, 8, 16)) & uvec3(0xFu, 0xFu, 0x0FFFu)) - ivec3(0, 0, VERT_EXTRA_NEG_Z)); + + // Transform into chunk space and scale + f_pos = (inst_mat * vec4(v_pos, 1.0)).xyz; + // Transform info world space + f_pos += chunk_offs; // Terrain 'pop-in' effect f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0)); - f_pos += wind_sway.w * vec3( + // Wind sway effect + f_pos += model_wind_sway * vec3( sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35), sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25), 0.0 - //) * pow(abs(v_pos_.z), 1.3) * SCALE_FACTOR; - ) * v_pos_.z * SCALE_FACTOR; + // NOTE: could potentially replace `v_pos.z * model_z_scale` with a calculation using `inst_chunk_pos` from below + //) * pow(abs(v_pos.z * model_z_scale), 1.3) * SCALE_FACTOR; + ) * v_pos.z * model_z_scale * SCALE_FACTOR; - vec3 norm = (inst_mat[(v_norm_ao >> 1u) & 3u].xyz); - f_norm = mix(-norm, norm, v_norm_ao & 1u); + // Determine normal + vec3 norm = (inst_mat[(v_pos_norm >> 30u) & 3u].xyz); + f_norm = mix(-norm, norm, v_pos_norm >> 29u & 1u); - f_uv_pos = vec2((uvec2(v_atlas_pos) >> uvec2(0, 16)) & uvec2(0xFFFFu, 0xFFFFu));/* + 0.5*/; + // Expand atlas tex coords to floats + // NOTE: Could defer to fragment shader if we are vert heavy + f_uv_pos = vec2((uvec2(v_atlas_pos) >> uvec2(0, 16)) & uvec2(0xFFFFu, 0xFFFFu));; + // Position of the sprite block in the chunk + // Used solely for highlighting the selected sprite + vec3 inst_chunk_pos = vec3(ivec3((uvec3(inst_pos_ori) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0xFFFFu)) - ivec3(0, 0, EXTRA_NEG_Z)); // Select glowing - vec3 sprite_pos = floor(((inst_mat * vec4(-offs.xyz, 1)).xyz) * SCALE) + inst_offs; + vec3 sprite_pos = inst_chunk_pos + chunk_offs; f_light = (select_pos.w > 0 && select_pos.xyz == sprite_pos) ? 5.0 : 1.0; gl_Position = diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index 65c8d43178..db537c8802 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -28,6 +28,9 @@ impl Mesh { /// Get a mutable slice referencing the vertices of this mesh. pub fn vertices_mut(&mut self) -> &mut [V] { &mut self.verts } + /// Get a mutable vec referencing the vertices of this mesh. + pub fn vertices_mut_vec(&mut self) -> &mut Vec { &mut self.verts } + /// Push a new vertex onto the end of this mesh. pub fn push(&mut self, vert: V) { self.verts.push(vert); } diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 358fd90e50..d7c0f9edf9 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -30,7 +30,11 @@ pub use self::{ postprocess::Locals as PostProcessLocals, shadow::{Locals as ShadowLocals, PointLightMatrix}, skybox::{create_mesh as create_skybox_mesh, Vertex as SkyboxVertex}, - sprite::{Instance as SpriteInstance, Locals as SpriteLocals, Vertex as SpriteVertex}, + sprite::{ + create_verts_texture as create_sprite_verts_texture, Instance as SpriteInstance, + SpriteGlobalsBindGroup, Vertex as SpriteVertex, + VERT_PAGE_SIZE as SPRITE_VERT_PAGE_SIZE, + }, terrain::{Locals as TerrainLocals, TerrainLayout, Vertex as TerrainVertex}, ui::{ create_quad as create_ui_quad, @@ -42,9 +46,9 @@ pub use self::{ }, renderer::{ drawer::{ - ChunkSpriteDrawer, Drawer, FigureDrawer, FigureShadowDrawer, FirstPassDrawer, - ParticleDrawer, PreparedUiDrawer, SecondPassDrawer, ShadowPassDrawer, SpriteDrawer, - TerrainDrawer, TerrainShadowDrawer, ThirdPassDrawer, UiDrawer, + Drawer, FigureDrawer, FigureShadowDrawer, FirstPassDrawer, ParticleDrawer, + PreparedUiDrawer, SecondPassDrawer, ShadowPassDrawer, SpriteDrawer, TerrainDrawer, + TerrainShadowDrawer, ThirdPassDrawer, UiDrawer, }, ColLightInfo, Renderer, }, diff --git a/voxygen/src/render/pipelines/lod_terrain.rs b/voxygen/src/render/pipelines/lod_terrain.rs index a1c0b346f7..f15e321801 100644 --- a/voxygen/src/render/pipelines/lod_terrain.rs +++ b/voxygen/src/render/pipelines/lod_terrain.rs @@ -104,7 +104,7 @@ impl LodData { array_layer_count: None, }; - renderer.create_texture_with_data_raw( + renderer.create_texture_with_data_raw::<4>( &texture_info, &view_info, &sampler_info, diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index c6ee2552a7..152bd417e2 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -253,142 +253,146 @@ pub struct GlobalsLayouts { } pub struct ColLights { - pub bind_group: wgpu::BindGroup, + pub(super) bind_group: wgpu::BindGroup, pub texture: Texture, phantom: std::marker::PhantomData, } impl GlobalsLayouts { + pub fn base_globals_layout() -> Vec { + vec![ + // Global uniform + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // Noise tex + wgpu::BindGroupLayoutEntry { + binding: 1, + 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: 2, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // Light uniform + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // Shadow uniform + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // Alt texture + wgpu::BindGroupLayoutEntry { + binding: 5, + 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: 6, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // Horizon texture + wgpu::BindGroupLayoutEntry { + binding: 7, + 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: 8, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + // light shadows (ie shadows from a light?) + wgpu::BindGroupLayoutEntry { + binding: 9, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + // TODO: is this relevant? + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // lod map (t_map) + wgpu::BindGroupLayoutEntry { + binding: 10, + 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: 11, + visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler { + filtering: true, + comparison: false, + }, + count: None, + }, + ] + } + pub fn new(device: &wgpu::Device) -> Self { let globals = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Globals layout"), - entries: &[ - // Global uniform - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // Noise tex - wgpu::BindGroupLayoutEntry { - binding: 1, - 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: 2, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Sampler { - filtering: true, - comparison: false, - }, - count: None, - }, - // Light uniform - wgpu::BindGroupLayoutEntry { - binding: 3, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // Shadow uniform - wgpu::BindGroupLayoutEntry { - binding: 4, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // Alt texture - wgpu::BindGroupLayoutEntry { - binding: 5, - 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: 6, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Sampler { - filtering: true, - comparison: false, - }, - count: None, - }, - // Horizon texture - wgpu::BindGroupLayoutEntry { - binding: 7, - 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: 8, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Sampler { - filtering: true, - comparison: false, - }, - count: None, - }, - // light shadows (ie shadows from a light?) - wgpu::BindGroupLayoutEntry { - binding: 9, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - // TODO: is this relevant? - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // lod map (t_map) - wgpu::BindGroupLayoutEntry { - binding: 10, - 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: 11, - visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT, - ty: wgpu::BindingType::Sampler { - filtering: true, - comparison: false, - }, - count: None, - }, - ], + entries: &Self::base_globals_layout(), }); let col_light = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -470,6 +474,72 @@ impl GlobalsLayouts { } } + // Note: this allocation serves the purpose of not having to duplicate code + pub fn bind_base_globals<'a>( + global_model: &'a GlobalModel, + lod_data: &'a lod_terrain::LodData, + noise: &'a Texture, + ) -> Vec> { + vec![ + // Global uniform + wgpu::BindGroupEntry { + binding: 0, + resource: global_model.globals.buf().as_entire_binding(), + }, + // Noise tex + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&noise.view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&noise.sampler), + }, + // Light uniform + wgpu::BindGroupEntry { + binding: 3, + resource: global_model.lights.buf().as_entire_binding(), + }, + // Shadow uniform + wgpu::BindGroupEntry { + binding: 4, + resource: global_model.shadows.buf().as_entire_binding(), + }, + // Alt texture + wgpu::BindGroupEntry { + binding: 5, + resource: wgpu::BindingResource::TextureView(&lod_data.alt.view), + }, + wgpu::BindGroupEntry { + binding: 6, + resource: wgpu::BindingResource::Sampler(&lod_data.alt.sampler), + }, + // Horizon texture + wgpu::BindGroupEntry { + binding: 7, + resource: wgpu::BindingResource::TextureView(&lod_data.horizon.view), + }, + wgpu::BindGroupEntry { + binding: 8, + resource: wgpu::BindingResource::Sampler(&lod_data.horizon.sampler), + }, + // light shadows + wgpu::BindGroupEntry { + binding: 9, + resource: global_model.shadow_mats.buf().as_entire_binding(), + }, + // lod map (t_map) + wgpu::BindGroupEntry { + binding: 10, + resource: wgpu::BindingResource::TextureView(&lod_data.map.view), + }, + wgpu::BindGroupEntry { + binding: 11, + resource: wgpu::BindingResource::Sampler(&lod_data.map.sampler), + }, + ] + } + pub fn bind( &self, device: &wgpu::Device, @@ -480,64 +550,7 @@ impl GlobalsLayouts { let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &self.globals, - entries: &[ - // Global uniform - wgpu::BindGroupEntry { - binding: 0, - resource: global_model.globals.buf().as_entire_binding(), - }, - // Noise tex - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&noise.view), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&noise.sampler), - }, - // Light uniform - wgpu::BindGroupEntry { - binding: 3, - resource: global_model.lights.buf().as_entire_binding(), - }, - // Shadow uniform - wgpu::BindGroupEntry { - binding: 4, - resource: global_model.shadows.buf().as_entire_binding(), - }, - // Alt texture - wgpu::BindGroupEntry { - binding: 5, - resource: wgpu::BindingResource::TextureView(&lod_data.alt.view), - }, - wgpu::BindGroupEntry { - binding: 6, - resource: wgpu::BindingResource::Sampler(&lod_data.alt.sampler), - }, - // Horizon texture - wgpu::BindGroupEntry { - binding: 7, - resource: wgpu::BindingResource::TextureView(&lod_data.horizon.view), - }, - wgpu::BindGroupEntry { - binding: 8, - resource: wgpu::BindingResource::Sampler(&lod_data.horizon.sampler), - }, - // light shadows - wgpu::BindGroupEntry { - binding: 9, - resource: global_model.shadow_mats.buf().as_entire_binding(), - }, - // lod map (t_map) - wgpu::BindGroupEntry { - binding: 10, - resource: wgpu::BindingResource::TextureView(&lod_data.map.view), - }, - wgpu::BindGroupEntry { - binding: 11, - resource: wgpu::BindingResource::Sampler(&lod_data.map.sampler), - }, - ], + entries: &Self::bind_base_globals(global_model, lod_data, noise), }); GlobalsBindGroup { bind_group } diff --git a/voxygen/src/render/pipelines/shadow.rs b/voxygen/src/render/pipelines/shadow.rs index b85377fad5..ba24ac7e4d 100644 --- a/voxygen/src/render/pipelines/shadow.rs +++ b/voxygen/src/render/pipelines/shadow.rs @@ -116,7 +116,7 @@ pub fn create_col_lights( array_layer_count: None, }; - renderer.create_texture_with_data_raw( + renderer.create_texture_with_data_raw::<4>( &texture_info, &view_info, &sampler_info, diff --git a/voxygen/src/render/pipelines/sprite.rs b/voxygen/src/render/pipelines/sprite.rs index 6c85a77704..c4b26a78eb 100644 --- a/voxygen/src/render/pipelines/sprite.rs +++ b/voxygen/src/render/pipelines/sprite.rs @@ -1,45 +1,51 @@ -use super::super::{AaMode, Bound, Consts, GlobalsLayouts, TerrainLayout, Vertex as VertexTrait}; +use super::{ + super::{ + AaMode, Bound, Consts, GlobalsLayouts, Mesh, Renderer, TerrainLayout, Texture, + Vertex as VertexTrait, + }, + lod_terrain, GlobalModel, +}; use bytemuck::{Pod, Zeroable}; use core::fmt; use std::mem; use vek::*; +pub const VERT_PAGE_SIZE: u32 = 256; + #[repr(C)] #[derive(Copy, Clone, Debug, Zeroable, Pod)] pub struct Vertex { - pos: [f32; 3], + pos_norm: u32, // Because we try to restrict terrain sprite data to a 128×128 block // we need an offset into the texture atlas. atlas_pos: u32, - // ____BBBBBBBBGGGGGGGGRRRRRRRR - // col: u32 = "v_col", - // ...AANNN - // A = AO - // N = Normal - norm_ao: u32, + /* ____BBBBBBBBGGGGGGGGRRRRRRRR + * col: u32 = "v_col", + * .....NNN + * A = AO + * N = Normal + *norm: u32, */ } -impl fmt::Display for Vertex { +// TODO: fix? +/*impl fmt::Display for Vertex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Vertex") - .field("pos", &Vec3::::from(self.pos)) + .field("pos_norm", &Vec3::::from(self.pos)) .field( "atlas_pos", &Vec2::new(self.atlas_pos & 0xFFFF, (self.atlas_pos >> 16) & 0xFFFF), ) - .field("norm_ao", &self.norm_ao) .finish() } -} +}*/ impl Vertex { // NOTE: Limit to 16 (x) × 16 (y) × 32 (z). #[allow(clippy::collapsible_if)] - pub fn new( - atlas_pos: Vec2, - pos: Vec3, - norm: Vec3, /* , col: Rgb, ao: f32 */ - ) -> Self { + pub fn new(atlas_pos: Vec2, pos: Vec3, norm: Vec3) -> Self { + const VERT_EXTRA_NEG_Z: i32 = 128; // NOTE: change if number of bits changes below, also we might not need this if meshing always produces positives values for sprites (I have no idea) + let norm_bits = if norm.x != 0.0 { if norm.x < 0.0 { 0 } else { 1 } } else if norm.y != 0.0 { @@ -54,13 +60,15 @@ impl Vertex { // | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12 // | if meta { 1 } else { 0 } << 28 // | (norm_bits & 0x7) << 29, - pos: pos.into_array(), + pos_norm: ((pos.x as u32) & 0x00FF) // NOTE: temp hack, this doesn't need 8 bits + | ((pos.y as u32) & 0x00FF) << 8 + | (((pos.z as i32 + VERT_EXTRA_NEG_Z).max(0).min(1 << 12) as u32) & 0x0FFF) << 16 + | (norm_bits & 0x7) << 29, atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) | ((atlas_pos.y as u32) & 0xFFFF) << 16, - norm_ao: norm_bits, } } - fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + /*fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { const ATTRIBUTES: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![0 => Float3, 1 => Uint, 2 => Uint]; wgpu::VertexBufferLayout { @@ -68,53 +76,130 @@ impl Vertex { step_mode: wgpu::InputStepMode::Vertex, attributes: &ATTRIBUTES, } - } + }*/ +} + +impl Default for Vertex { + fn default() -> Self { Self::new(Vec2::zero(), Vec3::zero(), Vec3::zero()) } } impl VertexTrait for Vertex { const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; } +pub fn create_verts_texture(renderer: &mut Renderer, mut mesh: Mesh) -> Texture { + let mut verts = mesh.vertices_mut_vec(); + let format = wgpu::TextureFormat::Rg32Uint; + + // TODO: temp + const WIDTH: u32 = 16384; + let height = verts.len() as u32 / WIDTH; + // Fill in verts to full texture size + verts.resize_with(height as usize * WIDTH as usize, Vertex::default); + + let texture_info = wgpu::TextureDescriptor { + label: Some("Sprite verts"), + size: wgpu::Extent3d { + width: WIDTH, + height, + depth: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format, + usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST, + }; + + let sampler_info = wgpu::SamplerDescriptor { + label: None, + address_mode_u: wgpu::AddressMode::Repeat, + address_mode_v: wgpu::AddressMode::Repeat, + address_mode_w: wgpu::AddressMode::Repeat, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, + mipmap_filter: wgpu::FilterMode::Nearest, + ..Default::default() + }; + + let view_info = wgpu::TextureViewDescriptor { + label: None, + format: Some(format), + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + level_count: None, + base_array_layer: 0, + array_layer_count: None, + }; + + renderer.create_texture_with_data_raw::<8>( + &texture_info, + &view_info, + &sampler_info, + bytemuck::cast_slice(verts), + ) +} + #[repr(C)] #[derive(Copy, Clone, Debug, Zeroable, Pod)] pub struct Instance { - pos_ori: u32, inst_mat0: [f32; 4], inst_mat1: [f32; 4], inst_mat2: [f32; 4], inst_mat3: [f32; 4], - inst_light: [f32; 4], - inst_wind_sway: f32, + pos_ori: u32, + inst_vert_page: u32, + inst_light: f32, + inst_glow: f32, + model_wind_sway: f32, + model_z_scale: f32, } impl Instance { pub fn new( mat: Mat4, wind_sway: f32, + z_scale: f32, pos: Vec3, ori_bits: u8, light: f32, glow: f32, + vert_page: u32, ) -> Self { const EXTRA_NEG_Z: i32 = 32768; let mat_arr = mat.into_col_arrays(); Self { - pos_ori: ((pos.x as u32) & 0x003F) - | ((pos.y as u32) & 0x003F) << 6 - | (((pos + EXTRA_NEG_Z).z.max(0).min(1 << 16) as u32) & 0xFFFF) << 12 - | (u32::from(ori_bits) & 0x7) << 29, inst_mat0: mat_arr[0], inst_mat1: mat_arr[1], inst_mat2: mat_arr[2], inst_mat3: mat_arr[3], - inst_light: [light, glow, 1.0, 1.0], - inst_wind_sway: wind_sway, + pos_ori: ((pos.x as u32) & 0x003F) + | ((pos.y as u32) & 0x003F) << 6 + | (((pos.z + EXTRA_NEG_Z).max(0).min(1 << 16) as u32) & 0xFFFF) << 12 + | (u32::from(ori_bits) & 0x7) << 29, + inst_vert_page: vert_page, + inst_light: light, + inst_glow: glow, + model_wind_sway: wind_sway, + model_z_scale: z_scale, } } fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { - const ATTRIBUTES: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![3 => Uint, 4 => Float4, 5 => Float4, 6 => Float4,7 => Float4, 8 => Float4, 9 => Float]; + const ATTRIBUTES: [wgpu::VertexAttribute; 10] = wgpu::vertex_attr_array![ + 0 => Float4, + 1 => Float4, + 2 => Float4, + 3 => Float4, + 4 => Uint, + 5 => Uint, + 6 => Float, + 7 => Float, + 8 => Float, + 9 => Float, + ]; wgpu::VertexBufferLayout { array_stride: mem::size_of::() as wgpu::BufferAddress, step_mode: wgpu::InputStepMode::Instance, @@ -124,10 +209,12 @@ impl Instance { } impl Default for Instance { - fn default() -> Self { Self::new(Mat4::identity(), 0.0, Vec3::zero(), 0, 1.0, 0.0) } + fn default() -> Self { Self::new(Mat4::identity(), 0.0, 0.0, Vec3::zero(), 0, 1.0, 0.0, 0) } } -#[repr(C)] +// TODO: ColLightsWrapper instead? +pub struct Locals; +/*#[repr(C)] #[derive(Copy, Clone, Debug, Zeroable, Pod)] pub struct Locals { // Each matrix performs rotatation, translation, and scaling, relative to the sprite @@ -138,8 +225,6 @@ pub struct Locals { offs: [f32; 4], } -pub type BoundLocals = Bound>; - impl Default for Locals { fn default() -> Self { Self::new(Mat4::identity(), Vec3::one(), Vec3::zero(), 0.0) } } @@ -152,16 +237,53 @@ impl Locals { offs: [offs.x, offs.y, offs.z, 0.0], } } +}*/ + +pub struct SpriteGlobalsBindGroup { + pub(in super::super) bind_group: wgpu::BindGroup, + pub(in super::super) sprite_verts: Texture, } +//pub type BoundLocals = Bound>; + pub struct SpriteLayout { - pub locals: wgpu::BindGroupLayout, + pub globals: wgpu::BindGroupLayout, + //pub locals: wgpu::BindGroupLayout, } impl SpriteLayout { pub fn new(device: &wgpu::Device) -> Self { + let mut entries = GlobalsLayouts::base_globals_layout(); + debug_assert_eq!(12, entries.len()); // To remember to adjust the bindings below + entries.extend_from_slice(&[ + // sprite verts (t_sprite_verts) + wgpu::BindGroupLayoutEntry { + binding: 12, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Uint, + view_dimension: wgpu::TextureViewDimension::D1, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 13, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::Sampler { + filtering: false, + comparison: false, + }, + count: None, + }, + ]); + Self { - locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + globals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &entries, + }), + /*locals: device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ // locals @@ -177,18 +299,64 @@ impl SpriteLayout { }, // instance buffer wgpu::BindGroupLayoutEntry { - binding: 1, + binding: 1, visibility: wgpu::ShaderStage::Vertex, ty: wgpu::BufferBindingType::Buffer { - ty: wgpu::BufferBindingType:: + ty: wgpu::BufferBindingType:: } }, ], - }), + }),*/ } } - pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts) -> BoundLocals { + fn bind_globals_inner( + &self, + device: &wgpu::Device, + global_model: &GlobalModel, + lod_data: &lod_terrain::LodData, + noise: &Texture, + sprite_verts: &Texture, + ) -> wgpu::BindGroup { + let mut entries = GlobalsLayouts::bind_base_globals(global_model, lod_data, noise); + + entries.extend_from_slice(&[ + // sprite verts (t_sprite_verts) + wgpu::BindGroupEntry { + binding: 12, + resource: wgpu::BindingResource::TextureView(&sprite_verts.view), + }, + wgpu::BindGroupEntry { + binding: 13, + resource: wgpu::BindingResource::Sampler(&sprite_verts.sampler), + }, + ]); + + device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &self.globals, + entries: &entries, + }) + } + + pub fn bind_globals( + &self, + device: &wgpu::Device, + global_model: &GlobalModel, + lod_data: &lod_terrain::LodData, + noise: &Texture, + sprite_verts: Texture, + ) -> SpriteGlobalsBindGroup { + let bind_group = + self.bind_globals_inner(device, global_model, lod_data, noise, &sprite_verts); + + SpriteGlobalsBindGroup { + bind_group, + sprite_verts, + } + } + + /*pub fn bind_locals(&self, device: &wgpu::Device, locals: Consts) -> BoundLocals { let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: None, layout: &self.locals, @@ -202,7 +370,7 @@ impl SpriteLayout { bind_group, with: locals, } - } + }*/ } pub struct SpritePipeline { @@ -226,10 +394,11 @@ impl SpritePipeline { label: Some("Sprite pipeline layout"), push_constant_ranges: &[], bind_group_layouts: &[ - &global_layout.globals, + &layout.globals, &global_layout.shadow_textures, &terrain_layout.locals, - &layout.locals, + //&layout.locals, + // Note: mergable with globals &global_layout.col_light, ], }); @@ -248,7 +417,7 @@ impl SpritePipeline { vertex: wgpu::VertexState { module: vs_module, entry_point: "main", - buffers: &[], + buffers: &[Instance::desc()], }, primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index e984d98fa0..3c597ff190 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -320,7 +320,6 @@ impl Renderer { .ok_or(RenderError::CouldNotFindAdapter)?; let limits = wgpu::Limits { - max_bind_groups: 5, max_push_constant_size: 64, ..Default::default() }; @@ -1182,7 +1181,7 @@ impl Renderer { } /// Create a new immutable texture from the provided image. - pub fn create_texture_with_data_raw( + pub fn create_texture_with_data_raw( &mut self, texture_info: &wgpu::TextureDescriptor, view_info: &wgpu::TextureViewDescriptor, @@ -1191,7 +1190,7 @@ impl Renderer { ) -> Texture { let tex = Texture::new_raw(&self.device, &texture_info, &view_info, &sampler_info); - tex.update( + tex.update::( &self.device, &self.queue, [0; 2], @@ -1257,7 +1256,7 @@ impl Renderer { // texture.update(&mut self.encoder, offset, size, data) data: &[[u8; 4]], ) { - texture.update( + texture.update::<4>( &self.device, &self.queue, offset, diff --git a/voxygen/src/render/renderer/binding.rs b/voxygen/src/render/renderer/binding.rs index 9bc704cda5..8723fbb6f5 100644 --- a/voxygen/src/render/renderer/binding.rs +++ b/voxygen/src/render/renderer/binding.rs @@ -20,6 +20,21 @@ impl Renderer { .bind(&self.device, global_model, lod_data, &self.noise_tex) } + pub fn bind_sprite_globals( + &self, + global_model: &GlobalModel, + lod_data: &lod_terrain::LodData, + sprite_verts: Texture, + ) -> sprite::SpriteGlobalsBindGroup { + self.layouts.sprite.bind_globals( + &self.device, + global_model, + lod_data, + &self.noise_tex, + sprite_verts, + ) + } + pub fn create_ui_bound_locals(&mut self, vals: &[ui::Locals]) -> ui::BoundLocals { let locals = self.create_consts(vals); self.layouts.ui.bind_locals(&self.device, locals) @@ -54,10 +69,10 @@ impl Renderer { self.layouts.shadow.bind_locals(&self.device, locals) } - pub fn create_sprite_bound_locals(&mut self, locals: &[sprite::Locals]) -> sprite::BoundLocals { - let locals = self.create_consts(locals); - self.layouts.sprite.bind_locals(&self.device, locals) - } + //pub fn create_sprite_bound_locals(&mut self, locals: &[sprite::Locals]) -> + // sprite::BoundLocals { let locals = self.create_consts(locals); + // self.layouts.sprite.bind_locals(&self.device, locals) + //} pub fn figure_bind_col_light(&self, col_light: Texture) -> ColLights { self.layouts.global.bind_col_light(&self.device, col_light) diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index ef07db6cc2..73eb141431 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -102,6 +102,7 @@ impl<'a> Drawer<'a> { FirstPassDrawer { render_pass, renderer: &self.renderer, + globals: self.globals, } } @@ -365,6 +366,7 @@ impl<'pass_ref, 'pass: 'pass_ref> TerrainShadowDrawer<'pass_ref, 'pass> { pub struct FirstPassDrawer<'pass> { pub(super) render_pass: wgpu::RenderPass<'pass>, pub renderer: &'pass Renderer, + globals: &'pass GlobalsBindGroup, } impl<'pass> FirstPassDrawer<'pass> { @@ -416,15 +418,18 @@ impl<'pass> FirstPassDrawer<'pass> { pub fn draw_sprites<'data: 'pass>( &mut self, + globals: &'data sprite::SpriteGlobalsBindGroup, col_lights: &'data ColLights, ) -> SpriteDrawer<'_, 'pass> { self.render_pass .set_pipeline(&self.renderer.sprite_pipeline.pipeline); + self.render_pass.set_bind_group(0, &globals.bind_group, &[]); self.render_pass - .set_bind_group(4, &col_lights.bind_group, &[]); + .set_bind_group(3, &col_lights.bind_group, &[]); SpriteDrawer { render_pass: &mut self.render_pass, + globals: self.globals, } } @@ -501,38 +506,34 @@ impl<'pass_ref, 'pass: 'pass_ref> ParticleDrawer<'pass_ref, 'pass> { pub struct SpriteDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: &'pass_ref mut wgpu::RenderPass<'pass>, + globals: &'pass GlobalsBindGroup, } impl<'pass_ref, 'pass: 'pass_ref> SpriteDrawer<'pass_ref, 'pass> { - pub fn in_chunk<'data: 'pass>( - &mut self, - terrain_locals: &'data terrain::BoundLocals, - ) -> ChunkSpriteDrawer<'_, 'pass> { - self.render_pass - .set_bind_group(2, &terrain_locals.bind_group, &[]); - - ChunkSpriteDrawer { - render_pass: &mut self.render_pass, - } - } -} -pub struct ChunkSpriteDrawer<'pass_ref, 'pass: 'pass_ref> { - render_pass: &'pass_ref mut wgpu::RenderPass<'pass>, -} - -impl<'pass_ref, 'pass: 'pass_ref> ChunkSpriteDrawer<'pass_ref, 'pass> { pub fn draw<'data: 'pass>( &mut self, - model: &'data Model, + terrain_locals: &'data terrain::BoundLocals, + //model: &'data Model, + //locals: &'data sprite::BoundLocals, instances: &'data Instances, - locals: &'data sprite::BoundLocals, ) { - self.render_pass.set_vertex_buffer(0, model.buf().slice(..)); self.render_pass - .set_vertex_buffer(1, instances.buf().slice(..)); - self.render_pass.set_bind_group(3, &locals.bind_group, &[]); + .set_bind_group(2, &terrain_locals.bind_group, &[]); + //self.render_pass.set_bind_group(3, &locals.bind_group, &[]); + + //self.render_pass.set_vertex_buffer(0, model.buf().slice(..)); self.render_pass - .draw(0..model.len() as u32, 0..instances.count() as u32); + .set_vertex_buffer(0, instances.buf().slice(..)); + self.render_pass + .draw(0..sprite::VERT_PAGE_SIZE - 4, 0..instances.count() as u32); + } +} + +impl<'pass_ref, 'pass: 'pass_ref> Drop for SpriteDrawer<'pass_ref, 'pass> { + fn drop(&mut self) { + // Reset to regular globals + self.render_pass + .set_bind_group(0, &self.globals.bind_group, &[]); } } diff --git a/voxygen/src/render/texture.rs b/voxygen/src/render/texture.rs index 2429695e7e..e7a2b9dc97 100644 --- a/voxygen/src/render/texture.rs +++ b/voxygen/src/render/texture.rs @@ -170,7 +170,9 @@ impl Texture { /// Update a texture with the given data (used for updating the glyph cache /// texture). - pub fn update( + /// TODO: using generic here seems a bit hacky, consider storing this info + /// in the texture type or pass in wgpu::TextureFormat + pub fn update( &self, device: &wgpu::Device, queue: &wgpu::Queue, @@ -178,9 +180,10 @@ impl Texture { size: [u32; 2], data: &[u8], ) { - // Note: we only accept 4 bytes per pixel - // (enforce this in API?) - debug_assert_eq!(data.len(), size[0] as usize * size[1] as usize * 4); + debug_assert_eq!( + data.len(), + size[0] as usize * size[1] as usize * BYTES_PER_PIXEL as usize + ); // TODO: Only works for 2D images queue.write_texture( wgpu::TextureCopyViewBase { @@ -195,7 +198,7 @@ impl Texture { data, wgpu::TextureDataLayout { offset: 0, - bytes_per_row: size[0] * 4, + bytes_per_row: size[0] * BYTES_PER_PIXEL, rows_per_image: size[1], }, wgpu::Extent3d { diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 2881f287ac..c4c96e57a1 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -271,6 +271,8 @@ impl Scene { let globals_bind_group = renderer.bind_globals(&data, lod.get_data()); + let terrain = Terrain::new(renderer, &data, lod.get_data()); + Self { data, globals_bind_group, @@ -281,7 +283,7 @@ impl Scene { skybox: Skybox { model: renderer.create_model(&create_skybox_mesh()).unwrap(), }, - terrain: Terrain::new(renderer), + terrain, lod, loaded_distance: 0.0, map_bounds: Vec2::new( diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 7173a5d8ed..40d8498ece 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -5,10 +5,11 @@ pub use self::watcher::BlocksOfInterest; use crate::{ mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_sprite, terrain::generate_mesh}, render::{ + create_sprite_verts_texture, pipelines::{self, ColLights}, ColLightInfo, Consts, Drawer, FirstPassDrawer, FluidVertex, FluidWaves, GlobalModel, - Instances, LodData, Mesh, Model, RenderError, Renderer, SpriteInstance, SpriteLocals, - SpriteVertex, TerrainLocals, TerrainShadowDrawer, TerrainVertex, Texture, + Instances, LodData, Mesh, Model, RenderError, Renderer, SpriteGlobalsBindGroup, + SpriteInstance, SpriteVertex, TerrainLocals, TerrainShadowDrawer, TerrainVertex, Texture, }, }; @@ -39,6 +40,8 @@ use treeculler::{BVol, Frustum, AABB}; use vek::*; const SPRITE_SCALE: Vec3 = Vec3::new(1.0 / 11.0, 1.0 / 11.0, 1.0 / 11.0); +const SPRITE_LOD_LEVELS: usize = 5; +const SPRITE_VERT_PAGE_SIZE: usize = 256; #[derive(Clone, Copy, Debug)] struct Visibility { @@ -67,7 +70,7 @@ pub struct TerrainChunkData { col_lights: guillotiere::AllocId, light_map: Box) -> f32 + Send + Sync>, glow_map: Box) -> f32 + Send + Sync>, - sprite_instances: HashMap<(SpriteKind, usize), Instances>, + sprite_instances: [Instances; SPRITE_LOD_LEVELS], locals: pipelines::terrain::BoundLocals, pub blocks_of_interest: BlocksOfInterest, @@ -95,7 +98,7 @@ struct MeshWorkerResponse { col_lights_info: ColLightInfo, light_map: Box) -> f32 + Send + Sync>, glow_map: Box) -> f32 + Send + Sync>, - sprite_instances: HashMap<(SpriteKind, usize), Vec>, + sprite_instances: [Vec; SPRITE_LOD_LEVELS], started_tick: u64, blocks_of_interest: BlocksOfInterest, } @@ -150,7 +153,7 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug + ' max_texture_size: u16, chunk: Arc, range: Aabb, - sprite_data: &HashMap<(SpriteKind, usize), Vec>, + sprite_data: &HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>, sprite_config: &SpriteSpec, ) -> MeshWorkerResponse { span!(_guard, "mesh_worker"); @@ -173,7 +176,7 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug + ' // Extract sprite locations from volume sprite_instances: { span!(_guard, "extract sprite_instances"); - let mut instances = HashMap::new(); + let mut instances = [Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()]; for x in 0..V::RECT_SIZE.x as i32 { for y in 0..V::RECT_SIZE.y as i32 { @@ -201,23 +204,43 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug + ' let key = (sprite, variation); // NOTE: Safe because we called sprite_config_for already. // NOTE: Safe because 0 ≤ ori < 8 - let sprite_data = &sprite_data[&key][0]; - let instance = SpriteInstance::new( - Mat4::identity() - .translated_3d(sprite_data.offset) + let sprite_data_lod_0 = &sprite_data[&key][0]; + let mat = Mat4::identity() + // NOTE: offset doesn't change with lod level + // TODO: pull out of per lod level info or remove lod levels + // for sprites entirely + .translated_3d(sprite_data_lod_0.offset) + // Lod scaling etc is baked during meshing + .scaled_3d(SPRITE_SCALE) .rotated_z(f32::consts::PI * 0.25 * ori as f32) .translated_3d( (rel_pos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0)) - / SPRITE_SCALE, - ), - cfg.wind_sway, - rel_pos, - ori, - light_map(wpos), - glow_map(wpos), - ); + ); + let light = light_map(wpos); + let glow = glow_map(wpos); - instances.entry(key).or_insert(Vec::new()).push(instance); + for lod_level in 0..SPRITE_LOD_LEVELS { + let sprite_data = &sprite_data[&key][lod_level]; + // Add an instance for each page in the sprite model + for page in sprite_data.vert_pages.clone() { + // TODO: could be more efficient to create once and clone while + // modifying vert_page + let instance = SpriteInstance::new( + mat, + cfg.wind_sway, + sprite_data.scale.z, + rel_pos, + ori, + light, + glow, + page, + ); + instances[lod_level].push(instance); + } + } + + //instances.entry(key).or_insert(Vec::new()). + // push(instance); } } } @@ -232,11 +255,18 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug + ' } } +// TODO: may be unecessary +struct ChunkSpriteData { + // Instances + model: Instances, +} + struct SpriteData { - /* mat: Mat4, */ - locals: pipelines::sprite::BoundLocals, - model: Model, - /* scale: Vec3, */ + // Sprite vert page ranges that need to be drawn + vert_pages: core::ops::Range, + // Scale + scale: Vec3, + // Offset offset: Vec3, } @@ -271,8 +301,10 @@ pub struct Terrain { mesh_todos_active: Arc, // GPU data - sprite_data: Arc>>, + // Maps sprite kind + variant to data detailing how to render it + sprite_data: Arc>, col_lights: ColLights, + sprite_globals: SpriteGlobalsBindGroup, sprite_col_lights: ColLights, waves: FluidWaves, @@ -285,7 +317,7 @@ impl TerrainChunkData { impl Terrain { #[allow(clippy::float_cmp)] // TODO: Pending review in #587 - pub fn new(renderer: &mut Renderer) -> Self { + pub fn new(renderer: &mut Renderer, global_model: &GlobalModel, lod_data: &LodData) -> Self { // Load all the sprite config data. let sprite_config = Arc::::load_expect("voxygen.voxel.sprite_manifest").cloned(); @@ -300,7 +332,8 @@ impl Terrain { let max_texture_size = renderer.max_texture_size(); let max_size = guillotiere::Size::new(max_texture_size as i32, max_texture_size as i32); let mut greedy = GreedyMesh::new(max_size); - let mut locals_buffer = [SpriteLocals::default(); 8]; + //let mut locals_buffer = [SpriteLocals::default(); 8]; + let mut opaque_mesh = Mesh::new(); 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() @@ -342,66 +375,89 @@ impl Terrain { scale } }); - let sprite_mat: Mat4 = - 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(); - generate_mesh_base_vol_sprite( - 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 = - 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, - ); - }, - ); + //let sprite_mat: Mat4 = + // Mat4::translation_3d(offset).scaled_3d(SPRITE_SCALE); + move |greedy: &mut GreedyMesh, opaque_mesh: &mut Mesh, /* , renderer: &mut Renderer */| { + ((kind, variation), { + let mut iter = 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 }) + }; + // Get starting page count of opaque mesh + let start_page_num = + opaque_mesh.vertices().len() / SPRITE_VERT_PAGE_SIZE; + // Mesh generation exclusively acts using side effects; it + // has no interesting return value, but updates the mesh. + generate_mesh_base_vol_sprite( + Segment::from(&model.read().0).scaled_by(lod_scale), + //( + // greedy, + // opaque_mesh, + // wind_sway >= 0.4 && lod_scale_orig == 1.0, + //), + (greedy, opaque_mesh, false), - SpriteData { - /* vertex_range */ model, - offset, - locals: renderer - .create_sprite_bound_locals(&locals_buffer), - } - }) - .collect::>(), - ) + ); + // Get the number of pages after the model was meshed + let end_page_num = + (opaque_mesh.vertices().len() + SPRITE_VERT_PAGE_SIZE - 1) + / SPRITE_VERT_PAGE_SIZE; + // Fill the current last page up with degenerate verts + opaque_mesh.vertices_mut_vec().resize_with( + end_page_num * SPRITE_VERT_PAGE_SIZE, + SpriteVertex::default, + ); + + let sprite_scale = Vec3::one() / lod_scale; + //let sprite_mat: Mat4 = + // 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 { + vert_pages: start_page_num as u32..end_page_num as u32, + scale: sprite_scale, + offset, + /*locals: renderer + * .create_sprite_bound_locals(&locals_buffer), */ + } + }); + [ + iter.next().unwrap(), + iter.next().unwrap(), + iter.next().unwrap(), + iter.next().unwrap(), + iter.next().unwrap(), + ] + }) } }, ) }) - .map(|mut f| f(&mut greedy, renderer)) + .map(|mut f| f(&mut greedy, &mut opaque_mesh, /* , renderer */)) .collect(); + // Write sprite model to a 1D texture + let sprite_verts_texture = create_sprite_verts_texture(renderer, opaque_mesh); + + let sprite_globals = + renderer.bind_sprite_globals(global_model, lod_data, sprite_verts_texture); + let sprite_col_lights = pipelines::shadow::create_col_lights(renderer, greedy.finalize()); Self { @@ -426,6 +482,7 @@ impl Terrain { renderer.fluid_bind_waves(waves_tex) }, + sprite_globals, col_lights, phantom: PhantomData, } @@ -811,18 +868,20 @@ impl Terrain { col_lights: allocation.id, light_map: response.light_map, glow_map: response.glow_map, - sprite_instances: response - .sprite_instances - .into_iter() - .map(|(kind, instances)| { - ( - kind, - renderer.create_instances(&instances).expect( - "Failed to upload chunk sprite instances to the GPU!", - ), - ) - }) - .collect(), + sprite_instances: { + let mut iter = response.sprite_instances.iter().map(|instances| { + renderer + .create_instances(instances) + .expect("Failed to upload chunk sprite instances to the GPU!") + }); + [ + iter.next().unwrap(), + iter.next().unwrap(), + iter.next().unwrap(), + iter.next().unwrap(), + iter.next().unwrap(), + ] + }, locals: renderer.create_terrain_bound_locals(&[TerrainLocals { model_offs: Vec3::from( response.pos.map2(VolGrid2d::::chunk_size(), |e, sz| { @@ -1147,15 +1206,21 @@ impl Terrain { span!(guard, "Terrain sprites"); let chunk_size = V::RECT_SIZE.map(|e| e as f32); let chunk_mag = (chunk_size * (f32::consts::SQRT_2 * 0.5)).magnitude_squared(); - let mut sprite_drawer = drawer.draw_sprites(&self.sprite_col_lights); + + let sprite_low_detail_distance = sprite_render_distance * 0.75; + let sprite_mid_detail_distance = sprite_render_distance * 0.5; + let sprite_hid_detail_distance = sprite_render_distance * 0.35; + let sprite_high_detail_distance = sprite_render_distance * 0.15; + + let mut sprite_drawer = drawer.draw_sprites(&self.sprite_globals, &self.sprite_col_lights); chunk_iter .clone() .filter(|(_, c)| c.visible.is_visible()) .for_each(|(pos, chunk)| { - let sprite_low_detail_distance = sprite_render_distance * 0.75; - let sprite_mid_detail_distance = sprite_render_distance * 0.5; - let sprite_hid_detail_distance = sprite_render_distance * 0.35; - let sprite_high_detail_distance = sprite_render_distance * 0.15; + // Skip chunk if it has no sprites + if chunk.sprite_instances[0].count() == 0 { + return; + } let chunk_center = pos.map2(chunk_size, |e, sz| (e as f32 + 0.5) * sz); let focus_dist_sqrd = Vec2::from(focus_pos).distance_squared(chunk_center); @@ -1173,31 +1238,27 @@ impl Terrain { chunk_center + chunk_size.x * 0.5 - chunk_size.y * 0.5, )); if focus_dist_sqrd < sprite_render_distance.powi(2) { - // TODO: skip if sprite_instances is empty - let mut chunk_sprite_drawer = sprite_drawer.in_chunk(&chunk.locals); - for (kind, instances) in (&chunk.sprite_instances).into_iter() { - let SpriteData { model, locals, .. } = if kind - .0 - .elim_case_pure(&self.sprite_config.0) - .as_ref() - .map(|config| config.wind_sway >= 0.4) - .unwrap_or(false) - && dist_sqrd <= chunk_mag - || dist_sqrd < sprite_high_detail_distance.powi(2) - { - &self.sprite_data[&kind][0] - } else if dist_sqrd < sprite_hid_detail_distance.powi(2) { - &self.sprite_data[&kind][1] - } else if dist_sqrd < sprite_mid_detail_distance.powi(2) { - &self.sprite_data[&kind][2] - } else if dist_sqrd < sprite_low_detail_distance.powi(2) { - &self.sprite_data[&kind][3] - } else { - &self.sprite_data[&kind][4] - }; + let lod_level = /*let SpriteData { model, locals, .. } = if kind + .0 + .elim_case_pure(&self.sprite_config.0) + .as_ref() + .map(|config| config.wind_sway >= 0.4) + .unwrap_or(false) + &&*/ if dist_sqrd <= chunk_mag + || dist_sqrd < sprite_high_detail_distance.powi(2) + { + 0 + } else if dist_sqrd < sprite_hid_detail_distance.powi(2) { + 1 + } else if dist_sqrd < sprite_mid_detail_distance.powi(2) { + 2 + } else if dist_sqrd < sprite_low_detail_distance.powi(2) { + 3 + } else { + 4 + }; - chunk_sprite_drawer.draw(model, instances, locals); - } + sprite_drawer.draw(&chunk.locals, &chunk.sprite_instances[lod_level]); } }); drop(sprite_drawer);