tirbofish/dropbear · diff
wip: morph targets
refactor: changed my mess of bind groups into one clean and organised thing :)
Signature present but could not be verified.
Unverified
@@ -28,14 +28,18 @@ pub struct AnimationComponent { #[serde(skip)] pub bone_buffer: Option<wgpu::Buffer>, - #[serde(skip)] - pub bind_group: Option<wgpu::BindGroup>, #[serde(skip)] pub available_animations: Vec<String>, #[serde(skip)] pub last_animation_index: Option<usize>, + + #[serde(skip)] + pub morph_weights: HashMap<usize, Vec<f32>>, + + #[serde(skip)] + pub morph_buffer: Option<wgpu::Buffer>, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -73,9 +77,10 @@ impl Default for AnimationComponent { local_pose: HashMap::new(), skinning_matrices: Vec::new(), bone_buffer: None, - bind_group: None, available_animations: vec![], last_animation_index: None, + morph_weights: HashMap::new(), + morph_buffer: None, } } } @@ -95,6 +100,7 @@ impl AnimationComponent { if self.active_animation_index != self.last_animation_index { self.local_pose.clear(); + self.morph_weights.clear(); self.last_animation_index = self.active_animation_index; } @@ -157,11 +163,11 @@ impl AnimationComponent { } if count == 1 || settings.time <= channel.times[0] { - Self::apply_single_keyframe(channel, 0, &mut self.local_pose, model); + Self::apply_single_keyframe(channel, 0, &mut self.local_pose, &mut self.morph_weights, model); continue; } if settings.time >= channel.times[count - 1] { - Self::apply_single_keyframe(channel, count - 1, &mut self.local_pose, model); + Self::apply_single_keyframe(channel, count - 1, &mut self.local_pose, &mut self.morph_weights, model); continue; } @@ -295,6 +301,41 @@ impl AnimationComponent { } }; } + ChannelValues::MorphWeights(values) => { + let weights = self.morph_weights + .entry(channel.target_node) + .or_insert_with(|| vec![0.0; values[0].len()]); + + *weights = match channel.interpolation { + AnimationInterpolation::Step => values[prev_idx].clone(), + AnimationInterpolation::Linear => { + let a = &values[prev_idx]; + let b = &values[next_idx]; + a.iter().zip(b.iter()) + .map(|(a, b)| a + (b - a) * factor) + .collect() + } + AnimationInterpolation::CubicSpline => { + // stored as [in_tangent, value, out_tangent] per keyframe + let p0 = &values[prev_idx * 3 + 1]; + let m0 = &values[prev_idx * 3 + 2]; + let m1 = &values[next_idx * 3 + 0]; + let p1 = &values[next_idx * 3 + 1]; + let t = factor; + let t2 = t * t; + let t3 = t2 * t; + let h00 = 2.0 * t3 - 3.0 * t2 + 1.0; + let h10 = t3 - 2.0 * t2 + t; + let h01 = -2.0 * t3 + 3.0 * t2; + let h11 = t3 - t2; + p0.iter().enumerate() + .map(|(i, p0i)| { + p0i * h00 + m0[i] * dt * h10 + p1[i] * h01 + m1[i] * dt * h11 + }) + .collect() + } + }; + } } } @@ -310,6 +351,7 @@ impl AnimationComponent { channel: &crate::model::AnimationChannel, index: usize, pose: &mut HashMap<usize, NodeTransform>, + morph_weights: &mut HashMap<usize, Vec<f32>>, model: &Model, ) { let transform = pose.entry(channel.target_node).or_insert_with(|| { @@ -336,6 +378,16 @@ impl AnimationComponent { transform.scale = *val; } } + ChannelValues::MorphWeights(v) => { + let actual_index = match channel.interpolation { + AnimationInterpolation::CubicSpline => index * 3 + 1, + _ => index, + }; + + if let Some(frame) = v.get(actual_index) { + morph_weights.insert(channel.target_node, frame.clone()); + } + }, } } @@ -394,6 +446,36 @@ impl AnimationComponent { return; } + if !self.morph_weights.is_empty() { + let mut flat: Vec<f32> = Vec::new(); + let mut sorted_nodes: Vec<usize> = self.morph_weights.keys().cloned().collect(); + sorted_nodes.sort(); + for node_idx in &sorted_nodes { + flat.extend_from_slice(&self.morph_weights[node_idx]); + } + + let data = bytemuck::cast_slice(&flat); + + if let Some(buffer) = &self.morph_buffer { + if buffer.size() as usize == data.len() { + graphics.queue.write_buffer(buffer, 0, data); + } else { + // size changed (different animation, different target count) — recreate + self.morph_buffer = None; + } + } + + if self.morph_buffer.is_none() { + let buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("morph weights buffer"), + contents: data, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + self.morph_buffer = Some(buffer); + } + } + let data = bytemuck::cast_slice(&self.skinning_matrices); if let Some(buffer) = &self.bone_buffer { @@ -407,19 +489,7 @@ impl AnimationComponent { usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, }); - let bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("skinning bind group"), - layout: &graphics.layouts.skinning_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }); - self.bone_buffer = Some(buffer); - self.bind_group = Some(bind_group); } } } @@ -1,125 +1,3 @@ -use crate::buffer::UniformBuffer; -use crate::graphics::SharedGraphicsContext; -use crate::pipelines::globals::Globals; -use wgpu::{BindGroup, Buffer}; - -/// Bind groups for @group(0) -pub struct SceneGlobalsBindGroup { - pub bind_group: BindGroup, -} - -impl SceneGlobalsBindGroup { - pub fn new( - graphics: &SharedGraphicsContext, - globals_buffer: &UniformBuffer<Globals>, - camera_buffer: &Buffer, - ) -> Self { - let bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene globals+camera bind group"), - layout: &graphics.layouts.scene_globals_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: globals_buffer.buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: camera_buffer.as_entire_binding(), - }, - ], - }); - - Self { bind_group } - } - - pub fn update( - &mut self, - graphics: &SharedGraphicsContext, - globals_buffer: &UniformBuffer<Globals>, - camera_buffer: &Buffer, - ) { - puffin::profile_function!(); - self.bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene globals+camera bind group"), - layout: &graphics.layouts.scene_globals_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: globals_buffer.buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: camera_buffer.as_entire_binding(), - }, - ], - }); - } - - pub fn as_ref(&self) -> &BindGroup { - &self.bind_group - } -} - -/// Bind group for @group(2) -pub struct LightSkinBindGroup { - pub bind_group: BindGroup, -} - -impl LightSkinBindGroup { - pub fn new( - graphics: &SharedGraphicsContext, - light_buffer: &Buffer, - skinning_buffer: &Buffer, - ) -> Self { - let bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene light+skin bind group"), - layout: &graphics.layouts.scene_light_skin_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: light_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: skinning_buffer.as_entire_binding(), - }, - ], - }); - - Self { bind_group } - } - - pub fn update( - &mut self, - graphics: &SharedGraphicsContext, - light_buffer: &Buffer, - skinning_buffer: &Buffer, - ) { - self.bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene light+skin bind group"), - layout: &graphics.layouts.scene_light_skin_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: light_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: skinning_buffer.as_entire_binding(), - }, - ], - }); - } - - pub fn as_ref(&self) -> &BindGroup { - &self.bind_group - } +pub struct PerFrameBindGroup { + } @@ -4,10 +4,7 @@ use std::sync::Arc; use glam::{DMat4, DQuat, DVec3, Mat4}; use serde::{Deserialize, Serialize}; -use wgpu::{ - BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, - BindGroupLayoutEntry, BindingType, BufferBindingType, ShaderStages, -}; +use wgpu::Buffer; use crate::{buffer::UniformBuffer, graphics::SharedGraphicsContext}; @@ -74,7 +71,6 @@ pub struct Camera { pub uniform: CameraUniform, buffer: UniformBuffer<CameraUniform>, - pub bind_group: BindGroup, /// View matrix pub view_mat: DMat4, @@ -94,21 +90,6 @@ pub struct CameraBuilder { } impl Camera { - pub const CAMERA_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = - BindGroupLayoutDescriptor { - entries: &[BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX.union(ShaderStages::FRAGMENT), - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("camera_bind_group_layout"), - }; - /// Creates a new camera pub fn new( graphics: Arc<SharedGraphicsContext>, @@ -136,15 +117,6 @@ impl Camera { let buffer = UniformBuffer::new(&graphics.device, "camera uniform"); - let bind_group = graphics.device.create_bind_group(&BindGroupDescriptor { - layout: &graphics.layouts.camera_bind_group_layout, - entries: &[BindGroupEntry { - binding: 0, - resource: buffer.buffer().as_entire_binding(), - }], - label: Some("camera_bind_group"), - }); - let camera = Self { eye: builder.eye, target: builder.target, @@ -154,7 +126,6 @@ impl Camera { zfar: builder.zfar, uniform, buffer, - bind_group, yaw, pitch, settings: builder.settings, @@ -202,7 +173,7 @@ impl Camera { yaw * pitch } - pub fn buffer(&self) -> &wgpu::Buffer { + pub fn buffer(&self) -> &Buffer { self.buffer.buffer() } @@ -22,7 +22,6 @@ pub struct SharedGraphicsContext { pub surface_format: TextureFormat, pub surface_config: Arc<RwLock<SurfaceConfiguration>>, pub instance: Arc<wgpu::Instance>, - pub layouts: Arc<BindGroupLayouts>, pub window: Arc<Window>, pub viewport_texture: texture::Texture, pub depth_texture: texture::Texture, @@ -32,24 +31,10 @@ pub struct SharedGraphicsContext { pub mipmapper: Arc<MipMapper>, pub hdr: Arc<RwLock<HdrPipeline>>, pub antialiasing: Arc<RwLock<AntiAliasingMode>>, + pub layouts: Arc<BindGroupLayouts>, } impl SharedGraphicsContext { - pub const MODEL_UNIFORM_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = - wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("model_uniform_bind_group_layout"), - }; - pub fn get_egui_context(&self) -> Context { self.egui_renderer.lock().context().clone() } @@ -62,7 +47,6 @@ impl SharedGraphicsContext { device: state.device.clone(), queue: state.queue.clone(), instance: state.instance.clone(), - layouts: state.layouts.clone(), window: state.window.clone(), viewport_texture: state.viewport_texture.clone(), depth_texture: state.depth_texture.clone(), @@ -72,10 +56,9 @@ impl SharedGraphicsContext { surface_format: state.surface_format, mipmapper: state.mipmapper.clone(), hdr: state.hdr.clone(), - // yakui_renderer: state.yakui_renderer.clone(), - // yakui_texture: state.yakui_texture.clone(), surface_config: state.config.clone(), antialiasing: state.antialiasing.clone(), + layouts: state.layouts.clone(), } } } @@ -55,7 +55,7 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use wgpu::{BindGroupLayout, BindGroupLayoutEntry, BindingType, BufferBindingType, Device, ExperimentalFeatures, Instance, Queue, ShaderStages, Surface, SurfaceConfiguration, SurfaceError, TextureFormat, TextureFormatFeatureFlags}; +use wgpu::{BindGroupLayoutEntry, BindingType, BufferBindingType, Device, ExperimentalFeatures, Instance, Queue, ShaderStages, Surface, SurfaceConfiguration, SurfaceError, TextureFormat, TextureFormatFeatureFlags}; use winit::event::{DeviceEvent, DeviceId}; use winit::{ application::ApplicationHandler, @@ -65,10 +65,8 @@ use winit::{ window::Window, }; -use crate::camera::Camera; use crate::egui_renderer::EguiRenderer; use crate::graphics::{CommandEncoder, SharedGraphicsContext}; -use crate::lighting::Light; use crate::mipmap::MipMapper; use crate::texture::Texture; @@ -82,16 +80,288 @@ use winit::window::{WindowAttributes, WindowId}; use crate::multisampling::{AntiAliasingMode}; pub struct BindGroupLayouts { - pub scene_globals_bind_group_layout: BindGroupLayout, - pub shader_globals_bind_group_layout: BindGroupLayout, - pub material_bind_layout: BindGroupLayout, - pub camera_bind_group_layout: BindGroupLayout, - pub light_bind_group_layout: BindGroupLayout, - pub light_array_bind_group_layout: BindGroupLayout, - pub scene_light_skin_bind_group_layout: BindGroupLayout, - pub light_cube_bind_group_layout: BindGroupLayout, - pub environment_bind_group_layout: BindGroupLayout, - pub skinning_bind_group_layout: BindGroupLayout, + pub camera_bind_group_layout: wgpu::BindGroupLayout, + pub per_frame_layout: wgpu::BindGroupLayout, + pub material_bind_layout: wgpu::BindGroupLayout, + pub animation_layout: wgpu::BindGroupLayout, + pub environment_layout: wgpu::BindGroupLayout, + pub light_cube_layout: wgpu::BindGroupLayout, +} + +impl BindGroupLayouts { + pub fn init(device: &wgpu::Device) -> Self { + let camera_bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("camera bind group layout"), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + + let per_frame_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("per frame bind group layout"), + entries: &[ + // u_globals + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // u_camera + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // s_light_array + BindGroupLayoutEntry { + binding: 2, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { + read_only: true + }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let material_bind_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("material_bind_layout"), + entries: &[ + // u_material + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // t_diffuse + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + // s_diffuse + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + // t_normal + wgpu::BindGroupLayoutEntry { + binding: 3, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + // s_normal + wgpu::BindGroupLayoutEntry { + binding: 4, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + // t_emissive + wgpu::BindGroupLayoutEntry { + binding: 5, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + // s_emissive + wgpu::BindGroupLayoutEntry { + binding: 6, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + // t_metallic + wgpu::BindGroupLayoutEntry { + binding: 7, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + // s_metallic + wgpu::BindGroupLayoutEntry { + binding: 8, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + // t_occlusion + wgpu::BindGroupLayoutEntry { + binding: 9, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }, + // s_occlusion + wgpu::BindGroupLayoutEntry { + binding: 10, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count: None, + }, + ], + }); + + let animation_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("animation bind group layout"), + entries: &[ + // s_skinning + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { + read_only: true + }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // s_morph_deltas + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { + read_only: true + }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // s_morph_weights + BindGroupLayoutEntry { + binding: 2, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Storage { + read_only: true + }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + // u_morph_info + BindGroupLayoutEntry { + binding: 3, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + }); + + let environment_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("environment bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::Cube, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, + ], + }); + + let light_cube_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("light cube bind group layout"), + entries: &[ + // u_camera + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, + count: None, + }, + // u_light + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, + count: None, + } + ], + }); + + Self { + camera_bind_group_layout, + per_frame_layout, + material_bind_layout, + animation_layout, + environment_layout, + light_cube_layout, + } + } } /// The backend information, such as the device, queue, config, surface, renderer, window and more. @@ -106,7 +376,6 @@ pub struct State { pub queue: Arc<Queue>, pub config: Arc<RwLock<SurfaceConfiguration>>, pub is_surface_configured: bool, - pub layouts: Arc<BindGroupLayouts>, pub egui_renderer: Arc<Mutex<EguiRenderer>>, pub depth_texture: Texture, pub viewport_texture: Texture, @@ -115,6 +384,7 @@ pub struct State { pub mipmapper: Arc<MipMapper>, pub hdr: Arc<RwLock<HdrPipeline>>, pub antialiasing: Arc<RwLock<AntiAliasingMode>>, + pub layouts: Arc<BindGroupLayouts>, physics_accumulator: Duration, @@ -280,236 +550,6 @@ Hardware: .renderer() .register_native_texture(&device, &viewport_texture.view, wgpu::FilterMode::Linear); - // shaders/shader.wgsl - @group(1) - let camera_bind_group_layout = - device.create_bind_group_layout(&Camera::CAMERA_BIND_GROUP_LAYOUT); - - // shader/light.wgsl - @group(1) - let light_bind_group_layout = - device.create_bind_group_layout(&Light::LIGHT_BIND_GROUP_LAYOUT); - - // shaders/light.wgsl - @group(1) - let light_cube_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - // @binding(0) - binding: 0, - visibility: wgpu::ShaderStages::VERTEX.union(wgpu::ShaderStages::FRAGMENT), - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: Some("Per-Light Layout"), - }); - - // shaders/shader.wgsl - @group(0) - let scene_globals_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("scene globals bind group layout"), - entries: &[ - // u_globals - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // u_camera - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - - // shaders/shader.wgsl - @group(1) - let material_bind_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("material_bind_layout"), - entries: &[ - // u_material - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // t_diffuse - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - // s_diffuse - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - // t_normal - wgpu::BindGroupLayoutEntry { - binding: 3, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - // s_normal - wgpu::BindGroupLayoutEntry { - binding: 4, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - // t_emissive - wgpu::BindGroupLayoutEntry { - binding: 5, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - // s_emissive - wgpu::BindGroupLayoutEntry { - binding: 6, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - // t_metallic - wgpu::BindGroupLayoutEntry { - binding: 7, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - // s_metallic - wgpu::BindGroupLayoutEntry { - binding: 8, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - // t_occlusion - wgpu::BindGroupLayoutEntry { - binding: 9, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - // s_occlusion - wgpu::BindGroupLayoutEntry { - binding: 10, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - }); - - // shaders/light.wgsl - @group(1) - let light_array_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[ - // @binding(0) - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - label: Some("Light Array Layout"), - }); - - // shaders/shader.wgsl - @group(2) - let scene_light_skin_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("scene light+skinning bind group layout"), - entries: &[ - // s_light_array - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - // s_skinning - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }, - ], - }); - - // shaders/shader.wgsl - legacy globals layout - let shader_globals_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("shader.wgsl globals bind group layout"), - entries: &[BindGroupLayoutEntry { - // @binding(0) - binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - }); - let device = Arc::new(device); let queue = Arc::new(queue); @@ -520,56 +560,7 @@ Hardware: antialiasing, ))); - // let yakui_renderer = Arc::new(Mutex::new(yakui_wgpu::YakuiWgpu::new( - // &device, - // &queue - // ))); - - // let yakui_texture = yakui_renderer.lock().add_texture( - // viewport_texture.view.clone(), - // wgpu::FilterMode::default(), - // wgpu::FilterMode::default(), - // wgpu::FilterMode::default(), - // wgpu::AddressMode::default(), - // ); - - let environment_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("environment bind group layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: false }, - view_dimension: wgpu::TextureViewDimension::Cube, - multisampled: false, - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), - count: None, - }, - ], - }); - - let skinning_bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("skinning bind group layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { read_only: true }, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - }); + let layouts = BindGroupLayouts::init(&device); let result = Self { surface: Arc::new(surface), @@ -589,21 +580,8 @@ Hardware: physics_accumulator: Duration::ZERO, scene_manager: scene::Manager::new(), antialiasing: Arc::new(RwLock::new(antialiasing)), - layouts: Arc::new(BindGroupLayouts { - scene_globals_bind_group_layout, - shader_globals_bind_group_layout, - material_bind_layout, - camera_bind_group_layout, - light_bind_group_layout, - light_array_bind_group_layout, - scene_light_skin_bind_group_layout, - light_cube_bind_group_layout, - environment_bind_group_layout, - skinning_bind_group_layout, - }), - // yakui_renderer, - // yakui_texture, hdr, + layouts: Arc::new(layouts), }; Ok(result) @@ -851,7 +829,6 @@ Hardware: drop(self.egui_renderer); drop(self.depth_texture); - drop(self.layouts); drop(self.surface); @@ -8,7 +8,6 @@ use crate::{entity::Transform, model::Model}; use glam::{DMat4, DQuat, DVec3}; use std::fmt::{Display, Formatter}; use std::sync::Arc; -use wgpu::BindGroup; pub const MAX_LIGHTS: usize = 10; @@ -272,28 +271,11 @@ pub struct Light { pub cube_model: Handle<Model>, pub label: String, pub buffer: UniformBuffer<LightUniform>, - pub bind_group: BindGroup, pub instance_buffer: ResizableBuffer<InstanceInput>, pub component: LightComponent, } impl Light { - pub const LIGHT_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = - wgpu::BindGroupLayoutDescriptor { - // @binding(0) - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX.union(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 descriptor"), - }; - pub async fn new( graphics: Arc<SharedGraphicsContext>, light: LightComponent, @@ -327,17 +309,6 @@ impl Light { let buffer = UniformBuffer::new(&graphics.device, &label_str); - let bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &graphics.layouts.light_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.buffer().as_entire_binding(), - }], - label, - }); - let transform = light.to_transform(); let instance: InstanceInput = DMat4::from_scale_rotation_translation( transform.scale, @@ -362,7 +333,6 @@ impl Light { cube_model, label: label_str, buffer, - bind_group, instance_buffer, component: light.clone(), } @@ -14,9 +14,9 @@ use serde::{Deserialize, Serialize}; use std::hash::{DefaultHasher, Hash, Hasher}; use std::sync::Arc; use std::{mem, ops::Range}; -use wgpu::{BindGroup, BufferAddress, VertexAttribute, VertexBufferLayout, util::DeviceExt}; +use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, util::DeviceExt}; -// do not derive clone otherwise it wil take too much memory +// note to self: do not derive clone otherwise it wil take too much memory // #[derive(Clone)] pub struct Model { pub hash: u64, // also the id related to the handle @@ -47,7 +47,6 @@ pub struct Material { pub emissive_texture: Option<Texture>, pub metallic_roughness_texture: Option<Texture>, pub occlusion_texture: Option<Texture>, - pub bind_group: wgpu::BindGroup, pub tint: [f32; 4], pub emissive_factor: [f32; 3], pub metallic_factor: f32, @@ -59,6 +58,7 @@ pub struct Material { pub normal_scale: f32, pub uv_tiling: [f32; 2], pub tint_buffer: UniformBuffer<MaterialUniform>, + pub bind_group: wgpu::BindGroup, pub texture_tag: Option<String>, pub wrap_mode: TextureWrapMode, pub has_normal_texture: bool, @@ -151,6 +151,7 @@ pub enum ChannelValues { Translations(Vec<glam::Vec3>), Rotations(Vec<glam::Quat>), Scales(Vec<glam::Vec3>), + MorphWeights(Vec<Vec<f32>>), } impl Material { @@ -193,22 +194,65 @@ impl Material { let tint_buffer = UniformBuffer::new(&graphics.device, "material_tint_uniform"); tint_buffer.write(&graphics.queue, &uniform); - let bind_group = Self::create_bind_group( - &graphics, - &diffuse_texture, - &normal_texture, - &emissive_texture_bound, - &metallic_roughness_texture_bound, - &occlusion_texture_bound, - &tint_buffer, - &name, - ); + let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("material bind group"), + layout: &graphics.layouts.material_bind_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: tint_buffer.buffer().as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::TextureView(&normal_texture.view), + }, + wgpu::BindGroupEntry { + binding: 4, + resource: wgpu::BindingResource::Sampler(&normal_texture.sampler), + }, + wgpu::BindGroupEntry { + binding: 5, + resource: wgpu::BindingResource::TextureView(&emissive_texture_bound.view), + }, + wgpu::BindGroupEntry { + binding: 6, + resource: wgpu::BindingResource::Sampler(&emissive_texture_bound.sampler), + }, + wgpu::BindGroupEntry { + binding: 7, + resource: wgpu::BindingResource::TextureView( + &metallic_roughness_texture_bound.view, + ), + }, + wgpu::BindGroupEntry { + binding: 8, + resource: wgpu::BindingResource::Sampler( + &metallic_roughness_texture_bound.sampler, + ), + }, + wgpu::BindGroupEntry { + binding: 9, + resource: wgpu::BindingResource::TextureView(&occlusion_texture_bound.view), + }, + wgpu::BindGroupEntry { + binding: 10, + resource: wgpu::BindingResource::Sampler(&occlusion_texture_bound.sampler), + }, + ], + }); Self { name, diffuse_texture, normal_texture, - bind_group, tint, emissive_factor: [0.0, 0.0, 0.0], metallic_factor: 1.0, @@ -220,6 +264,7 @@ impl Material { normal_scale: 1.0, uv_tiling, tint_buffer, + bind_group, texture_tag, wrap_mode: TextureWrapMode::Repeat, emissive_texture, @@ -229,71 +274,6 @@ impl Material { } } - pub fn create_bind_group( - graphics: &SharedGraphicsContext, - diffuse: &Texture, - normal: &Texture, - emissive: &Texture, - metallic_roughness: &Texture, - occlusion: &Texture, - uniform_buffer: &UniformBuffer<MaterialUniform>, - name: &str, - ) -> BindGroup { - puffin::profile_function!(); - graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some(format!("{} texture bind group", name).as_str()), - layout: &graphics.layouts.material_bind_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: uniform_buffer.buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(&diffuse.view), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&diffuse.sampler), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: wgpu::BindingResource::TextureView(&normal.view), - }, - wgpu::BindGroupEntry { - binding: 4, - resource: wgpu::BindingResource::Sampler(&normal.sampler), - }, - wgpu::BindGroupEntry { - binding: 5, - resource: wgpu::BindingResource::TextureView(&emissive.view), - }, - wgpu::BindGroupEntry { - binding: 6, - resource: wgpu::BindingResource::Sampler(&emissive.sampler), - }, - wgpu::BindGroupEntry { - binding: 7, - resource: wgpu::BindingResource::TextureView(&metallic_roughness.view), - }, - wgpu::BindGroupEntry { - binding: 8, - resource: wgpu::BindingResource::Sampler(&metallic_roughness.sampler), - }, - wgpu::BindGroupEntry { - binding: 9, - resource: wgpu::BindingResource::TextureView(&occlusion.view), - }, - wgpu::BindGroupEntry { - binding: 10, - resource: wgpu::BindingResource::Sampler(&occlusion.sampler), - }, - ], - }) - } - pub fn sync_uniform(&self, graphics: &SharedGraphicsContext) { let uniform = MaterialUniform { base_colour: self.tint, @@ -833,6 +813,18 @@ impl Model { max_time = max_time.max(last_time); } + let num_morph_targets = channel + .target() + .node() + .mesh() + .map(|mesh| { + mesh.primitives() + .next() + .map(|prim| prim.morph_targets().count()) + .unwrap_or(0) + }) + .unwrap_or(0); + let values = match target.property() { gltf::animation::Property::Translation => { puffin::profile_scope!("reading translation values"); @@ -894,17 +886,30 @@ impl Model { } gltf::animation::Property::MorphTargetWeights => { puffin::profile_scope!("reading morph target weights"); - // Skip morph targets for now - continue; + if let Some(outputs) = reader.read_outputs() { + match outputs { + gltf::animation::util::ReadOutputs::MorphTargetWeights(iter) => { + let flat: Vec<f32> = iter.into_f32().collect(); + + let weights: Vec<Vec<f32>> = flat + .chunks(num_morph_targets) + .map(|chunk| chunk.to_vec()) + .collect(); + + ChannelValues::MorphWeights(weights) + } + _ => continue, + } + } else { + continue; + } } }; let interpolation = match channel.sampler().interpolation() { gltf::animation::Interpolation::Linear => AnimationInterpolation::Linear, gltf::animation::Interpolation::Step => AnimationInterpolation::Step, - gltf::animation::Interpolation::CubicSpline => { - AnimationInterpolation::CubicSpline - } + gltf::animation::Interpolation::CubicSpline => AnimationInterpolation::CubicSpline, // note with morph target weights }; channels.push(AnimationChannel { @@ -1300,7 +1305,7 @@ pub trait DrawModel<'a> { mesh: &'a Mesh, material: &'a Material, globals_camera_bind_group: &'a wgpu::BindGroup, - light_skin_bind_group: &'a wgpu::BindGroup, + animation_bind_group: &'a wgpu::BindGroup, environment_bind_group: &'a wgpu::BindGroup, ); fn draw_mesh_instanced( @@ -1309,7 +1314,7 @@ pub trait DrawModel<'a> { material: &'a Material, instances: Range<u32>, globals_camera_bind_group: &'a wgpu::BindGroup, - light_skin_bind_group: &'a wgpu::BindGroup, + animation_bind_group: &'a wgpu::BindGroup, environment_bind_group: &'a wgpu::BindGroup, ); @@ -1318,7 +1323,7 @@ pub trait DrawModel<'a> { &mut self, model: &'a Model, globals_camera_bind_group: &'a wgpu::BindGroup, - light_skin_bind_group: &'a wgpu::BindGroup, + animation_bind_group: &'a wgpu::BindGroup, environment_bind_group: &'a wgpu::BindGroup, ); fn draw_model_instanced( @@ -1326,7 +1331,7 @@ pub trait DrawModel<'a> { model: &'a Model, instances: Range<u32>, globals_camera_bind_group: &'a wgpu::BindGroup, - light_skin_bind_group: &'a wgpu::BindGroup, + animation_bind_group: &'a wgpu::BindGroup, environment_bind_group: &'a wgpu::BindGroup, ); } @@ -1340,7 +1345,7 @@ where mesh: &'b Mesh, material: &'b Material, globals_camera_bind_group: &'b wgpu::BindGroup, - light_skin_bind_group: &'a wgpu::BindGroup, + animation_bind_group: &'a wgpu::BindGroup, environment_bind_group: &'a wgpu::BindGroup, ) { self.draw_mesh_instanced( @@ -1348,7 +1353,7 @@ where material, 0..1, globals_camera_bind_group, - light_skin_bind_group, + animation_bind_group, environment_bind_group, ); } @@ -1359,14 +1364,14 @@ where material: &'b Material, instances: Range<u32>, globals_camera_bind_group: &'b wgpu::BindGroup, - light_skin_bind_group: &'a wgpu::BindGroup, + animation_bind_group: &'a wgpu::BindGroup, environment_bind_group: &'a wgpu::BindGroup, ) { self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32); self.set_bind_group(0, globals_camera_bind_group, &[]); self.set_bind_group(1, &material.bind_group, &[]); - self.set_bind_group(2, light_skin_bind_group, &[]); + self.set_bind_group(2, animation_bind_group, &[]); self.set_bind_group(3, environment_bind_group, &[]); self.draw_indexed(0..mesh.num_elements, 0, instances); @@ -1376,14 +1381,14 @@ where &mut self, model: &'b Model, globals_camera_bind_group: &'b wgpu::BindGroup, - light_skin_bind_group: &'a wgpu::BindGroup, + animation_bind_group: &'a wgpu::BindGroup, environment_bind_group: &'a wgpu::BindGroup, ) { self.draw_model_instanced( model, 0..1, globals_camera_bind_group, - light_skin_bind_group, + animation_bind_group, environment_bind_group ); } @@ -1393,7 +1398,7 @@ where model: &'b Model, instances: Range<u32>, globals_camera_bind_group: &'b wgpu::BindGroup, - light_skin_bind_group: &'a wgpu::BindGroup, + animation_bind_group: &'a wgpu::BindGroup, environment_bind_group: &'a wgpu::BindGroup, ) { for mesh in &model.meshes { @@ -1403,7 +1408,7 @@ where material, instances.clone(), globals_camera_bind_group, - light_skin_bind_group, + animation_bind_group, environment_bind_group, ); } @@ -25,7 +25,6 @@ impl Default for Globals { pub struct GlobalsUniform { pub data: Globals, pub buffer: UniformBuffer<Globals>, - pub bind_group: wgpu::BindGroup, } impl GlobalsUniform { @@ -34,24 +33,12 @@ impl GlobalsUniform { let buffer = UniformBuffer::new(&graphics.device, label); - let bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &graphics.layouts.shader_globals_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.buffer().as_entire_binding(), - }], - label: Some(label), - }); - let data = Globals::default(); buffer.write(&graphics.queue, &data); Self { data, buffer, - bind_group, } } @@ -34,8 +34,7 @@ impl DropbearShaderPipeline for LightCubePipeline { .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("light cube pipeline layout"), bind_group_layouts: &[ - &graphics.layouts.camera_bind_group_layout, - &graphics.layouts.light_cube_bind_group_layout, + &graphics.layouts.light_cube_layout, ], push_constant_ranges: &[], }); @@ -104,7 +103,7 @@ impl DropbearShaderPipeline for LightCubePipeline { let light_bind_group = graphics .device .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &graphics.layouts.light_array_bind_group_layout, + layout: &graphics.layouts.light_cube_layout, entries: &[wgpu::BindGroupEntry { binding: 0, resource: light_buffer.as_entire_binding(), @@ -1,7 +1,7 @@ use crate::graphics::{InstanceRaw, SharedGraphicsContext}; use crate::model; use crate::model::Vertex; -use crate::pipelines::DropbearShaderPipeline; +use crate::pipelines::{DropbearShaderPipeline}; use crate::shader::Shader; use crate::texture::Texture; use std::sync::Arc; @@ -12,6 +12,11 @@ pub struct MainRenderPipeline { shader: Shader, pipeline_layout: wgpu::PipelineLayout, pipeline: wgpu::RenderPipeline, + + pub per_frame: Option<wgpu::BindGroup>, + pub per_material: Option<wgpu::BindGroup>, + pub animation: Option<wgpu::BindGroup>, + pub environment: Option<wgpu::BindGroup>, } impl DropbearShaderPipeline for MainRenderPipeline { @@ -23,10 +28,10 @@ impl DropbearShaderPipeline for MainRenderPipeline { ); let bind_group_layouts = vec![ - &graphics.layouts.scene_globals_bind_group_layout, // @group(0) - &graphics.layouts.material_bind_layout, // @group(1) - &graphics.layouts.scene_light_skin_bind_group_layout, // @group(2) - &graphics.layouts.environment_bind_group_layout, // @group(3) + &graphics.layouts.per_frame_layout, + &graphics.layouts.material_bind_layout, + &graphics.layouts.animation_layout, + &graphics.layouts.environment_layout, ]; let pipeline_layout = @@ -92,13 +97,13 @@ impl DropbearShaderPipeline for MainRenderPipeline { shader, pipeline_layout, pipeline, + per_frame: None, + per_material: None, + animation: None, + environment: None, } } - fn shader(&self) -> &Shader { - &self.shader - } - fn pipeline_layout(&self) -> &wgpu::PipelineLayout { &self.pipeline_layout } @@ -106,4 +111,177 @@ impl DropbearShaderPipeline for MainRenderPipeline { fn pipeline(&self) -> &wgpu::RenderPipeline { &self.pipeline } + + fn shader(&self) -> &Shader { + &self.shader + } +} + +impl MainRenderPipeline { + pub fn per_frame_bind_group( + &mut self, + graphics: Arc<SharedGraphicsContext>, + globals_buffer: &wgpu::Buffer, + camera_buffer: &wgpu::Buffer, + light_array_buffer: &wgpu::Buffer, + ) -> &wgpu::BindGroup { + if self.per_frame.is_none() { + let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("per frame bind group"), + layout: &graphics.layouts.per_frame_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: globals_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: camera_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: light_array_buffer.as_entire_binding(), + }, + ], + }); + + self.per_frame = Some(bind_group); + } + + self.per_frame.as_ref().unwrap() // safe as its guaranteed to always have some content + } + + pub fn per_material_bind_group( + &mut self, + graphics: Arc<SharedGraphicsContext>, + material_uniform_buffer: &wgpu::Buffer, + diffuse_texture: &Texture, + normal_texture: &Texture, + emissive_texture: &Texture, + metallic_texture: &Texture, + occlusion_texture: &Texture, + ) -> &wgpu::BindGroup { + if self.per_material.is_none() { + let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("per material bind group"), + layout: &graphics.layouts.material_bind_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: material_uniform_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: wgpu::BindingResource::TextureView(&normal_texture.view), + }, + wgpu::BindGroupEntry { + binding: 4, + resource: wgpu::BindingResource::Sampler(&normal_texture.sampler), + }, + wgpu::BindGroupEntry { + binding: 5, + resource: wgpu::BindingResource::TextureView(&emissive_texture.view), + }, + wgpu::BindGroupEntry { + binding: 6, + resource: wgpu::BindingResource::Sampler(&emissive_texture.sampler), + }, + wgpu::BindGroupEntry { + binding: 7, + resource: wgpu::BindingResource::TextureView(&metallic_texture.view), + }, + wgpu::BindGroupEntry { + binding: 8, + resource: wgpu::BindingResource::Sampler(&metallic_texture.sampler), + }, + wgpu::BindGroupEntry { + binding: 9, + resource: wgpu::BindingResource::TextureView(&occlusion_texture.view), + }, + wgpu::BindGroupEntry { + binding: 10, + resource: wgpu::BindingResource::Sampler(&occlusion_texture.sampler), + }, + ], + }); + + self.per_material = Some(bind_group); + } + + self.per_material.as_ref().unwrap() + } + + pub fn animation_bind_group( + &mut self, + graphics: Arc<SharedGraphicsContext>, + skinning_buffer: &wgpu::Buffer, + morph_deltas_buffer: &wgpu::Buffer, + morph_weights_buffer: &wgpu::Buffer, + morph_info_buffer: &wgpu::Buffer, + ) -> &wgpu::BindGroup { + if self.animation.is_none() { + let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("animation bind group"), + layout: &graphics.layouts.animation_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: skinning_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: morph_deltas_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: morph_weights_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: morph_info_buffer.as_entire_binding(), + }, + ], + }); + + self.animation = Some(bind_group); + } + + self.animation.as_ref().unwrap() + } + + pub fn environment_bind_group( + &mut self, + graphics: Arc<SharedGraphicsContext>, + environment_view: &wgpu::TextureView, + environment_sampler: &wgpu::Sampler, + ) -> &wgpu::BindGroup { + if self.environment.is_none() { + let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("environment bind group"), + layout: &graphics.layouts.environment_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(environment_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(environment_sampler), + }, + ], + }); + + self.environment = Some(bind_group); + } + + self.environment.as_ref().unwrap() + } } @@ -1,10 +1,10 @@ #include "dropbear.slang" [[vk::binding(0, 0)]] -ConstantBuffer<CameraUniform> camera; +ConstantBuffer<CameraUniform> u_camera; -[[vk::binding(0, 1)]] -ConstantBuffer<Light> light; +[[vk::binding(1, 0)]] +ConstantBuffer<Light> u_light; struct VertexInput { [[vk::location(0)]] @@ -40,8 +40,8 @@ VertexOutput vs_main(VertexInput input) { ); VertexOutput output; - output.clip_position = mul(camera.view_proj, mul(model_matrix, float4(input.position, 1.0))); - output.color = light.color.xyz; + output.clip_position = mul(u_camera.view_proj, mul(model_matrix, float4(input.position, 1.0))); + output.color = u_light.color.xyz; return output; } @@ -40,11 +40,20 @@ struct MaterialUniform { has_occlusion_texture: u32, // 0,1 bool } +struct MorphTargetInfo { + num_vertices: u32, + num_targets: u32, +} + +// per-frame @group(0) @binding(0) var<uniform> u_globals: Globals; @group(0) @binding(1) var<uniform> u_camera: CameraUniform; +@group(0) @binding(2) +var<storage, read> s_light_array: array<Light>; +// per-material @group(1) @binding(0) var<uniform> u_material: MaterialUniform; @group(1) @binding(1) @@ -68,11 +77,17 @@ var t_occlusion: texture_2d<f32>; @group(1) @binding(10) var s_occlusion: sampler; +// animation @group(2) @binding(0) -var<storage, read> s_light_array: array<Light>; -@group(2) @binding(1) var<storage, read> s_skinning: array<mat4x4<f32>>; +@group(2) @binding(1) +var<storage, read> s_morph_deltas: array<f32>; +@group(2) @binding(2) +var<storage, read> s_morph_weights: array<f32>; +@group(2) @binding(3) +var<uniform> u_morph_info: MorphTargetInfo; +// environment @group(3) @binding(0) var env_map: texture_cube<f32>; @group(3) @binding(1) @@ -1,5 +1,5 @@ use crate::graphics::SharedGraphicsContext; -use crate::pipelines::create_render_pipeline_ex; +use crate::pipelines::{create_render_pipeline_ex}; use crate::texture::Texture; use image::codecs::hdr::HdrDecoder; use std::io::Cursor; @@ -264,29 +264,78 @@ impl HdrLoader { pub struct SkyPipeline { pub texture: CubeTexture, pub pipeline: wgpu::RenderPipeline, - pub bind_group: wgpu::BindGroup, + pub camera_layout: wgpu::BindGroupLayout, + pub environment_layout: wgpu::BindGroupLayout, + pub camera_bind_group: wgpu::BindGroup, + pub environment_bind_group: wgpu::BindGroup, } impl SkyPipeline { - pub fn new(graphics: Arc<SharedGraphicsContext>, sky_texture: CubeTexture) -> Self { + pub fn new( + graphics: Arc<SharedGraphicsContext>, + sky_texture: CubeTexture, + camera_buffer: &wgpu::Buffer, + ) -> Self { puffin::profile_function!(); - let environment_bind_group = - graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("environment_bind_group"), - layout: &graphics.layouts.environment_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&sky_texture.view()), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(sky_texture.sampler()), - }, - ], - }); + let camera_layout = graphics.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("sky camera bind group layout"), + 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, + }], + }); + + let environment_layout = graphics.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("sky environment bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::Cube, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, + ], + }); + + let camera_bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("sky camera bind group"), + layout: &camera_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera_buffer.as_entire_binding(), + }], + }); + + let environment_bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("sky environment bind group"), + layout: &environment_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(sky_texture.view()), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(sky_texture.sampler()), + }, + ], + }); let sky_pipeline = { let layout = graphics @@ -294,8 +343,8 @@ impl SkyPipeline { .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Sky Pipeline Layout"), bind_group_layouts: &[ - &graphics.layouts.camera_bind_group_layout, - &graphics.layouts.environment_bind_group_layout, + &camera_layout, + &environment_layout, ], push_constant_ranges: &[], }); @@ -318,7 +367,10 @@ impl SkyPipeline { Self { texture: sky_texture, pipeline: sky_pipeline, - bind_group: environment_bind_group, + camera_layout, + environment_layout, + camera_bind_group, + environment_bind_group, } } } @@ -7,60 +7,6 @@ use image::GenericImageView; use serde::{Deserialize, Serialize}; use crate::multisampling::{AntiAliasingMode}; -/// As defined in `shaders.wgsl` as -/// ``` -/// @group(0) @binding(0) -/// var t_diffuse: texture_2d<f32>; -/// @group(0) @binding(1) -/// var s_diffuse: sampler; -/// @group(0) @binding(2) -/// var t_normal: texture_2d<f32>; -/// @group(0) @binding(3) -/// var s_normal: sampler; -/// ``` -pub const TEXTURE_BIND_GROUP_LAYOUT: wgpu::BindGroupLayoutDescriptor<'_> = - wgpu::BindGroupLayoutDescriptor { - entries: &[ - // t_diffuse - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, - }, - // s_diffuse - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - // t_normal - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - }, - count: None, - }, - // s_normal - wgpu::BindGroupLayoutEntry { - binding: 3, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, - }, - ], - label: Some("texture bind group layout"), - }; - #[derive(Clone)] /// Describes a texture, like an image of some sort. Can be a normal texture on a model or a viewport or depth texture. pub struct Texture { @@ -407,6 +407,7 @@ pub enum NChannelValues { Translations { values: Vec<NVector3> }, Rotations { values: Vec<NQuaternion> }, Scales { values: Vec<NVector3> }, + MorphWeights { values: Vec<Vec<f64>> }, } impl ToJObject for NChannelValues { @@ -442,6 +443,16 @@ impl ToJObject for NChannelValues { .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?; Ok(obj) } + NChannelValues::MorphWeights { values } => { + let class = env + .find_class("com/dropbear/asset/model/ChannelValues$MorphWeights") + .map_err(|_| DropbearNativeError::JNIClassNotFound)?; + let list = values.to_jobject(env)?; + let obj = env + .new_object(&class, "(Ljava/util/List;)V", &[JValue::Object(&list)]) + .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?; + Ok(obj) + } } } } @@ -570,6 +581,12 @@ fn map_channel_values(values: &ChannelValues) -> NChannelValues { ChannelValues::Scales(list) => NChannelValues::Scales { values: list.iter().map(|v| NVector3::from(*v)).collect(), }, + ChannelValues::MorphWeights(items) => NChannelValues::MorphWeights { + values: items + .iter() + .map(|weights| weights.iter().map(|v| *v as f64).collect()) + .collect(), + }, } } @@ -13,6 +13,8 @@ pub mod viewport; pub(crate) use crate::editor::dock::*; +const MAX_MORPH_WEIGHTS: usize = 4096; + use crate::about::AboutWindow; use crate::build::build; use crate::debug; @@ -57,6 +59,7 @@ use eucalyptus_core::{ utils::ViewportMode, warn, }; +use glam::Mat4; use hecs::{Entity, World}; use log::{debug, error}; use parking_lot::{Mutex, RwLock}; @@ -67,6 +70,7 @@ use std::{collections::HashMap, fs, path::PathBuf, sync::Arc, time::Instant}; use tokio::sync::oneshot; use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode, GizmoOrientation}; use wgpu::{Color, Extent3d}; +use wgpu::util::DeviceExt; use winit::dpi::PhysicalSize; use dropbear_engine::multisampling::AntiAliasingMode; use winit::window::{CursorGrabMode, WindowAttributes}; @@ -92,10 +96,12 @@ pub struct Editor { pub collider_wireframe_pipeline: Option<ColliderWireframePipeline>, pub mipmapper: Option<MipMapper>, pub sky_pipeline: Option<SkyPipeline>, + pub(crate) camera_bind_group: Option<wgpu::BindGroup>, pub(crate) default_skinning_buffer: Option<wgpu::Buffer>, - pub(crate) default_skinning_bind_group: Option<wgpu::BindGroup>, - pub(crate) light_skin_bind_group: Option<wgpu::BindGroup>, - pub(crate) scene_globals_bind_group: Option<dropbear_engine::bind_groups::SceneGlobalsBindGroup>, + pub(crate) default_morph_deltas_buffer: Option<wgpu::Buffer>, + pub(crate) default_morph_weights_buffer: Option<wgpu::Buffer>, + pub(crate) default_morph_info_buffer: Option<wgpu::Buffer>, + pub(crate) default_animation_bind_group: Option<wgpu::BindGroup>, pub active_camera: Arc<Mutex<Option<Entity>>>, @@ -302,10 +308,12 @@ impl Editor { collider_instance_buffer: None, mipmapper: None, sky_pipeline: None, + camera_bind_group: None, default_skinning_buffer: None, - default_skinning_bind_group: None, - light_skin_bind_group: None, - scene_globals_bind_group: None, + default_morph_deltas_buffer: None, + default_morph_weights_buffer: None, + default_morph_info_buffer: None, + default_animation_bind_group: None, }) } @@ -1402,25 +1410,146 @@ impl Editor { self.collider_wireframe_pipeline = Some(ColliderWireframePipeline::new(graphics.clone())); self.mipmapper = None; + self.camera_bind_group = None; + self.default_skinning_buffer = None; + self.default_morph_deltas_buffer = None; + self.default_morph_weights_buffer = None; + self.default_morph_info_buffer = None; + self.default_animation_bind_group = None; + self.texture_id = Some((*graphics.texture_id).clone()); self.window = Some(graphics.window.clone()); self.is_world_loaded.mark_rendering_loaded(); - let sky_texture = HdrLoader::from_equirectangular_bytes( - &graphics.device, - &graphics.queue, - skybox_texture.map_or(DEFAULT_SKY_TEXTURE, |v| v.as_slice()), - 1080, - Some("sky texture"), - ); + let mut pending_sky_pipeline = None; + + let active_camera = self.active_camera.lock().clone(); + if let Some(camera_entity) = active_camera { + if let Ok(camera) = self.world.query_one::<&Camera>(camera_entity).get() { + self.camera_bind_group = Some(graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("editor camera bind group"), + layout: &graphics.layouts.camera_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera.buffer().as_entire_binding(), + }], + })); + + let max_skinning_matrices = 256usize; + let identity = vec![Mat4::IDENTITY; max_skinning_matrices]; + let skinning_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("editor default skinning buffer"), + contents: bytemuck::cast_slice(&identity), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); - match sky_texture { - Ok(sky_texture) => { - self.sky_pipeline = Some(SkyPipeline::new(graphics.clone(), sky_texture)); - } - Err(e) => { - error!("Failed to load sky texture: {}", e); + let morph_deltas_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("editor default morph deltas buffer"), + contents: bytemuck::cast_slice(&[0.0f32]), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let morph_weights = vec![0.0f32; MAX_MORPH_WEIGHTS]; + let morph_weights_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("editor default morph weights buffer"), + contents: bytemuck::cast_slice(&morph_weights), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let morph_info_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("editor default morph info buffer"), + contents: bytemuck::cast_slice(&[0u32; 4]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + self.default_skinning_buffer = Some(skinning_buffer); + self.default_morph_deltas_buffer = Some(morph_deltas_buffer); + self.default_morph_weights_buffer = Some(morph_weights_buffer); + self.default_morph_info_buffer = Some(morph_info_buffer); + + let skinning_buffer = self + .default_skinning_buffer + .as_ref() + .expect("Default skinning buffer missing"); + let morph_deltas_buffer = self + .default_morph_deltas_buffer + .as_ref() + .expect("Default morph deltas buffer missing"); + let morph_weights_buffer = self + .default_morph_weights_buffer + .as_ref() + .expect("Default morph weights buffer missing"); + let morph_info_buffer = self + .default_morph_info_buffer + .as_ref() + .expect("Default morph info buffer missing"); + + self.default_animation_bind_group = Some(graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("editor default animation bind group"), + layout: &graphics.layouts.animation_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: skinning_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: morph_deltas_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: morph_weights_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: morph_info_buffer.as_entire_binding(), + }, + ], + })); + + if let Some(main_pipeline) = self.main_render_pipeline.as_mut() { + if let (Some(globals), Some(light_pipeline)) = ( + self.shader_globals.as_ref(), + self.light_cube_pipeline.as_ref(), + ) { + let _ = main_pipeline.per_frame_bind_group( + graphics.clone(), + globals.buffer.buffer(), + camera.buffer(), + light_pipeline.light_buffer(), + ); + } + } + + let sky_texture_result = HdrLoader::from_equirectangular_bytes( + &graphics.device, + &graphics.queue, + skybox_texture.map_or(DEFAULT_SKY_TEXTURE, |v| v.as_slice()), + 1080, + Some("sky texture"), + ); + + match sky_texture_result { + Ok(sky_texture) => { + pending_sky_pipeline = Some(SkyPipeline::new( + graphics.clone(), + sky_texture, + camera.buffer(), + )); + } + Err(e) => { + error!("Failed to load sky texture: {}", e); + } + } + } else { + error!("Unable to create editor bind groups without an active camera component"); } + } else { + error!("Unable to create editor bind groups without an active camera"); + } + + if let Some(sky_pipeline) = pending_sky_pipeline { + self.sky_pipeline = Some(sky_pipeline); } } @@ -26,7 +26,6 @@ use std::{ fs, path::{Path, PathBuf}, }; -use wgpu::util::DeviceExt; use winit::{event::WindowEvent, event_loop::ActiveEventLoop, keyboard::KeyCode}; use winit::event::{MouseScrollDelta, TouchPhase}; @@ -354,6 +353,11 @@ impl Scene for Editor { }; log_once::debug_once!("Pipeline ready"); + let Some(camera_bind_group) = self.camera_bind_group.as_ref() else { + log_once::warn_once!("Camera bind group not ready"); + return; + }; + { puffin::profile_scope!("Clearing viewport"); let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -410,10 +414,11 @@ impl Scene for Editor { } let mut static_batches: HashMap<u64, Vec<InstanceRaw>> = HashMap::new(); - let mut animated_instances: Vec<(u64, InstanceRaw, wgpu::Buffer)> = Vec::new(); + let mut animated_instances: Vec<(u64, InstanceRaw, Vec<Mat4>, Vec<f32>, u32, u32)> = Vec::new(); { puffin::profile_scope!("Locating all renderers and animation components"); + let registry = ASSET_REGISTRY.read(); let mut query = self .world .query::<(&MeshRenderer, Option<&AnimationComponent>)>(); @@ -425,8 +430,51 @@ impl Scene for Editor { } let instance = renderer.instance.to_raw(); - if let Some(buffer) = animation.and_then(|anim| anim.bone_buffer.clone()) { - animated_instances.push((handle.id, instance, buffer)); + if let Some(animation) = animation { + if !animation.skinning_matrices.is_empty() { + let morph_weights = if animation.morph_weights.is_empty() { + Vec::new() + } else { + let mut sorted_nodes: Vec<usize> = + animation.morph_weights.keys().cloned().collect(); + sorted_nodes.sort(); + let mut flat = Vec::new(); + for node_idx in &sorted_nodes { + if let Some(weights) = animation.morph_weights.get(node_idx) { + flat.extend_from_slice(weights); + } + } + flat + }; + + let num_targets = animation + .morph_weights + .values() + .next() + .map(|weights| weights.len() as u32) + .unwrap_or(0); + let num_vertices = registry + .get_model(handle) + .map(|model| { + model + .meshes + .iter() + .map(|mesh| mesh.vertices.len()) + .sum::<usize>() as u32 + }) + .unwrap_or(0); + + animated_instances.push(( + handle.id, + instance, + animation.skinning_matrices.clone(), + morph_weights, + num_vertices, + num_targets, + )); + } else { + static_batches.entry(handle.id).or_default().push(instance); + } } else { static_batches.entry(handle.id).or_default().push(instance); } @@ -495,78 +543,54 @@ impl Scene for Editor { continue; }; - render_pass.draw_light_model(model, &camera.bind_group, &light.bind_group); + render_pass.draw_light_model(model, camera_bind_group, light_pipeline.bind_group()); } } } - if self.default_skinning_bind_group.is_none() { - let identity = [Mat4::IDENTITY.to_cols_array_2d()]; - let buffer = graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("default skinning buffer"), - contents: bytemuck::cast_slice(&identity), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - }); - - let bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("default skinning bind group"), - layout: &graphics.layouts.skinning_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }); - - self.default_skinning_buffer = Some(buffer); - self.default_skinning_bind_group = Some(bind_group); - } - let default_skinning_buffer = self .default_skinning_buffer .as_ref() .expect("Default skinning buffer not initialized"); + let default_animation_bind_group = self + .default_animation_bind_group + .as_ref() + .expect("Default animation bind group not initialized"); + let default_morph_weights_buffer = self + .default_morph_weights_buffer + .as_ref() + .expect("Default morph weights buffer not initialized"); + let default_morph_info_buffer = self + .default_morph_info_buffer + .as_ref() + .expect("Default morph info buffer not initialized"); + + graphics.queue.write_buffer( + default_skinning_buffer, + 0, + bytemuck::cast_slice(&[Mat4::IDENTITY]), + ); + graphics.queue.write_buffer( + default_morph_info_buffer, + 0, + bytemuck::cast_slice(&[0u32; 4]), + ); // model rendering - let sky = self.sky_pipeline.as_ref().expect("Sky pipeline must be initialized before rendering models"); - let environment_bind_group = &sky.bind_group; + let sky = self + .sky_pipeline + .as_ref() + .expect("Sky pipeline must be initialized before rendering models"); + let environment_bind_group = &sky.environment_bind_group; - let globals = self.shader_globals.as_ref().expect("Shader globals not initialised"); - if let Some(scene_globals) = &mut self.scene_globals_bind_group { - scene_globals.update(&graphics, &globals.buffer, camera.buffer()); - } else { - self.scene_globals_bind_group = Some(dropbear_engine::bind_groups::SceneGlobalsBindGroup::new( - &graphics, - &globals.buffer, - camera.buffer(), - )); - } - let scene_globals_bind_group = self.scene_globals_bind_group.as_ref().unwrap(); + let per_frame_bind_group = pipeline + .per_frame + .as_ref() + .expect("Per-frame bind group not initialized"); - if let Some(lcp) = &self.light_cube_pipeline { + if let Some(_) = &self.light_cube_pipeline { puffin::profile_scope!("model render pass"); for (model, handle, instance_count) in prepared_models { - let light_skin_bind_group = - graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene light+skin bind group"), - layout: &graphics.layouts.scene_light_skin_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: lcp.light_buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: default_skinning_buffer.as_entire_binding(), - }, - ], - }); - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("model render pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { @@ -599,16 +623,16 @@ impl Scene for Editor { render_pass.draw_model_instanced( model, 0..instance_count, - scene_globals_bind_group.as_ref(), - &light_skin_bind_group, + per_frame_bind_group, + default_animation_bind_group, environment_bind_group, ); } } - if let Some(lcp) = &self.light_cube_pipeline { + if let Some(_lcp) = &self.light_cube_pipeline { puffin::profile_scope!("animated model render pass"); - for (handle, instance, skin_buffer) in animated_instances { + for (handle, instance, skinning_matrices, morph_weights, num_vertices, num_targets) in animated_instances { let Some(model) = registry.get_model(Handle::new(handle)) else { log_once::error_once!("Missing model handle {} in registry", handle); continue; @@ -649,30 +673,42 @@ impl Scene for Editor { render_pass.set_pipeline(pipeline.pipeline()); render_pass.set_vertex_buffer(1, instance_buffer.slice(1)); - if self.light_skin_bind_group.is_none() { - self.light_skin_bind_group = Some(graphics.device.create_bind_group( - &wgpu::BindGroupDescriptor { - label: Some("scene light+skin bind group"), - layout: &graphics.layouts.scene_light_skin_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: lcp.light_buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: skin_buffer.as_entire_binding(), - }, - ], - }, - )); + + let max_skinning_matrices = 256usize; + let matrices = if skinning_matrices.len() > max_skinning_matrices { + &skinning_matrices[..max_skinning_matrices] + } else { + &skinning_matrices[..] + }; + graphics.queue.write_buffer( + default_skinning_buffer, + 0, + bytemuck::cast_slice(matrices), + ); + + let weights = if morph_weights.len() > MAX_MORPH_WEIGHTS { + &morph_weights[..MAX_MORPH_WEIGHTS] + } else { + &morph_weights[..] + }; + if !weights.is_empty() { + graphics.queue.write_buffer( + default_morph_weights_buffer, + 0, + bytemuck::cast_slice(weights), + ); } + graphics.queue.write_buffer( + default_morph_info_buffer, + 0, + bytemuck::cast_slice(&[num_vertices, num_targets, 0, 0]), + ); render_pass.draw_model_instanced( model, 0..1, - scene_globals_bind_group.as_ref(), - &self.light_skin_bind_group.as_ref().unwrap(), // safe to do so because of check above + per_frame_bind_group, + default_animation_bind_group, environment_bind_group, ); } @@ -704,8 +740,8 @@ impl Scene for Editor { }); render_pass.set_pipeline(&sky.pipeline); - render_pass.set_bind_group(0, &camera.bind_group, &[]); - render_pass.set_bind_group(1, &sky.bind_group, &[]); + render_pass.set_bind_group(0, &sky.camera_bind_group, &[]); + render_pass.set_bind_group(1, &sky.environment_bind_group, &[]); render_pass.draw(0..3, 0..1); } @@ -751,7 +787,7 @@ impl Scene for Editor { }); render_pass.set_pipeline(&collider_pipeline.pipeline); - render_pass.set_bind_group(0, &camera.bind_group, &[]); + render_pass.set_bind_group(0, camera_bind_group, &[]); let mut instances_by_shape: HashMap< ColliderShapeKey, @@ -9,13 +9,7 @@ use eucalyptus_core::{fatal, info, success, success_without_console, warn, warn_ use std::fs; use std::path::PathBuf; use std::sync::Arc; -use log::error; use winit::keyboard::KeyCode; -use dropbear_engine::pipelines::GlobalsUniform; -use dropbear_engine::pipelines::light_cube::LightCubePipeline; -use dropbear_engine::pipelines::shader::MainRenderPipeline; -use dropbear_engine::sky::{HdrLoader, SkyPipeline, DEFAULT_SKY_TEXTURE}; -use eucalyptus_core::physics::collider::shader::ColliderWireframePipeline; pub trait SignalController { fn run_signal(&mut self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<()>; @@ -2,6 +2,7 @@ use crossbeam_channel::{Receiver, unbounded}; use dropbear_engine::buffer::ResizableBuffer; +use dropbear_engine::camera::Camera; use dropbear_engine::future::{FutureHandle, FutureQueue}; use dropbear_engine::graphics::{InstanceRaw, SharedGraphicsContext}; use dropbear_engine::pipelines::DropbearShaderPipeline; @@ -27,6 +28,7 @@ use eucalyptus_core::scene::loading::{SCENE_LOADER, SceneLoadResult}; use eucalyptus_core::scripting::{ScriptManager, ScriptTarget}; use eucalyptus_core::states::{SCENES, Script, WorldLoadingStatus}; use futures::executor; +use glam::Mat4; use hecs::{Entity, World}; use kino_ui::KinoState; use kino_ui::rendering::KinoWGPURenderer; @@ -36,12 +38,15 @@ use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use wgpu::SurfaceConfiguration; +use wgpu::util::DeviceExt; use winit::window::Fullscreen; mod command; mod input; mod scene; +const MAX_MORPH_WEIGHTS: usize = 4096; + #[cfg(feature = "debug")] fn find_jvm_library_path() -> PathBuf { let proj = eucalyptus_core::states::PROJECT.read(); @@ -134,10 +139,12 @@ pub struct PlayMode { animated_instance_buffer: Option<ResizableBuffer<InstanceRaw>>, collider_wireframe_pipeline: Option<ColliderWireframePipeline>, sky_pipeline: Option<SkyPipeline>, + camera_bind_group: Option<wgpu::BindGroup>, default_skinning_buffer: Option<wgpu::Buffer>, - default_skinning_bind_group: Option<wgpu::BindGroup>, - light_skin_bind_group: Option<wgpu::BindGroup>, - scene_globals_bind_group: Option<dropbear_engine::bind_groups::SceneGlobalsBindGroup>, + default_morph_deltas_buffer: Option<wgpu::Buffer>, + default_morph_weights_buffer: Option<wgpu::Buffer>, + default_morph_info_buffer: Option<wgpu::Buffer>, + default_animation_bind_group: Option<wgpu::BindGroup>, initial_scene: Option<String>, current_scene: Option<String>, @@ -226,10 +233,12 @@ impl PlayMode { }, kino: None, sky_pipeline: None, + camera_bind_group: None, default_skinning_buffer: None, - default_skinning_bind_group: None, - light_skin_bind_group: None, - scene_globals_bind_group: None, + default_morph_deltas_buffer: None, + default_morph_weights_buffer: None, + default_morph_info_buffer: None, + default_animation_bind_group: None, }; log::debug!("Created new play mode instance"); @@ -244,6 +253,12 @@ impl PlayMode { self.collider_wireframe_pipeline = None; self.kino = None; self.sky_pipeline = None; + self.camera_bind_group = None; + self.default_skinning_buffer = None; + self.default_morph_deltas_buffer = None; + self.default_morph_weights_buffer = None; + self.default_morph_info_buffer = None; + self.default_animation_bind_group = None; self.load_wgpu_nerdy_stuff(graphics, sky_texture); } @@ -257,6 +272,85 @@ impl PlayMode { )); self.collider_wireframe_pipeline = Some(ColliderWireframePipeline::new(graphics.clone())); + let mut camera_bind_group = None; + let mut pending_sky_pipeline = None; + + if self.default_skinning_buffer.is_none() { + let max_skinning_matrices = 256usize; + let identity = vec![Mat4::IDENTITY; max_skinning_matrices]; + let skinning_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("runtime default skinning buffer"), + contents: bytemuck::cast_slice(&identity), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let morph_deltas_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("runtime default morph deltas buffer"), + contents: bytemuck::cast_slice(&[0.0f32]), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let morph_weights = vec![0.0f32; MAX_MORPH_WEIGHTS]; + let morph_weights_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("runtime default morph weights buffer"), + contents: bytemuck::cast_slice(&morph_weights), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let morph_info_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("runtime default morph info buffer"), + contents: bytemuck::cast_slice(&[0u32; 4]), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + self.default_skinning_buffer = Some(skinning_buffer); + self.default_morph_deltas_buffer = Some(morph_deltas_buffer); + self.default_morph_weights_buffer = Some(morph_weights_buffer); + self.default_morph_info_buffer = Some(morph_info_buffer); + } + + if self.default_animation_bind_group.is_none() { + let skinning_buffer = self + .default_skinning_buffer + .as_ref() + .expect("Default skinning buffer missing"); + let morph_deltas_buffer = self + .default_morph_deltas_buffer + .as_ref() + .expect("Default morph deltas buffer missing"); + let morph_weights_buffer = self + .default_morph_weights_buffer + .as_ref() + .expect("Default morph weights buffer missing"); + let morph_info_buffer = self + .default_morph_info_buffer + .as_ref() + .expect("Default morph info buffer missing"); + + self.default_animation_bind_group = Some(graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("runtime default animation bind group"), + layout: &graphics.layouts.animation_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: skinning_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: morph_deltas_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: morph_weights_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: morph_info_buffer.as_entire_binding(), + }, + ], + })); + } + self.kino = Some(KinoState::new( KinoWGPURenderer::new( &graphics.device, @@ -270,7 +364,7 @@ impl PlayMode { KinoWinitWindowing::new(graphics.window.clone(), None), )); - let sky_texture = HdrLoader::from_equirectangular_bytes( + let sky_texture_result = HdrLoader::from_equirectangular_bytes( &graphics.device, &graphics.queue, sky_texture.map_or(DEFAULT_SKY_TEXTURE, |v| v.as_slice()), @@ -278,13 +372,52 @@ impl PlayMode { Some("sky texture"), ); - match sky_texture { - Ok(sky_texture) => { - self.sky_pipeline = Some(SkyPipeline::new(graphics.clone(), sky_texture)); - } - Err(e) => { - error!("Failed to load sky texture: {}", e); + if let Some(camera_entity) = self.active_camera { + if let Ok(camera) = self.world.query_one::<&Camera>(camera_entity).get() { + camera_bind_group = Some(graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("runtime camera bind group"), + layout: &graphics.layouts.camera_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera.buffer().as_entire_binding(), + }], + })); + + match sky_texture_result { + Ok(sky_texture) => { + pending_sky_pipeline = Some(SkyPipeline::new( + graphics.clone(), + sky_texture, + camera.buffer(), + )); + } + Err(e) => { + error!("Failed to load sky texture: {}", e); + } + } + + if let (Some(main_pipeline), Some(globals), Some(light_pipeline)) = ( + self.main_pipeline.as_mut(), + self.shader_globals.as_ref(), + self.light_cube_pipeline.as_ref(), + ) { + let _ = main_pipeline.per_frame_bind_group( + graphics.clone(), + globals.buffer.buffer(), + camera.buffer(), + light_pipeline.light_buffer(), + ); + } + } else { + error!("Unable to create bind groups without an active camera component"); } + } else { + error!("Unable to create bind groups without an active camera"); + } + + self.camera_bind_group = camera_bind_group; + if let Some(sky_pipeline) = pending_sky_pipeline { + self.sky_pipeline = Some(sky_pipeline); } } @@ -1,6 +1,7 @@ use std::sync::Arc; use crate::PlayMode; +use crate::MAX_MORPH_WEIGHTS; use dropbear_engine::animation::AnimationComponent; use dropbear_engine::asset::{ASSET_REGISTRY, Handle}; use dropbear_engine::buffer::ResizableBuffer; @@ -30,7 +31,6 @@ use hecs::Entity; // use kino_ui::widgets::rect::Rectangle; // use kino_ui::widgets::{Border, Fill}; use std::collections::HashMap; -use wgpu::util::DeviceExt; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; @@ -656,6 +656,11 @@ impl Scene for PlayMode { }; log_once::debug_once!("Pipeline ready"); + let Some(camera_bind_group) = self.camera_bind_group.as_ref() else { + log_once::warn_once!("Camera bind group not ready"); + return; + }; + { let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("viewport clear pass"), @@ -709,9 +714,10 @@ impl Scene for PlayMode { } let mut static_batches: HashMap<u64, Vec<InstanceRaw>> = HashMap::new(); - let mut animated_instances: Vec<(u64, InstanceRaw, wgpu::Buffer)> = Vec::new(); + let mut animated_instances: Vec<(u64, InstanceRaw, Vec<Mat4>, Vec<f32>, u32, u32)> = Vec::new(); { + let registry = ASSET_REGISTRY.read(); let mut query = self .world .query::<(&MeshRenderer, Option<&AnimationComponent>)>(); @@ -723,8 +729,51 @@ impl Scene for PlayMode { } let instance = renderer.instance.to_raw(); - if let Some(buffer) = animation.and_then(|anim| anim.bone_buffer.clone()) { - animated_instances.push((handle.id, instance, buffer)); + if let Some(animation) = animation { + if !animation.skinning_matrices.is_empty() { + let morph_weights = if animation.morph_weights.is_empty() { + Vec::new() + } else { + let mut sorted_nodes: Vec<usize> = + animation.morph_weights.keys().cloned().collect(); + sorted_nodes.sort(); + let mut flat = Vec::new(); + for node_idx in &sorted_nodes { + if let Some(weights) = animation.morph_weights.get(node_idx) { + flat.extend_from_slice(weights); + } + } + flat + }; + + let num_targets = animation + .morph_weights + .values() + .next() + .map(|weights| weights.len() as u32) + .unwrap_or(0); + let num_vertices = registry + .get_model(handle) + .map(|model| { + model + .meshes + .iter() + .map(|mesh| mesh.vertices.len()) + .sum::<usize>() as u32 + }) + .unwrap_or(0); + + animated_instances.push(( + handle.id, + instance, + animation.skinning_matrices.clone(), + morph_weights, + num_vertices, + num_targets, + )); + } else { + static_batches.entry(handle.id).or_default().push(instance); + } } else { static_batches.entry(handle.id).or_default().push(instance); } @@ -792,118 +841,92 @@ impl Scene for PlayMode { continue; }; - render_pass.draw_light_model(model, &camera.bind_group, &light.bind_group); + render_pass.draw_light_model(model, camera_bind_group, light_pipeline.bind_group()); } } } - if self.default_skinning_bind_group.is_none() { - let identity = [Mat4::IDENTITY.to_cols_array_2d()]; - let buffer = graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("default skinning buffer"), - contents: bytemuck::cast_slice(&identity), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - }); - - let bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("default skinning bind group"), - layout: &graphics.layouts.skinning_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - }); - - self.default_skinning_buffer = Some(buffer); - self.default_skinning_bind_group = Some(bind_group); - } - let default_skinning_buffer = self .default_skinning_buffer .as_ref() - .expect("Default skinning buffer not initialized"); + .expect("Default skinning buffer not initialised"); + let default_animation_bind_group = self + .default_animation_bind_group + .as_ref() + .expect("Default animation bind group not initialized"); + let default_morph_weights_buffer = self + .default_morph_weights_buffer + .as_ref() + .expect("Default morph weights buffer not initialised"); + let default_morph_info_buffer = self + .default_morph_info_buffer + .as_ref() + .expect("Default morph info buffer not initialised"); + + graphics.queue.write_buffer( + default_skinning_buffer, + 0, + bytemuck::cast_slice(&[Mat4::IDENTITY]), + ); + graphics.queue.write_buffer( + default_morph_info_buffer, + 0, + bytemuck::cast_slice(&[0u32; 4]), + ); // model rendering - let sky = self.sky_pipeline.as_ref().expect("Sky pipeline must be initialized before rendering models"); - let environment_bind_group = &sky.bind_group; - - let globals = self.shader_globals.as_ref().expect("Shader globals not initialised"); - if let Some(scene_globals) = &mut self.scene_globals_bind_group { - scene_globals.update(&graphics, &globals.buffer, camera.buffer()); - } else { - self.scene_globals_bind_group = Some(dropbear_engine::bind_groups::SceneGlobalsBindGroup::new( - &graphics, - &globals.buffer, - camera.buffer(), - )); - } - let scene_globals_bind_group = self.scene_globals_bind_group.as_ref().unwrap(); - - if let Some(lcp) = &self.light_cube_pipeline { - for (model, handle, instance_count) in prepared_models { - let light_skin_bind_group = - graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene light+skin bind group"), - layout: &graphics.layouts.scene_light_skin_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: lcp.light_buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: default_skinning_buffer.as_entire_binding(), - }, - ], - }); + let sky = self + .sky_pipeline + .as_ref() + .expect("Sky pipeline must be initialized before rendering models"); + let environment_bind_group = &sky.environment_bind_group; - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("model render pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: hdr.render_view(), - depth_slice: None, - resolve_target: hdr.resolve_target(), - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &graphics.depth_texture.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }), - stencil_ops: None, + let per_frame_bind_group = pipeline + .per_frame + .as_ref() + .expect("Per-frame bind group not initialized"); + + for (model, handle, instance_count) in prepared_models { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("model render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: hdr.render_view(), + depth_slice: None, + resolve_target: hdr.resolve_target(), + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &graphics.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, }), - occlusion_query_set: None, - timestamp_writes: None, - }); - render_pass.set_pipeline(pipeline.pipeline()); - if let Some(instance_buffer) = self.instance_buffer_cache.get(&handle) { - render_pass - .set_vertex_buffer(1, instance_buffer.slice(instance_count as usize)); - } else { - continue; - } - render_pass.draw_model_instanced( - model, - 0..instance_count, - scene_globals_bind_group.as_ref(), - &light_skin_bind_group, - environment_bind_group, - ); + stencil_ops: None, + }), + occlusion_query_set: None, + timestamp_writes: None, + }); + render_pass.set_pipeline(pipeline.pipeline()); + if let Some(instance_buffer) = self.instance_buffer_cache.get(&handle) { + render_pass + .set_vertex_buffer(1, instance_buffer.slice(instance_count as usize)); + } else { + continue; } + render_pass.draw_model_instanced( + model, + 0..instance_count, + per_frame_bind_group, + default_animation_bind_group, + environment_bind_group, + ); } - if let Some(lcp) = &self.light_cube_pipeline { - for (handle, instance, skin_buffer) in animated_instances { + if let Some(_lcp) = &self.light_cube_pipeline { + for (handle, instance, skinning_matrices, morph_weights, num_vertices, num_targets) in animated_instances { let Some(model) = registry.get_model(Handle::new(handle)) else { log_once::error_once!("Missing model handle {} in registry", handle); continue; @@ -944,30 +967,42 @@ impl Scene for PlayMode { render_pass.set_pipeline(pipeline.pipeline()); render_pass.set_vertex_buffer(1, instance_buffer.slice(1)); - if self.light_skin_bind_group.is_none() { - self.light_skin_bind_group = Some(graphics.device.create_bind_group( - &wgpu::BindGroupDescriptor { - label: Some("scene light+skin bind group"), - layout: &graphics.layouts.scene_light_skin_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: lcp.light_buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: skin_buffer.as_entire_binding(), - }, - ], - }, - )); + + let max_skinning_matrices = 256usize; + let matrices = if skinning_matrices.len() > max_skinning_matrices { + &skinning_matrices[..max_skinning_matrices] + } else { + &skinning_matrices[..] + }; + graphics.queue.write_buffer( + default_skinning_buffer, + 0, + bytemuck::cast_slice(matrices), + ); + + let weights = if morph_weights.len() > MAX_MORPH_WEIGHTS { + &morph_weights[..MAX_MORPH_WEIGHTS] + } else { + &morph_weights[..] + }; + if !weights.is_empty() { + graphics.queue.write_buffer( + default_morph_weights_buffer, + 0, + bytemuck::cast_slice(weights), + ); } + graphics.queue.write_buffer( + default_morph_info_buffer, + 0, + bytemuck::cast_slice(&[num_vertices, num_targets, 0, 0]), + ); render_pass.draw_model_instanced( model, 0..1, - scene_globals_bind_group.as_ref(), - &self.light_skin_bind_group.as_ref().unwrap(), // safe to do so because of check above + per_frame_bind_group, + default_animation_bind_group, environment_bind_group, ); } @@ -998,8 +1033,8 @@ impl Scene for PlayMode { }); render_pass.set_pipeline(&sky.pipeline); - render_pass.set_bind_group(0, &camera.bind_group, &[]); - render_pass.set_bind_group(1, &sky.bind_group, &[]); + render_pass.set_bind_group(0, &sky.camera_bind_group, &[]); + render_pass.set_bind_group(1, &sky.environment_bind_group, &[]); render_pass.draw(0..3, 0..1); } @@ -1042,7 +1077,7 @@ impl Scene for PlayMode { }); render_pass.set_pipeline(&collider_pipeline.pipeline); - render_pass.set_bind_group(0, &camera.bind_group, &[]); + render_pass.set_bind_group(0, camera_bind_group, &[]); let mut instances_by_shape: HashMap< ColliderShapeKey, @@ -115,10 +115,17 @@ typedef struct NQuaternionArray { size_t capacity; } NQuaternionArray; +typedef struct f64ArrayArray { + double* values; + size_t length; + size_t capacity; +} f64ArrayArray; + typedef enum NChannelValuesTag { NChannelValuesTag_Translations = 0, NChannelValuesTag_Rotations = 1, NChannelValuesTag_Scales = 2, + NChannelValuesTag_MorphWeights = 3, } NChannelValuesTag; typedef struct NChannelValuesTranslations { @@ -133,10 +140,15 @@ typedef struct NChannelValuesScales { NVector3Array values; } NChannelValuesScales; +typedef struct NChannelValuesMorphWeights { + f64ArrayArray values; +} NChannelValuesMorphWeights; + typedef union NChannelValuesData { NChannelValuesTranslations Translations; NChannelValuesRotations Rotations; NChannelValuesScales Scales; + NChannelValuesMorphWeights MorphWeights; } NChannelValuesData; typedef struct NChannelValuesFfi { @@ -387,12 +399,6 @@ typedef struct NShapeCastHit { NShapeCastStatus status; } NShapeCastHit; -typedef struct f64ArrayArray { - double* values; - size_t length; - size_t capacity; -} f64ArrayArray; - typedef struct NSkin { const char* name; i32Array joints;