use crate::camera::Camera;
use crate::draw2d::Color;
use crate::gpu::GpuContext;
use crate::mesh::{Mesh, Transform, Vertex3d};
use crate::texture::Texture;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct CameraUniforms {
pub view_proj: [[f32; 4]; 4],
pub view: [[f32; 4]; 4],
pub proj: [[f32; 4]; 4],
pub camera_pos: [f32; 3],
pub time: f32,
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct InstanceData {
pub model: [[f32; 4]; 4],
pub normal_matrix: [[f32; 4]; 4],
pub color: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ModelUniforms {
pub model: [[f32; 4]; 4],
pub normal_matrix: [[f32; 4]; 4],
pub color: [f32; 4],
}
pub struct DrawCall<'a> {
pub mesh: &'a Mesh,
pub transform: Transform,
pub color: Color,
pub texture: Option<&'a Texture>,
}
const MAX_INSTANCES: usize = 4096;
pub struct MeshPass {
pipeline: wgpu::RenderPipeline,
camera_buffer: wgpu::Buffer,
camera_bind_group: wgpu::BindGroup,
instance_buffer: wgpu::Buffer,
#[allow(dead_code)]
instance_bind_group_layout: wgpu::BindGroupLayout,
instance_bind_group: wgpu::BindGroup,
pub(crate) depth_texture: wgpu::Texture,
pub(crate) depth_view: wgpu::TextureView,
depth_size: (u32, u32),
blit_pipeline: wgpu::RenderPipeline,
blit_bind_group_layout: wgpu::BindGroupLayout,
blit_sampler: wgpu::Sampler,
texture_bind_group_layout: wgpu::BindGroupLayout,
default_texture: Texture,
}
impl MeshPass {
pub fn new(gpu: &GpuContext) -> Self {
let device = &gpu.device;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Mesh Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shaders/mesh.wgsl").into()),
});
let camera_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Camera Uniforms"),
size: std::mem::size_of::<CameraUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let camera_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("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 camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Camera Bind Group"),
layout: &camera_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: camera_buffer.as_entire_binding(),
}],
});
let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Instance Storage Buffer"),
size: (std::mem::size_of::<InstanceData>() * MAX_INSTANCES) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let instance_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Instance Bind Group Layout"),
entries: &[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,
}],
});
let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Instance Bind Group"),
layout: &instance_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: instance_buffer.as_entire_binding(),
}],
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Texture Bind Group Layout"),
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 =
Texture::from_rgba(gpu, &[255, 255, 255, 255], 1, 1, "Default White Texture");
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Mesh Pipeline Layout"),
bind_group_layouts: &[
&camera_bind_group_layout,
&instance_bind_group_layout,
&texture_bind_group_layout,
],
push_constant_ranges: &[],
});
let (depth_texture, depth_view) = Self::create_depth_texture(gpu);
let blit_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Blit Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("shaders/blit.wgsl").into()),
});
let blit_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Blit Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let blit_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Blit Bind Group Layout"),
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 blit_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Blit Pipeline Layout"),
bind_group_layouts: &[&blit_bind_group_layout],
push_constant_ranges: &[],
});
let blit_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Blit Pipeline"),
layout: Some(&blit_pipeline_layout),
vertex: wgpu::VertexState {
module: &blit_shader,
entry_point: Some("vs"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &blit_shader,
entry_point: Some("fs"),
targets: &[Some(wgpu::ColorTargetState {
format: gpu.config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Mesh Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs"),
buffers: &[Vertex3d::LAYOUT],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs"),
targets: &[Some(wgpu::ColorTargetState {
format: gpu.config.format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: Some(wgpu::Face::Back),
front_face: wgpu::FrontFace::Ccw,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
Self {
pipeline,
camera_buffer,
camera_bind_group,
instance_buffer,
instance_bind_group_layout,
instance_bind_group,
depth_texture,
depth_view,
depth_size: (gpu.width(), gpu.height()),
blit_pipeline,
blit_bind_group_layout,
blit_sampler,
texture_bind_group_layout,
default_texture,
}
}
pub fn create_texture_bind_group(
&self,
gpu: &GpuContext,
texture: &Texture,
) -> wgpu::BindGroup {
gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Mesh Texture Bind Group"),
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 create_depth_texture(gpu: &GpuContext) -> (wgpu::Texture, wgpu::TextureView) {
let texture = gpu.device.create_texture(&wgpu::TextureDescriptor {
label: Some("Depth Texture"),
size: wgpu::Extent3d {
width: gpu.width(),
height: gpu.height(),
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
(texture, view)
}
pub fn ensure_depth_size(&mut self, gpu: &GpuContext) {
if self.depth_size != (gpu.width(), gpu.height()) {
let (texture, view) = Self::create_depth_texture(gpu);
self.depth_texture = texture;
self.depth_view = view;
self.depth_size = (gpu.width(), gpu.height());
}
}
pub fn blit(
&self,
gpu: &GpuContext,
render_pass: &mut wgpu::RenderPass,
input_view: &wgpu::TextureView,
) {
let bind_group = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Blit Bind Group"),
layout: &self.blit_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(input_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&self.blit_sampler),
},
],
});
render_pass.set_pipeline(&self.blit_pipeline);
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.draw(0..3, 0..1);
}
pub fn render(
&self,
gpu: &GpuContext,
render_pass: &mut wgpu::RenderPass,
camera: &Camera,
time: f32,
draw_calls: &[DrawCall],
) {
if draw_calls.is_empty() {
return;
}
let view = camera.view_matrix();
let proj = camera.projection_matrix(gpu.aspect(), 0.1, 1000.0);
let view_proj = proj * view;
let camera_uniforms = CameraUniforms {
view_proj: view_proj.to_cols_array_2d(),
view: view.to_cols_array_2d(),
proj: proj.to_cols_array_2d(),
camera_pos: camera.position.to_array(),
time,
};
gpu.queue.write_buffer(
&self.camera_buffer,
0,
bytemuck::cast_slice(&[camera_uniforms]),
);
use std::collections::HashMap;
type BatchKey = (*const Mesh, *const Texture);
let mut batches: HashMap<BatchKey, (&Mesh, &Texture, Vec<u32>)> = HashMap::new();
let mut instance_data: Vec<InstanceData> =
Vec::with_capacity(draw_calls.len().min(MAX_INSTANCES));
for call in draw_calls.iter().take(MAX_INSTANCES) {
let model_matrix = call.transform.matrix();
let normal_matrix = model_matrix.inverse().transpose();
let instance_idx = instance_data.len() as u32;
instance_data.push(InstanceData {
model: model_matrix.to_cols_array_2d(),
normal_matrix: normal_matrix.to_cols_array_2d(),
color: [call.color.r, call.color.g, call.color.b, call.color.a],
});
let texture = call.texture.unwrap_or(&self.default_texture);
let key: BatchKey = (call.mesh as *const Mesh, texture as *const Texture);
batches
.entry(key)
.or_insert_with(|| (call.mesh, texture, Vec::new()))
.2
.push(instance_idx);
}
if !instance_data.is_empty() {
gpu.queue.write_buffer(
&self.instance_buffer,
0,
bytemuck::cast_slice(&instance_data),
);
}
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
render_pass.set_bind_group(1, &self.instance_bind_group, &[]);
for (_key, (mesh, texture, indices)) in batches {
let texture_bind_group = self.create_texture_bind_group(gpu, texture);
render_pass.set_bind_group(2, &texture_bind_group, &[]);
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
render_pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
for &instance_idx in &indices {
render_pass.draw_indexed(0..mesh.index_count, 0, instance_idx..instance_idx + 1);
}
}
}
}