You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
learn-wgpu/code/showcase/compute/src/model.rs

588 lines
20 KiB
Rust

use anyhow::*;
use rayon::prelude::*;
use std::ops::Range;
use std::path::Path;
use wgpu::util::DeviceExt;
use crate::pipeline;
use crate::texture;
pub trait Vertex {
fn desc() -> wgpu::VertexBufferLayout<'static>;
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ModelVertex {
position: [f32; 3],
tex_coords: [f32; 2],
normal: [f32; 3],
tangent: [f32; 3],
bitangent: [f32; 3],
padding: [u32; 2],
}
impl Vertex for ModelVertex {
fn desc() -> wgpu::VertexBufferLayout<'static> {
use std::mem;
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<ModelVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
// format: wgpu::VertexFormat::Float32x3,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
// offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
shader_location: 2,
format: wgpu::VertexFormat::Float32x3,
},
// Tangent and bitangent
wgpu::VertexAttribute {
// offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
// offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32x3,
},
],
}
}
}
pub struct Material {
pub name: String,
pub diffuse_texture: texture::Texture,
pub normal_texture: texture::Texture,
pub bind_group: wgpu::BindGroup,
}
impl Material {
pub fn new(
device: &wgpu::Device,
name: &str,
diffuse_texture: texture::Texture,
normal_texture: texture::Texture,
layout: &wgpu::BindGroupLayout,
) -> Self {
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&normal_texture.view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&normal_texture.sampler),
},
],
label: Some(name),
});
Self {
name: String::from(name),
diffuse_texture,
normal_texture,
bind_group,
}
}
}
pub struct Mesh {
pub name: String,
pub vertex_buffer: wgpu::Buffer,
pub index_buffer: wgpu::Buffer,
pub num_elements: u32,
pub material: usize,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ComputeInfo {
num_vertices: u32,
num_indices: u32,
}
struct BitangentComputeBinding {
src_vertex_buffer: wgpu::Buffer,
dst_vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer,
info_buffer: wgpu::Buffer,
compute_info: ComputeInfo,
}
impl pipeline::Bindable for BitangentComputeBinding {
fn layout_entries() -> Vec<wgpu::BindGroupLayoutEntry> {
vec![
// Src Vertices
// We use these vertices to compute the tangent and bitangent
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
// Dst Vertices
// We'll store the computed tangent and bitangent here
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage {
// We will change the values in this buffer
read_only: false,
},
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
// Indices
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage {
// We won't change the indices
read_only: true,
},
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
// ComputeInfo
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
]
}
fn bind_group_entries(&self) -> Vec<wgpu::BindGroupEntry> {
vec![
// Src Vertices
wgpu::BindGroupEntry {
binding: 0,
resource: self.src_vertex_buffer.as_entire_binding(),
},
// Dst Vertices
wgpu::BindGroupEntry {
binding: 1,
resource: self.dst_vertex_buffer.as_entire_binding(),
},
// Indices
wgpu::BindGroupEntry {
binding: 2,
resource: self.index_buffer.as_entire_binding(),
},
// ComputeInfo
wgpu::BindGroupEntry {
binding: 3,
resource: self.info_buffer.as_entire_binding(),
},
]
}
}
pub struct Model {
pub meshes: Vec<Mesh>,
pub materials: Vec<Material>,
}
pub struct ModelLoader {
binder: pipeline::Binder<BitangentComputeBinding>,
pipeline: wgpu::ComputePipeline,
}
// UPDATED!
impl ModelLoader {
// NEW!
pub fn new(device: &wgpu::Device) -> Self {
println!("Creating MODELLOADER pipeline");
let binder = pipeline::Binder::new(device, Some("ModelLoader Binder"));
let shader_src = wgpu::include_wgsl!("model_load.comp.wgsl");
let pipeline = pipeline::create_compute_pipeline(
device,
&[&binder.layout],
shader_src,
Some("ModelLoader ComputePipeline"),
);
Self { binder, pipeline }
}
// UPDATED!
pub fn load<P: AsRef<Path>>(
&self, // NEW!
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
path: P,
) -> Result<Model> {
let (obj_models, obj_materials) = tobj::load_obj(path.as_ref(), true)?;
// We're assuming that the texture files are stored with the obj file
let containing_folder = path.as_ref().parent().context("Directory has no parent")?;
let materials = obj_materials
.par_iter()
.map(|mat| {
// We can also parallelize loading the textures!
let mut textures = [
(containing_folder.join(&mat.diffuse_texture), false),
(containing_folder.join(&mat.normal_texture), true),
]
.par_iter()
.map(|(texture_path, is_normal_map)| {
texture::Texture::load(device, queue, texture_path, *is_normal_map)
})
.collect::<Result<Vec<_>>>()?;
// Pop removes from the end of the list.
let normal_texture = textures.pop().unwrap();
let diffuse_texture = textures.pop().unwrap();
Ok(Material::new(
device,
&mat.name,
diffuse_texture,
normal_texture,
layout,
))
})
.collect::<Result<Vec<Material>>>()?;
let meshes = obj_models
.par_iter()
.map(|m| {
let vertices = (0..m.mesh.positions.len() / 3)
.into_par_iter()
.map(|i| {
ModelVertex {
position: [
m.mesh.positions[i * 3],
m.mesh.positions[i * 3 + 1],
m.mesh.positions[i * 3 + 2],
],
// tex_coords: [m.mesh.texcoords[i * 2], m.mesh.texcoords[i * 2 + 1], 0.0]
tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]],
normal: [
m.mesh.normals[i * 3],
m.mesh.normals[i * 3 + 1],
m.mesh.normals[i * 3 + 2],
],
// We'll calculate these later
tangent: [0.0; 3],
bitangent: [0.0; 3],
padding: [0; 2],
}
})
.collect::<Vec<_>>();
let src_vertex_buffer =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Vertex Buffer", m.name)),
contents: bytemuck::cast_slice(&vertices),
// UPDATED!
usage: wgpu::BufferUsages::STORAGE,
});
let dst_vertex_buffer =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Vertex Buffer", m.name)),
contents: bytemuck::cast_slice(&vertices),
// UPDATED!
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::STORAGE,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Index Buffer", m.name)),
contents: bytemuck::cast_slice(&m.mesh.indices),
// UPDATED!
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::STORAGE,
});
let compute_info = ComputeInfo {
num_vertices: vertices.len() as _,
num_indices: m.mesh.indices.len() as _,
};
let info_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{:?} Compute Info Buffer", m.name)),
contents: bytemuck::cast_slice(&[compute_info]),
usage: wgpu::BufferUsages::UNIFORM,
});
// NEW!
// We'll need the mesh for the tangent/bitangent calculation
let binding = BitangentComputeBinding {
dst_vertex_buffer,
src_vertex_buffer,
index_buffer,
info_buffer,
compute_info,
};
// Calculate the tangents and bitangents
let calc_bind_group =
self.binder
.create_bind_group(&binding, device, Some("Mesh BindGroup"));
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Tangent and Bitangent Calc"),
});
{
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Compute Pass"),
timestamp_writes: None,
});
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &calc_bind_group, &[]);
pass.dispatch_workgroups(binding.compute_info.num_vertices as u32, 1, 1);
}
queue.submit(std::iter::once(encoder.finish()));
device.poll(wgpu::Maintain::Wait);
Ok(Mesh {
name: m.name.clone(),
vertex_buffer: binding.dst_vertex_buffer,
index_buffer: binding.index_buffer,
num_elements: binding.compute_info.num_indices,
material: m.mesh.material_id.unwrap_or(0),
})
})
.collect::<Result<Vec<_>>>()?;
Ok(Model { meshes, materials })
}
}
pub trait DrawModel<'a> {
fn draw_mesh(
&mut self,
mesh: &'a Mesh,
material: &'a Material,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_mesh_instanced(
&mut self,
mesh: &'a Mesh,
material: &'a Material,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_model(
&mut self,
model: &'a Model,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_model_instanced(
&mut self,
model: &'a Model,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_model_instanced_with_material(
&mut self,
model: &'a Model,
material: &'a Material,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
}
impl<'a, 'b> DrawModel<'b> for wgpu::RenderPass<'a>
where
'b: 'a,
{
fn draw_mesh(
&mut self,
mesh: &'b Mesh,
material: &'b Material,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
self.draw_mesh_instanced(mesh, material, 0..1, camera_bind_group, light_bind_group);
}
fn draw_mesh_instanced(
&mut self,
mesh: &'b Mesh,
material: &'b Material,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b 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, &material.bind_group, &[]);
self.set_bind_group(1, camera_bind_group, &[]);
self.set_bind_group(2, light_bind_group, &[]);
self.draw_indexed(0..mesh.num_elements, 0, instances);
}
fn draw_model(
&mut self,
model: &'b Model,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
self.draw_model_instanced(model, 0..1, camera_bind_group, light_bind_group);
}
fn draw_model_instanced(
&mut self,
model: &'b Model,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
for mesh in &model.meshes {
let material = &model.materials[mesh.material];
self.draw_mesh_instanced(
mesh,
material,
instances.clone(),
camera_bind_group,
light_bind_group,
);
}
}
fn draw_model_instanced_with_material(
&mut self,
model: &'b Model,
material: &'b Material,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
for mesh in &model.meshes {
self.draw_mesh_instanced(
mesh,
material,
instances.clone(),
camera_bind_group,
light_bind_group,
);
}
}
}
pub trait DrawLight<'a> {
fn draw_light_mesh(
&mut self,
mesh: &'a Mesh,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_light_mesh_instanced(
&mut self,
mesh: &'a Mesh,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_light_model(
&mut self,
model: &'a Model,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
fn draw_light_model_instanced(
&mut self,
model: &'a Model,
instances: Range<u32>,
camera_bind_group: &'a wgpu::BindGroup,
light_bind_group: &'a wgpu::BindGroup,
);
}
impl<'a, 'b> DrawLight<'b> for wgpu::RenderPass<'a>
where
'b: 'a,
{
fn draw_light_mesh(
&mut self,
mesh: &'b Mesh,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
self.draw_light_mesh_instanced(mesh, 0..1, camera_bind_group, light_bind_group);
}
fn draw_light_mesh_instanced(
&mut self,
mesh: &'b Mesh,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b 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, camera_bind_group, &[]);
self.set_bind_group(1, light_bind_group, &[]);
self.draw_indexed(0..mesh.num_elements, 0, instances);
}
fn draw_light_model(
&mut self,
model: &'b Model,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
self.draw_light_model_instanced(model, 0..1, camera_bind_group, light_bind_group);
}
fn draw_light_model_instanced(
&mut self,
model: &'b Model,
instances: Range<u32>,
camera_bind_group: &'b wgpu::BindGroup,
light_bind_group: &'b wgpu::BindGroup,
) {
for mesh in &model.meshes {
self.draw_light_mesh_instanced(
mesh,
instances.clone(),
camera_bind_group,
light_bind_group,
);
}
}
}