use std::borrow::Cow;
use glam::Mat4;
use crate::color::Color;
use crate::engine::Engine;
use crate::light3d::Material3D;
use crate::platform::types::TextureResource;
use super::geometry::DrawMode;
use super::mesh::Mesh3D;
use super::skinned::SkinnedMesh3D;
use super::vertex::Mesh3DVertex;
pub struct ResolvedDraw3DSkinned {
pub mesh: SkinnedMesh3D,
pub model: Mat4,
pub color: Color,
pub material: Material3D,
pub bone_matrices: Vec<Mat4>,
pub texture_bind_group: Option<wgpu::BindGroup>,
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Debug)]
struct Mesh3DUniform {
mvp: [[f32; 4]; 4],
model: [[f32; 4]; 4],
camera_pos: [f32; 4], diffuse: [f32; 4],
specular: [f32; 4],
emissive: [f32; 4],
ambient: [f32; 4],
light_dir: [f32; 4], light_color: [f32; 4],
light_pos: [f32; 4], light_params: [f32; 4], shininess: f32,
has_texture: f32,
_padding: [f32; 2],
}
const MAX_MESH3D_DRAWS: usize = 256;
pub struct Mesh3DShader {
pipeline_solid: wgpu::RenderPipeline,
pipeline_wireframe: wgpu::RenderPipeline,
pipeline_points: wgpu::RenderPipeline,
uniform: wgpu::Buffer,
uniform_alignment: u32,
#[allow(dead_code)]
uniform_bind_group_layout: wgpu::BindGroupLayout,
uniform_bind_group: wgpu::BindGroup,
texture_bind_group_layout: wgpu::BindGroupLayout,
#[allow(dead_code)]
default_texture: TextureResource,
default_texture_bind_group: wgpu::BindGroup,
texture_bind_group_cache: hashbrown::HashMap<usize, wgpu::BindGroup>,
}
impl Mesh3DShader {
pub fn new(g: &Engine) -> Self {
let state = g.backend_state();
let device = &state.device;
let format = state.surface_view_format;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("mesh3d.shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
"../../assets/shaders/mesh3d.wgsl"
))),
});
let uniform_alignment = device.limits().min_uniform_buffer_offset_alignment;
let uniform_size = std::mem::size_of::<Mesh3DUniform>() as u32;
let aligned_uniform_size = uniform_size.div_ceil(uniform_alignment) * uniform_alignment;
let uniform = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("mesh3d.uniform"),
size: (aligned_uniform_size as usize * MAX_MESH3D_DRAWS) as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("mesh3d.uniform_bgl"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: wgpu::BufferSize::new(uniform_size as u64),
},
count: None,
}],
});
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("mesh3d.uniform_bg"),
layout: &uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &uniform,
offset: 0,
size: wgpu::BufferSize::new(uniform_size as u64),
}),
}],
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("mesh3d.texture_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
});
let default_texture = Self::create_default_texture(device, &state.queue);
let default_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("mesh3d.default_texture_bg"),
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&default_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&default_texture.sampler),
},
],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("mesh3d.pl"),
bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
push_constant_ranges: &[],
});
let create_pipeline = |label: &str,
topology: wgpu::PrimitiveTopology,
polygon_mode: wgpu::PolygonMode,
cull_mode: Option<wgpu::Face>|
-> wgpu::RenderPipeline {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&pipeline_layout),
cache: None,
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Mesh3DVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x2],
}],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology,
cull_mode,
front_face: wgpu::FrontFace::Ccw,
polygon_mode,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Greater,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
})
};
let pipeline_solid = create_pipeline(
"mesh3d.pipeline.solid",
wgpu::PrimitiveTopology::TriangleList,
wgpu::PolygonMode::Fill,
Some(wgpu::Face::Back),
);
let pipeline_wireframe = create_pipeline(
"mesh3d.pipeline.wireframe",
wgpu::PrimitiveTopology::TriangleList,
wgpu::PolygonMode::Fill,
None, );
let pipeline_points = create_pipeline(
"mesh3d.pipeline.points",
wgpu::PrimitiveTopology::PointList,
wgpu::PolygonMode::Fill,
None, );
Self {
pipeline_solid,
pipeline_wireframe,
pipeline_points,
uniform,
uniform_alignment,
uniform_bind_group_layout,
uniform_bind_group,
texture_bind_group_layout,
default_texture,
default_texture_bind_group,
texture_bind_group_cache: hashbrown::HashMap::new(),
}
}
pub(super) fn create_default_texture(
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> TextureResource {
let size = wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("mesh3d.default_texture"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
&[255u8, 255, 255, 255],
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4),
rows_per_image: Some(1),
},
size,
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
..Default::default()
});
TextureResource {
texture,
view,
sampler,
}
}
pub fn frame_start(&mut self) {
self.texture_bind_group_cache.clear();
}
fn create_texture_bind_group(
&self,
device: &wgpu::Device,
texture: &TextureResource,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("mesh3d.texture_bg"),
layout: &self.texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&texture.sampler),
},
],
})
}
fn get_or_create_texture_bind_group(
&mut self,
device: &wgpu::Device,
texture: &TextureResource,
) {
let key = std::ptr::from_ref(&texture.texture) as usize;
if !self.texture_bind_group_cache.contains_key(&key) {
let bg = self.create_texture_bind_group(device, texture);
self.texture_bind_group_cache.insert(key, bg);
}
}
#[allow(clippy::type_complexity)]
#[allow(clippy::too_many_arguments)]
pub fn record_to_encoder(
&mut self,
g: &Engine,
color_target: &wgpu::Texture,
depth_view: &wgpu::TextureView,
view_proj: Mat4,
draws: &[(
&Mesh3D,
Mat4,
Option<&TextureResource>,
Color,
&crate::light3d::Material3D,
DrawMode,
)],
encoder: &mut wgpu::CommandEncoder,
should_clear: bool,
) {
let color_view = color_target.create_view(&wgpu::TextureViewDescriptor::default());
let state = g.backend_state();
let lighting = g.lighting();
let camera = g.camera3d();
let (light_dir, light_color, light_pos, light_params) = lighting
.lights
.iter()
.flatten()
.find(|l| l.enabled)
.map(|l| {
use crate::light3d::LightKind;
let (light_type, radius, inner_angle, outer_angle) = match l.kind {
LightKind::Directional => (0.0, 0.0, 0.0, 0.0),
LightKind::Point { radius } => (1.0, radius, 0.0, 0.0),
LightKind::Spot {
radius,
inner_angle,
outer_angle,
} => (2.0, radius, inner_angle, outer_angle),
};
(
l.direction,
l.color,
[l.position.x, l.position.y, l.position.z, light_type],
[radius, inner_angle, outer_angle, l.intensity],
)
})
.unwrap_or_else(|| {
(
glam::Vec3::new(0.5, -1.0, -0.5).normalize(),
Color::rgb(1.0, 1.0, 1.0),
[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0], )
});
let uniform_size = std::mem::size_of::<Mesh3DUniform>() as u32;
let aligned_uniform_size =
uniform_size.div_ceil(self.uniform_alignment) * self.uniform_alignment;
let draw_count = draws.len().min(MAX_MESH3D_DRAWS);
let mut uniform_data_buffer = vec![0u8; aligned_uniform_size as usize * draw_count];
for (i, (_, model, texture_res, color, material, _)) in
draws.iter().take(draw_count).enumerate()
{
let mvp = view_proj * *model;
let has_texture = if let Some(tex_res) = texture_res {
self.get_or_create_texture_bind_group(&state.device, tex_res);
1.0f32
} else {
0.0f32
};
let diffuse = Color::rgba(
material.diffuse.r * color.r,
material.diffuse.g * color.g,
material.diffuse.b * color.b,
material.diffuse.a * color.a,
);
let uniform_data = Mesh3DUniform {
mvp: mvp.to_cols_array_2d(),
model: model.to_cols_array_2d(),
camera_pos: [camera.eye.x, camera.eye.y, camera.eye.z, 0.0],
diffuse: [diffuse.r, diffuse.g, diffuse.b, diffuse.a],
specular: [
material.specular.r,
material.specular.g,
material.specular.b,
material.specular.a,
],
emissive: [
material.emissive.r,
material.emissive.g,
material.emissive.b,
material.emissive.a,
],
ambient: [
lighting.ambient.r,
lighting.ambient.g,
lighting.ambient.b,
lighting.ambient.a,
],
light_dir: [light_dir.x, light_dir.y, light_dir.z, 0.0],
light_color: [light_color.r, light_color.g, light_color.b, light_color.a],
light_pos,
light_params,
shininess: material.shininess,
has_texture,
_padding: [0.0; 2],
};
let offset = i * aligned_uniform_size as usize;
let uniform_bytes = bytemuck::bytes_of(&uniform_data);
uniform_data_buffer[offset..offset + uniform_bytes.len()]
.copy_from_slice(uniform_bytes);
}
state
.queue
.write_buffer(&self.uniform, 0, &uniform_data_buffer);
let (color_load, depth_load) = if should_clear {
(
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
wgpu::LoadOp::Clear(0.0),
)
} else {
(wgpu::LoadOp::Load, wgpu::LoadOp::Load)
};
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("mesh3d.pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &color_view,
resolve_target: None,
ops: wgpu::Operations {
load: color_load,
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth_view,
depth_ops: Some(wgpu::Operations {
load: depth_load,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
occlusion_query_set: None,
timestamp_writes: None,
});
let mut current_mode: Option<DrawMode> = None;
for (i, (mesh, _, texture_res, _, _, draw_mode)) in
draws.iter().take(draw_count).enumerate()
{
if current_mode != Some(*draw_mode) {
current_mode = Some(*draw_mode);
let pipeline = match draw_mode {
DrawMode::Solid => &self.pipeline_solid,
DrawMode::Wireframe => &self.pipeline_wireframe,
DrawMode::Points => &self.pipeline_points,
};
pass.set_pipeline(pipeline);
}
let dynamic_offset = (i as u32) * aligned_uniform_size;
pass.set_bind_group(0, &self.uniform_bind_group, &[dynamic_offset]);
if let Some(tex_res) = texture_res {
let key = std::ptr::from_ref(&tex_res.texture) as usize;
if let Some(bg) = self.texture_bind_group_cache.get(&key) {
pass.set_bind_group(1, bg, &[]);
} else {
pass.set_bind_group(1, &self.default_texture_bind_group, &[]);
}
} else {
pass.set_bind_group(1, &self.default_texture_bind_group, &[]);
}
pass.set_vertex_buffer(0, mesh.vertex.slice(..));
pass.set_index_buffer(mesh.index.slice(..), wgpu::IndexFormat::Uint16);
pass.draw_indexed(0..mesh.index_count, 0, 0..1);
}
}
}
}