diff --git a/src/render/light/mod.rs b/src/render/light/mod.rs index dc9fa9c..358b667 100644 --- a/src/render/light/mod.rs +++ b/src/render/light/mod.rs @@ -1,17 +1,193 @@ pub mod point; +use std::{collections::{VecDeque, HashMap}, num::NonZeroU64}; + +use edict::query::EpochOf; pub use point::*; +use tracing::debug; +use wgpu::util::DeviceExt; -use crate::math::Transform; +use std::mem; -#[repr(C)] -#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -pub struct LightUniform { - pub position: glam::Vec3, - // Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here - pub(crate) _padding: u32, - pub color: glam::Vec3, - // Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here - pub(crate) _padding2: u32, +use crate::{math::Transform, ecs::components::TransformComponent}; + +pub struct LightBuffer { + pub bind_group: wgpu::BindGroup, + /// The buffer on the gpu storing the lights + buffer: wgpu::Buffer, + device_limits: wgpu::Limits, + /// The max amount of light casters that could fit in this buffer. + pub max_count: usize, + /// The current amount of light casters in this buffer. + pub current_count: usize, + /// The buffer index for a specific entity/caster. + used_indexes: HashMap, + /// Indexes that were being used but are no longer needed. + dead_indexes: VecDeque, +} + +impl LightBuffer { + pub fn new(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, max_count: usize) -> Self + where + U: Default + bytemuck::Pod + bytemuck::Zeroable + { + let device_limits = device.limits(); + + let buffer = device.create_buffer( + &wgpu::BufferDescriptor { + label: Some("Light buffer"), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + size: (mem::size_of::() * max_count) as u64,//render_limits.max_uniform_buffer_binding_size as u64, + mapped_at_creation: false, + /* label: Some("Light Buffer"), + contents: bytemuck::cast_slice(&[U::default()]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, */ + } + ); + + /* let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + } + ], + label: Some("light_bind_group"), + }); */ + + let stride = device_limits.min_uniform_buffer_offset_alignment as usize + mem::size_of::(); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: &buffer, + offset: 0, + size: Some(NonZeroU64::new(stride as u64).unwrap()) + } + ) + } + ], + label: Some("light_bind_group"), + }); + + + Self { + buffer, + bind_group, + device_limits, + max_count, + current_count: 0, + used_indexes: HashMap::new(), + dead_indexes: VecDeque::new(), + } + } + + pub fn has_light(&self, entity: edict::EntityId) -> bool { + self.used_indexes.contains_key(&entity) + } + + /// Update an existing light in the light buffer. + pub fn update_light(&mut self, queue: &wgpu::Queue, entity: edict::EntityId, light: &PointLightUniform) { + let buffer_idx = *self.used_indexes.get(&entity) + .expect("Entity for Light is not in buffer!"); + + let offset = buffer_idx as u64 * self.device_limits.min_uniform_buffer_offset_alignment as u64; + queue.write_buffer(&self.buffer, offset, bytemuck::bytes_of(light)); + } + + /// Add a new light to the light buffer. + pub fn add_light(&mut self, queue: &wgpu::Queue, entity: edict::EntityId, light: &PointLightUniform) { + let buffer_idx = match self.dead_indexes.pop_front() { + Some(i) => i, + None => { + let i = self.current_count; + self.current_count += 1; + i + }, + }; + + self.used_indexes.insert(entity, buffer_idx); + self.update_light(queue, entity, light); + } + + /// Update, or add a new caster, to the light buffer. + pub fn update_or_add(&mut self, queue: &wgpu::Queue, entity: edict::EntityId, light: &PointLightUniform) { + if self.used_indexes.contains_key(&entity) { + self.update_light(queue, entity, light); + } else { + self.add_light(queue, entity, light); + } + } + + pub fn remove_light(&mut self, queue: &wgpu::Queue, entity: edict::EntityId) { + todo!() // TODO + } + + pub fn prune_dead_lights(&mut self, queue: &wgpu::Queue, entity: edict::EntityId) { + todo!() // TODO + } +} + +pub struct LightUniformBuffers { + pub bindgroup_layout: wgpu::BindGroupLayout, + pub point_lights: LightBuffer, + //spotlights: LightBuffer, + //directional_light +} + +impl LightUniformBuffers { + pub fn new(device: &wgpu::Device) -> Self { + let bindgroup_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + } + ], + label: Some("light_bind_group_layout"), + }); + + let point_lights = LightBuffer::new::(device, &bindgroup_layout, 10); + + Self { + bindgroup_layout, + point_lights, + } + } + + pub fn update_lights(&mut self, queue: &wgpu::Queue, world: &edict::World) { + let now_epoch = world.epoch(); + for (entity, point_light, transform, light_epoch, transform_epoch) + in world.query::<(edict::Entities, &PointLight, &TransformComponent, EpochOf, EpochOf)>().iter() { + + /* if !self.point_lights.has_light(entity) || light_epoch == now_epoch || transform_epoch == now_epoch { + debug!("Updated light after update!"); + let uniform = PointLightUniform::from_bundle(point_light, &transform.transform); + self.point_lights.update_or_add(queue, entity, &uniform); + } */ + let uniform = PointLightUniform::from_bundle(point_light, &transform.transform); + self.point_lights.update_or_add(queue, entity, &uniform); + } + } + + // Binds the light buffer to the render pass. + // + // Parameters: + // * `render_pass` - The render pass to bind the buffers to. + // * `point_bind_index` - The bind group index that the point light buffers will be bound to. + /* pub fn bind_lights<'a, 'b: 'a>(&'a mut self, render_pass: &'b mut wgpu::RenderPass<'b>, point_bind_index: u32) { + render_pass.set_bind_group(point_bind_index, &self.point_lights.bind_group, &[]); + //render_pass.set_bind_group(3, &self.point_light_bind_group, &[]); + } */ } #[repr(C)] diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 9d86b1e..c657eaa 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -22,7 +22,7 @@ use crate::render::light::PointLightUniform; use super::camera::{RenderCamera, CameraUniform}; use super::desc_buf_lay::DescVertexBufferLayout; -use super::light::{LightUniform, PointLight}; +use super::light::{PointLight, LightUniformBuffers}; use super::texture::RenderTexture; use super::transform_buffer_storage::{TransformBufferIndices, TransformBuffers}; use super::vertex::Vertex; @@ -91,9 +91,7 @@ pub struct BasicRenderer { default_texture_bind_group: BindGroup, depth_buffer_texture: RenderTexture, - point_light_buffer: wgpu::Buffer, - point_light_bind_group_layout: BindGroupLayout, - point_light_bind_group: BindGroup, + light_buffers: LightUniformBuffers, } impl BasicRenderer { @@ -243,41 +241,6 @@ impl BasicRenderer { next_indices: TransformBufferIndices { buffer_index: 0, transform_index: 0 }, }; - let point_light_buffer = device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Point Light Buffer"), - contents: bytemuck::cast_slice(&[PointLightUniform::default()]), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - } - ); - - let point_light_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - } - ], - label: Some("point_light_bind_group_layout"), - }); - - let point_light_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &point_light_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: point_light_buffer.as_entire_binding(), - } - ], - label: Some("point_light_bind_group"), - }); - let camera_buffer = device.create_buffer_init( &wgpu::util::BufferInitDescriptor { label: Some("Camera Buffer"), @@ -335,6 +298,8 @@ impl BasicRenderer { } ); + let light_uniform_buffers = LightUniformBuffers::new(&device); + let mut s = Self { window, surface, @@ -366,9 +331,7 @@ impl BasicRenderer { depth_buffer_texture: depth_texture, entity_last_transforms: HashMap::new(), - point_light_buffer, - point_light_bind_group_layout, - point_light_bind_group, + light_buffers: light_uniform_buffers, }; // create the default pipelines @@ -376,7 +339,7 @@ impl BasicRenderer { pipelines.insert(0, Arc::new(FullRenderPipeline::new(&s.device, &s.config, &shader, vec![super::vertex::Vertex::desc(),], vec![&s.texture_bind_group_layout, &s.transform_bind_group_layout, &camera_bind_group_layout, - &s.point_light_bind_group_layout]))); + &s.light_buffers.bindgroup_layout]))); s.render_pipelines = pipelines; s @@ -647,10 +610,7 @@ impl Renderer for BasicRenderer { warn!("Missing camera!"); } - for (point_light, transform) in main_world.query::<(&PointLight, &TransformComponent)>().iter() { - let uniform = PointLightUniform::from_bundle(point_light, &transform.transform); - self.queue.write_buffer(&self.point_light_buffer, 0, bytemuck::cast_slice(&[uniform])); - } + self.light_buffers.update_lights(&self.queue, &main_world); } fn render(&mut self) -> Result<(), wgpu::SurfaceError> { @@ -711,7 +671,10 @@ impl Renderer for BasicRenderer { render_pass.set_bind_group(2, &self.camera_bind_group, &[]); // bind light - render_pass.set_bind_group(3, &self.point_light_bind_group, &[]); + //render_pass.set_bind_group(3, &self.point_light_bind_group, &[]); + render_pass.set_bind_group(3, &self.light_buffers.point_lights.bind_group, &[]); + ////self.light_buffers.bind_lights(&mut render_pass, 3); + // if this mesh uses indices, use them to draw the mesh if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() { diff --git a/src/render/shaders/base.wgsl b/src/render/shaders/base.wgsl index 2123c59..ec7267c 100755 --- a/src/render/shaders/base.wgsl +++ b/src/render/shaders/base.wgsl @@ -1,5 +1,7 @@ // Vertex shader +const max_light_count: i32 = 10; + struct VertexInput { @location(0) position: vec3, @location(1) tex_coords: vec2, @@ -31,10 +33,10 @@ struct PointLight { var u_model_transform: mat4x4; @group(2) @binding(0) -var camera: CameraUniform; +var u_camera: CameraUniform; @group(3) @binding(0) -var point_light: PointLight; +var u_point_lights: array; @vertex fn vs_main( @@ -43,7 +45,7 @@ fn vs_main( var out: VertexOutput; out.tex_coords = model.tex_coords; - out.clip_position = camera.view_proj * u_model_transform * vec4(model.position, 1.0); + out.clip_position = u_camera.view_proj * u_model_transform * vec4(model.position, 1.0); out.world_normal = (u_model_transform * vec4(model.normal, 0.0)).xyz; @@ -61,38 +63,45 @@ var t_diffuse: texture_2d; @group(0)@binding(1) var s_diffuse: sampler; -//@group(3) @binding(0) -//var point_light: PointLight; - @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); + + var light_res = vec3(0.0); + for (var i = 0; i < 1; i++) { + light_res += blinn_phong_point_light(in.world_position, in.world_normal, u_point_lights[i]); + } + let light_object_res = light_res * object_color.xyz; + + return vec4(light_object_res, object_color.a); +} + +fn blinn_phong_point_light(world_pos: vec3, world_norm: vec3, point_light: PointLight) -> vec3 { let light_color = point_light.color.xyz; let light_pos = point_light.position.xyz; - let camera_view_pos = camera.view_pos.xyz; - - let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); + let camera_view_pos = u_camera.view_pos.xyz; // We don't need (or want) much ambient light, so 0.1 is fine let ambient_strength = 0.1; var ambient_color = light_color * ambient_strength; //// diffuse //// - let light_dir = normalize(light_pos - in.world_position); + let light_dir = normalize(light_pos - world_pos); - let diffuse_strength = max(dot(in.world_normal, light_dir), 0.0); + let diffuse_strength = max(dot(world_norm, light_dir), 0.0); var diffuse_color = light_color * diffuse_strength; //// end of diffuse //// //// specular //// - let view_dir = normalize(camera_view_pos - in.world_position); + let view_dir = normalize(camera_view_pos - world_pos); let half_dir = normalize(view_dir + light_dir); - let specular_strength = pow(max(dot(in.world_normal, half_dir), 0.0), 32.0); + let specular_strength = pow(max(dot(world_norm, half_dir), 0.0), 32.0); var specular_color = specular_strength * light_color; //// end of specular //// //// point light attenuation //// - let distance = length(light_pos - in.world_position); + let distance = length(light_pos - world_pos); let attenuation = 1.0 / (point_light.constant + point_light.linear * distance + point_light.quadratic * (distance * distance)); @@ -101,7 +110,5 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { specular_color *= attenuation * point_light.intensity; //// end of point light attenuation //// - let result = (ambient_color + diffuse_color + specular_color) * object_color.xyz; - - return vec4(result, object_color.a); -} + return (ambient_color + diffuse_color + specular_color); +} \ No newline at end of file