mirror of https://github.com/sotrh/learn-wgpu
started performance research
parent
eb20026731
commit
1a850cc1b1
@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "performance"
|
||||
version = "0.1.0"
|
||||
authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
image = "0.23"
|
||||
winit = "0.22"
|
||||
failure = "0.1"
|
||||
tobj = "1"
|
||||
bytemuck = "1.2"
|
||||
futures = "0.3.4"
|
||||
wgpu = "0.5.0"
|
||||
|
||||
[dependencies.cgmath]
|
||||
version = "0.17"
|
||||
features = ["swizzle"]
|
||||
|
||||
[build-dependencies]
|
||||
shaderc = "0.6"
|
||||
glob = "0.3"
|
||||
failure = "0.1"
|
||||
|
||||
|
||||
[[bin]]
|
||||
name = "performance"
|
||||
path = "src/main.rs"
|
@ -0,0 +1,68 @@
|
||||
use glob::glob;
|
||||
use failure::bail;
|
||||
use std::fs::{read_to_string, write};
|
||||
use std::path::{PathBuf};
|
||||
|
||||
fn main() {
|
||||
// This tells cargo to rerun this script if something in /src/ changes.
|
||||
println!("cargo:rerun-if-changed=src/*");
|
||||
|
||||
// Collect all shaders recursively within /src/
|
||||
let mut shader_paths = [
|
||||
glob("./src/**/*.vert").unwrap(),
|
||||
glob("./src/**/*.frag").unwrap(),
|
||||
glob("./src/**/*.comp").unwrap(),
|
||||
];
|
||||
|
||||
// This could be parallelized
|
||||
let shaders = shader_paths.iter_mut()
|
||||
.flatten()
|
||||
.map(|glob_result| {
|
||||
ShaderData::load(glob_result.unwrap()).unwrap()
|
||||
})
|
||||
.collect::<Vec<ShaderData>>();
|
||||
|
||||
let mut compiler = shaderc::Compiler::new().unwrap();
|
||||
|
||||
// This can't be parallelized. The [shaderc::Compiler] is not
|
||||
// thread safe. Also, it creates a lot of resources. You could
|
||||
// spawn multiple processes to handle this, but it would probably
|
||||
// be better just to only compile shaders that have been changed
|
||||
// recently.
|
||||
for shader in shaders {
|
||||
let compiled = compiler.compile_into_spirv(
|
||||
&shader.src,
|
||||
shader.kind,
|
||||
&shader.src_path.to_str().unwrap(),
|
||||
"main",
|
||||
None
|
||||
).unwrap();
|
||||
write(shader.spv_path, compiled.as_binary_u8()).unwrap();
|
||||
}
|
||||
|
||||
// panic!("Debugging...");
|
||||
}
|
||||
|
||||
struct ShaderData {
|
||||
src: String,
|
||||
src_path: PathBuf,
|
||||
spv_path: PathBuf,
|
||||
kind: shaderc::ShaderKind,
|
||||
}
|
||||
|
||||
impl ShaderData {
|
||||
pub fn load(src_path: PathBuf) -> Result<Self, failure::Error> {
|
||||
let extension = src_path.extension().unwrap().to_str().unwrap();
|
||||
let kind = match extension {
|
||||
"vert" => shaderc::ShaderKind::Vertex,
|
||||
"frag" => shaderc::ShaderKind::Fragment,
|
||||
"comp" => shaderc::ShaderKind::Compute,
|
||||
_ => bail!("Unsupported shader: {}", src_path.display()),
|
||||
};
|
||||
|
||||
let src = read_to_string(src_path.clone())?;
|
||||
let spv_path = src_path.with_extension(format!("{}.spv", extension));
|
||||
|
||||
Ok(Self { src, src_path, spv_path, kind })
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
After Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
@ -0,0 +1,14 @@
|
||||
# Blender MTL File: 'cube.blend'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material.001
|
||||
Ns 323.999994
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Bump cube-normal.png
|
||||
map_Kd cube-diffuse.jpg
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -0,0 +1,16 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 v_TexCoord;
|
||||
|
||||
layout(location = 1) out vec4 f_MipMap;
|
||||
|
||||
layout(set = 0, binding = 0) uniform texture2D t_Color;
|
||||
layout(set = 0, binding = 1) uniform sampler s_Color;
|
||||
|
||||
void main() {
|
||||
// The [textureLod] function will sample the supplied texture
|
||||
// at the specified Level Of Detail (LOD). In our case the
|
||||
// Lod is 0, meaning we want to use the texture with it's
|
||||
// normal detail.
|
||||
f_MipMap = textureLod(sampler2D(t_Color, s_Color), v_TexCoord, 0.0);
|
||||
}
|
Binary file not shown.
@ -0,0 +1,18 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec2 v_TexCoord;
|
||||
|
||||
void main() {
|
||||
// We need to know what vertex we are processing in
|
||||
// order to use the right texture coord.
|
||||
switch(gl_VertexIndex % 4) {
|
||||
case 0: v_TexCoord = vec2(1.0, 0.0); break;
|
||||
case 1: v_TexCoord = vec2(1.0, 1.0); break;
|
||||
case 2: v_TexCoord = vec2(0.0, 0.0); break;
|
||||
case 3: v_TexCoord = vec2(0.0, 1.0); break;
|
||||
}
|
||||
|
||||
// We us `v_TexCoord` to generate gl_Position rather
|
||||
// than supply a vertex buffer.
|
||||
gl_Position = vec4(v_TexCoord * 2.0 - 1.0, 0.5, 1.0);
|
||||
}
|
Binary file not shown.
@ -0,0 +1,7 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec4 f_Color;
|
||||
|
||||
void main() {
|
||||
f_Color = vec4(1, 1, 0, 1);
|
||||
}
|
Binary file not shown.
@ -0,0 +1,8 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 v_color;
|
||||
layout(location=0) out vec4 f_color;
|
||||
|
||||
void main() {
|
||||
f_color = vec4(v_color, 1.0);
|
||||
}
|
Binary file not shown.
@ -0,0 +1,27 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
|
||||
layout(location=0) out vec4 v_color;
|
||||
|
||||
layout(set=0, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Light {
|
||||
vec3 u_position;
|
||||
vec3 u_color;
|
||||
};
|
||||
|
||||
// Let's keep our light smaller than our other objects
|
||||
float scale = 0.25;
|
||||
|
||||
void main() {
|
||||
vec3 v_position = a_position * scale + u_position;
|
||||
gl_Position = u_view_proj * vec4(v_position, 1);
|
||||
|
||||
v_color = vec4(u_color, 0);
|
||||
}
|
Binary file not shown.
@ -0,0 +1,663 @@
|
||||
use cgmath::prelude::*;
|
||||
use winit::{
|
||||
event::*,
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::Window,
|
||||
};
|
||||
use futures::executor::block_on;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
mod pipeline;
|
||||
mod model;
|
||||
mod texture;
|
||||
|
||||
use model::{DrawLight, DrawModel, Vertex};
|
||||
|
||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
||||
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.5, 0.0,
|
||||
0.0, 0.0, 0.5, 1.0,
|
||||
);
|
||||
|
||||
const NUM_INSTANCES_PER_ROW: u32 = 10;
|
||||
|
||||
struct Camera {
|
||||
eye: cgmath::Point3<f32>,
|
||||
target: cgmath::Point3<f32>,
|
||||
up: cgmath::Vector3<f32>,
|
||||
aspect: f32,
|
||||
fovy: f32,
|
||||
znear: f32,
|
||||
zfar: f32,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
fn build_view_projection_matrix(&self) -> cgmath::Matrix4<f32> {
|
||||
let view = cgmath::Matrix4::look_at(self.eye, self.target, self.up);
|
||||
let proj = cgmath::perspective(cgmath::Deg(self.fovy), self.aspect, self.znear, self.zfar);
|
||||
return proj * view;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct Uniforms {
|
||||
view_position: cgmath::Vector4<f32>,
|
||||
view_proj: cgmath::Matrix4<f32>,
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
view_position: Zero::zero(),
|
||||
view_proj: cgmath::Matrix4::identity(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_view_proj(&mut self, camera: &Camera) {
|
||||
self.view_position = camera.eye.to_homogeneous();
|
||||
self.view_proj = OPENGL_TO_WGPU_MATRIX * camera.build_view_projection_matrix();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for Uniforms {}
|
||||
unsafe impl bytemuck::Pod for Uniforms {}
|
||||
|
||||
struct CameraController {
|
||||
speed: f32,
|
||||
is_up_pressed: bool,
|
||||
is_down_pressed: bool,
|
||||
is_forward_pressed: bool,
|
||||
is_backward_pressed: bool,
|
||||
is_left_pressed: bool,
|
||||
is_right_pressed: bool,
|
||||
}
|
||||
|
||||
impl CameraController {
|
||||
fn new(speed: f32) -> Self {
|
||||
Self {
|
||||
speed,
|
||||
is_up_pressed: false,
|
||||
is_down_pressed: false,
|
||||
is_forward_pressed: false,
|
||||
is_backward_pressed: false,
|
||||
is_left_pressed: false,
|
||||
is_right_pressed: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_events(&mut self, event: &WindowEvent) -> bool {
|
||||
match event {
|
||||
WindowEvent::KeyboardInput {
|
||||
input:
|
||||
KeyboardInput {
|
||||
state,
|
||||
virtual_keycode: Some(keycode),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => {
|
||||
let is_pressed = *state == ElementState::Pressed;
|
||||
match keycode {
|
||||
VirtualKeyCode::Space => {
|
||||
self.is_up_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::LShift => {
|
||||
self.is_down_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::W | VirtualKeyCode::Up => {
|
||||
self.is_forward_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::A | VirtualKeyCode::Left => {
|
||||
self.is_left_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::S | VirtualKeyCode::Down => {
|
||||
self.is_backward_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
VirtualKeyCode::D | VirtualKeyCode::Right => {
|
||||
self.is_right_pressed = is_pressed;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_camera(&self, camera: &mut Camera) {
|
||||
let forward = (camera.target - camera.eye).normalize();
|
||||
|
||||
if self.is_forward_pressed {
|
||||
camera.eye += forward * self.speed;
|
||||
}
|
||||
if self.is_backward_pressed {
|
||||
camera.eye -= forward * self.speed;
|
||||
}
|
||||
|
||||
let right = forward.cross(camera.up);
|
||||
|
||||
if self.is_right_pressed {
|
||||
camera.eye += right * self.speed;
|
||||
}
|
||||
if self.is_left_pressed {
|
||||
camera.eye -= right * self.speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Instance {
|
||||
position: cgmath::Vector3<f32>,
|
||||
rotation: cgmath::Quaternion<f32>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
fn to_raw(&self) -> InstanceRaw {
|
||||
InstanceRaw {
|
||||
model: cgmath::Matrix4::from_translation(self.position) * cgmath::Matrix4::from(self.rotation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct InstanceRaw {
|
||||
#[allow(dead_code)]
|
||||
model: cgmath::Matrix4<f32>,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for InstanceRaw {}
|
||||
unsafe impl bytemuck::Zeroable for InstanceRaw {}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct Light {
|
||||
position: cgmath::Vector3<f32>,
|
||||
// Due to uniforms requiring 16 byte (4 float) spacing, we need to use a padding field here
|
||||
_padding: u32,
|
||||
color: cgmath::Vector3<f32>,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for Light {}
|
||||
unsafe impl bytemuck::Pod for Light {}
|
||||
|
||||
struct State {
|
||||
surface: wgpu::Surface,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
sc_desc: wgpu::SwapChainDescriptor,
|
||||
swap_chain: wgpu::SwapChain,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
obj_model: model::Model,
|
||||
camera: Camera,
|
||||
camera_controller: CameraController,
|
||||
uniforms: Uniforms,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
instances: Vec<Instance>,
|
||||
#[allow(dead_code)]
|
||||
instance_buffer: wgpu::Buffer,
|
||||
depth_texture: texture::Texture,
|
||||
size: winit::dpi::PhysicalSize<u32>,
|
||||
light: Light,
|
||||
light_buffer: wgpu::Buffer,
|
||||
light_bind_group: wgpu::BindGroup,
|
||||
light_render_pipeline: wgpu::RenderPipeline,
|
||||
#[allow(dead_code)]
|
||||
debug_material: model::Material,
|
||||
}
|
||||
|
||||
|
||||
impl State {
|
||||
async fn new(window: &Window) -> Self {
|
||||
let size = window.inner_size();
|
||||
|
||||
let surface = wgpu::Surface::create(window);
|
||||
|
||||
let adapter = wgpu::Adapter::request(
|
||||
&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::Default,
|
||||
compatible_surface: Some(&surface),
|
||||
},
|
||||
wgpu::BackendBit::PRIMARY, // Vulkan + Metal + DX12 + Browser WebGPU
|
||||
).await.unwrap();
|
||||
|
||||
let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
extensions: wgpu::Extensions {
|
||||
anisotropic_filtering: false,
|
||||
},
|
||||
limits: Default::default(),
|
||||
}).await;
|
||||
|
||||
|
||||
let sc_desc = wgpu::SwapChainDescriptor {
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
|
||||
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
};
|
||||
|
||||
let swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
||||
|
||||
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::SampledTexture {
|
||||
multisampled: false,
|
||||
component_type: wgpu::TextureComponentType::Float,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler { comparison: false },
|
||||
},
|
||||
// normal map
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::SampledTexture {
|
||||
multisampled: false,
|
||||
component_type: wgpu::TextureComponentType::Float,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler { comparison: false },
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let camera = Camera {
|
||||
eye: (0.0, 5.0, -10.0).into(),
|
||||
target: (0.0, 0.0, 0.0).into(),
|
||||
up: cgmath::Vector3::unit_y(),
|
||||
aspect: sc_desc.width as f32 / sc_desc.height as f32,
|
||||
fovy: 45.0,
|
||||
znear: 0.1,
|
||||
zfar: 100.0,
|
||||
};
|
||||
|
||||
let camera_controller = CameraController::new(0.2);
|
||||
|
||||
let mut uniforms = Uniforms::new();
|
||||
uniforms.update_view_proj(&camera);
|
||||
|
||||
let uniform_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[uniforms]),
|
||||
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||
);
|
||||
|
||||
const SPACE_BETWEEN: f32 = 3.0;
|
||||
let instances = (0..NUM_INSTANCES_PER_ROW)
|
||||
.flat_map(|z| {
|
||||
(0..NUM_INSTANCES_PER_ROW).map(move |x| {
|
||||
let x = SPACE_BETWEEN * (x as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
|
||||
let z = SPACE_BETWEEN * (z as f32 - NUM_INSTANCES_PER_ROW as f32 / 2.0);
|
||||
|
||||
let position = cgmath::Vector3 { x, y: 0.0, z };
|
||||
|
||||
let rotation = if position.is_zero() {
|
||||
cgmath::Quaternion::from_axis_angle(
|
||||
cgmath::Vector3::unit_z(),
|
||||
cgmath::Deg(0.0),
|
||||
)
|
||||
} else {
|
||||
cgmath::Quaternion::from_axis_angle(
|
||||
position.clone().normalize(),
|
||||
cgmath::Deg(45.0),
|
||||
)
|
||||
};
|
||||
|
||||
Instance { position, rotation }
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance_data = instances
|
||||
.iter()
|
||||
.map(Instance::to_raw)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance_buffer_size =
|
||||
instance_data.len() * std::mem::size_of::<cgmath::Matrix4<f32>>();
|
||||
let instance_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&instance_data),
|
||||
wgpu::BufferUsage::STORAGE_READ | wgpu::BufferUsage::COPY_DST,
|
||||
);
|
||||
|
||||
let uniform_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::UniformBuffer { dynamic: false },
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::VERTEX,
|
||||
ty: wgpu::BindingType::StorageBuffer {
|
||||
dynamic: false,
|
||||
readonly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &uniform_bind_group_layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &uniform_buffer,
|
||||
range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress,
|
||||
},
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &instance_buffer,
|
||||
range: 0..instance_buffer_size as wgpu::BufferAddress,
|
||||
},
|
||||
},
|
||||
],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let (obj_model, cmds) =
|
||||
model::Model::load(&device, &texture_bind_group_layout, "code/intermediate/tutorial10-lighting/src/res/cube.obj").unwrap();
|
||||
|
||||
queue.submit(&cmds);
|
||||
|
||||
let light = Light {
|
||||
position: (2.0, 2.0, 2.0).into(),
|
||||
_padding: 0,
|
||||
color: (1.0, 1.0, 1.0).into(),
|
||||
};
|
||||
|
||||
let light_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[light]),
|
||||
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
|
||||
);
|
||||
|
||||
let light_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::VERTEX | wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::UniformBuffer { dynamic: false },
|
||||
}],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let light_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &light_bind_group_layout,
|
||||
bindings: &[wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer {
|
||||
buffer: &light_buffer,
|
||||
range: 0..std::mem::size_of_val(&light) as wgpu::BufferAddress,
|
||||
},
|
||||
}],
|
||||
label: None,
|
||||
});
|
||||
|
||||
let depth_texture = texture::Texture::create_depth_texture(&device, &sc_desc, "depth_texture");
|
||||
|
||||
let render_pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[
|
||||
&texture_bind_group_layout,
|
||||
&uniform_bind_group_layout,
|
||||
&light_bind_group_layout,
|
||||
],
|
||||
});
|
||||
|
||||
let render_pipeline = pipeline::RenderPipelineBuilder::new()
|
||||
.layout(&render_pipeline_layout)
|
||||
.color_solid(sc_desc.format)
|
||||
.depth_format(texture::Texture::DEPTH_FORMAT)
|
||||
.vertex_buffer(model::ModelVertex::desc())
|
||||
.vertex_shader(include_bytes!("shader.vert.spv"))
|
||||
.fragment_shader(include_bytes!("shader.frag.spv"))
|
||||
.build(&device).unwrap();
|
||||
|
||||
let light_render_pipeline = {
|
||||
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[&uniform_bind_group_layout, &light_bind_group_layout],
|
||||
});
|
||||
|
||||
pipeline::RenderPipelineBuilder::new()
|
||||
.layout(&layout)
|
||||
.color_solid(sc_desc.format)
|
||||
.depth_format(texture::Texture::DEPTH_FORMAT)
|
||||
.vertex_buffer(model::ModelVertex::desc())
|
||||
.vertex_shader(include_bytes!("light.vert.spv"))
|
||||
.fragment_shader(include_bytes!("light.frag.spv"))
|
||||
.build(&device).unwrap()
|
||||
};
|
||||
|
||||
let debug_material = {
|
||||
let diffuse_bytes = include_bytes!("../res/cobble-diffuse.png");
|
||||
let normal_bytes = include_bytes!("../res/cobble-normal.png");
|
||||
|
||||
let mut command_buffers = vec![];
|
||||
let (diffuse_texture, cmds) = texture::Texture::from_bytes(&device, diffuse_bytes, "res/alt-diffuse.png", false).unwrap();
|
||||
command_buffers.push(cmds);
|
||||
let (normal_texture, cmds) = texture::Texture::from_bytes(&device, normal_bytes, "res/alt-normal.png", true).unwrap();
|
||||
command_buffers.push(cmds);
|
||||
queue.submit(&command_buffers);
|
||||
|
||||
model::Material::new(&device, "alt-material", diffuse_texture, normal_texture, &texture_bind_group_layout)
|
||||
};
|
||||
|
||||
Self {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
sc_desc,
|
||||
swap_chain,
|
||||
render_pipeline,
|
||||
obj_model,
|
||||
camera,
|
||||
camera_controller,
|
||||
uniform_buffer,
|
||||
uniform_bind_group,
|
||||
uniforms,
|
||||
instances,
|
||||
instance_buffer,
|
||||
depth_texture,
|
||||
size,
|
||||
light,
|
||||
light_buffer,
|
||||
light_bind_group,
|
||||
light_render_pipeline,
|
||||
#[allow(dead_code)]
|
||||
debug_material,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
self.camera.aspect = self.sc_desc.width as f32 / self.sc_desc.height as f32;
|
||||
self.size = new_size;
|
||||
self.sc_desc.width = new_size.width;
|
||||
self.sc_desc.height = new_size.height;
|
||||
self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
|
||||
self.depth_texture = texture::Texture::create_depth_texture(&self.device, &self.sc_desc, "depth_texture");
|
||||
}
|
||||
|
||||
fn input(&mut self, event: &WindowEvent) -> bool {
|
||||
self.camera_controller.process_events(event)
|
||||
}
|
||||
|
||||
fn update(&mut self, dt: Duration) {
|
||||
self.camera_controller.update_camera(&mut self.camera);
|
||||
self.uniforms.update_view_proj(&self.camera);
|
||||
|
||||
let mut encoder =
|
||||
self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||
|
||||
let staging_buffer = self.device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[self.uniforms]),
|
||||
wgpu::BufferUsage::COPY_SRC,
|
||||
);
|
||||
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&staging_buffer,
|
||||
0,
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
std::mem::size_of::<Uniforms>() as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
// Update the light
|
||||
let old_position = self.light.position;
|
||||
self.light.position =
|
||||
cgmath::Quaternion::from_axis_angle(
|
||||
(0.0, 1.0, 0.0).into(),
|
||||
cgmath::Deg(45.0) * dt.as_secs_f32()
|
||||
) * old_position;
|
||||
|
||||
let staging_buffer = self.device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&[self.light]),
|
||||
wgpu::BufferUsage::COPY_SRC,
|
||||
);
|
||||
encoder.copy_buffer_to_buffer(
|
||||
&staging_buffer,
|
||||
0,
|
||||
&self.light_buffer,
|
||||
0,
|
||||
std::mem::size_of::<Light>() as wgpu::BufferAddress,
|
||||
);
|
||||
|
||||
self.queue.submit(&[encoder.finish()]);
|
||||
}
|
||||
|
||||
fn render(&mut self) {
|
||||
let frame = self.swap_chain.get_next_texture()
|
||||
.expect("Timeout getting texture");
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: None
|
||||
});
|
||||
|
||||
{
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: &frame.view,
|
||||
resolve_target: None,
|
||||
load_op: wgpu::LoadOp::Clear,
|
||||
store_op: wgpu::StoreOp::Store,
|
||||
clear_color: wgpu::Color {
|
||||
r: 0.1,
|
||||
g: 0.2,
|
||||
b: 0.3,
|
||||
a: 1.0,
|
||||
},
|
||||
}],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
|
||||
attachment: &self.depth_texture.view,
|
||||
depth_load_op: wgpu::LoadOp::Clear,
|
||||
depth_store_op: wgpu::StoreOp::Store,
|
||||
clear_depth: 1.0,
|
||||
stencil_load_op: wgpu::LoadOp::Clear,
|
||||
stencil_store_op: wgpu::StoreOp::Store,
|
||||
clear_stencil: 0,
|
||||
}),
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(&self.light_render_pipeline);
|
||||
render_pass.draw_light_model(
|
||||
&self.obj_model,
|
||||
&self.uniform_bind_group,
|
||||
&self.light_bind_group,
|
||||
);
|
||||
|
||||
render_pass.set_pipeline(&self.render_pipeline);
|
||||
render_pass.draw_model_instanced(
|
||||
&self.obj_model,
|
||||
0..self.instances.len() as u32,
|
||||
&self.uniform_bind_group,
|
||||
&self.light_bind_group,
|
||||
);
|
||||
}
|
||||
self.queue.submit(&[encoder.finish()]);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let event_loop = EventLoop::new();
|
||||
let title = env!("CARGO_PKG_NAME");
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
let mut state = block_on(State::new(&window));
|
||||
let mut is_focused = false;
|
||||
let mut last_update = Instant::now();
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = if is_focused {
|
||||
ControlFlow::Poll
|
||||
} else {
|
||||
ControlFlow::Wait
|
||||
};
|
||||
match event {
|
||||
Event::MainEventsCleared => if is_focused {
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
ref event,
|
||||
window_id,
|
||||
} if window_id == window.id() => {
|
||||
if !state.input(event) {
|
||||
match event {
|
||||
WindowEvent::Focused(focused) => is_focused = *focused,
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::KeyboardInput { input, .. } => match input {
|
||||
KeyboardInput {
|
||||
state: ElementState::Pressed,
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
..
|
||||
} => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
state.resize(*physical_size);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
state.resize(**new_inner_size);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::RedrawRequested(_) => {
|
||||
let now = Instant::now();
|
||||
let dt = now - last_update;
|
||||
state.update(dt);
|
||||
last_update = now;
|
||||
state.render();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,441 @@
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::texture;
|
||||
|
||||
pub trait Vertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a>;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ModelVertex {
|
||||
position: cgmath::Vector3<f32>,
|
||||
tex_coords: cgmath::Vector2<f32>,
|
||||
normal: cgmath::Vector3<f32>,
|
||||
tangent: cgmath::Vector3<f32>,
|
||||
bitangent: cgmath::Vector3<f32>,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Zeroable for ModelVertex {}
|
||||
unsafe impl bytemuck::Pod for ModelVertex {}
|
||||
|
||||
impl Vertex for ModelVertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferDescriptor<'a> {
|
||||
use std::mem;
|
||||
wgpu::VertexBufferDescriptor {
|
||||
stride: mem::size_of::<ModelVertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::InputStepMode::Vertex,
|
||||
attributes: &[
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: 0,
|
||||
shader_location: 0,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
||||
shader_location: 1,
|
||||
format: wgpu::VertexFormat::Float2,
|
||||
},
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
||||
shader_location: 2,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
// Tangent and bitangent
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||
shader_location: 3,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
wgpu::VertexAttributeDescriptor {
|
||||
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress,
|
||||
shader_location: 4,
|
||||
format: wgpu::VertexFormat::Float3,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::TextureView(&normal_texture.view),
|
||||
},
|
||||
wgpu::Binding {
|
||||
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,
|
||||
}
|
||||
|
||||
pub struct Model {
|
||||
pub meshes: Vec<Mesh>,
|
||||
pub materials: Vec<Material>,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn load<P: AsRef<Path>>(
|
||||
device: &wgpu::Device,
|
||||
layout: &wgpu::BindGroupLayout,
|
||||
path: P,
|
||||
) -> Result<(Self, Vec<wgpu::CommandBuffer>), failure::Error> {
|
||||
let (obj_models, obj_materials) = tobj::load_obj(path.as_ref())?;
|
||||
|
||||
// We're assuming that the texture files are stored with the obj file
|
||||
let containing_folder = path.as_ref().parent().unwrap();
|
||||
|
||||
// Our `Texure` struct currently returns a `CommandBuffer` when it's created so we need to collect those and return them.
|
||||
let mut command_buffers = Vec::new();
|
||||
|
||||
let mut materials = Vec::new();
|
||||
for mat in obj_materials {
|
||||
let diffuse_path = mat.diffuse_texture;
|
||||
let (diffuse_texture, cmds) = texture::Texture::load(device, containing_folder.join(diffuse_path), false)?;
|
||||
command_buffers.push(cmds);
|
||||
|
||||
let normal_path = match mat.unknown_param.get("map_Bump") {
|
||||
Some(v) => Ok(v),
|
||||
None => Err(failure::err_msg("Unable to find normal map"))
|
||||
};
|
||||
let (normal_texture, cmds) = texture::Texture::load(device, containing_folder.join(normal_path?), true)?;
|
||||
command_buffers.push(cmds);
|
||||
|
||||
materials.push(Material::new(
|
||||
device,
|
||||
&mat.name,
|
||||
diffuse_texture,
|
||||
normal_texture,
|
||||
layout,
|
||||
));
|
||||
}
|
||||
|
||||
let mut meshes = Vec::new();
|
||||
for m in obj_models {
|
||||
let mut vertices = Vec::new();
|
||||
for i in 0..m.mesh.positions.len() / 3 {
|
||||
vertices.push(ModelVertex {
|
||||
position: [
|
||||
m.mesh.positions[i * 3],
|
||||
m.mesh.positions[i * 3 + 1],
|
||||
m.mesh.positions[i * 3 + 2],
|
||||
].into(),
|
||||
tex_coords: [
|
||||
m.mesh.texcoords[i * 2],
|
||||
m.mesh.texcoords[i * 2 + 1]
|
||||
].into(),
|
||||
normal: [
|
||||
m.mesh.normals[i * 3],
|
||||
m.mesh.normals[i * 3 + 1],
|
||||
m.mesh.normals[i * 3 + 2],
|
||||
].into(),
|
||||
// We'll calculate these later
|
||||
tangent: [0.0; 3].into(),
|
||||
bitangent: [0.0; 3].into(),
|
||||
});
|
||||
}
|
||||
|
||||
let indices = &m.mesh.indices;
|
||||
|
||||
// Calculate tangents and bitangets. We're going to
|
||||
// use the triangles, so we need to loop through the
|
||||
// indices in chunks of 3
|
||||
for c in indices.chunks(3) {
|
||||
let v0 = vertices[c[0] as usize];
|
||||
let v1 = vertices[c[1] as usize];
|
||||
let v2 = vertices[c[2] as usize];
|
||||
|
||||
let pos0 = v0.position;
|
||||
let pos1 = v1.position;
|
||||
let pos2 = v2.position;
|
||||
|
||||
let uv0 = v0.tex_coords;
|
||||
let uv1 = v1.tex_coords;
|
||||
let uv2 = v2.tex_coords;
|
||||
|
||||
// Calculate the edges of the triangle
|
||||
let delta_pos1 = pos1 - pos0;
|
||||
let delta_pos2 = pos2 - pos0;
|
||||
|
||||
// This will give us a direction to calculate the
|
||||
// tangent and bitangent
|
||||
let delta_uv1 = uv1 - uv0;
|
||||
let delta_uv2 = uv2 - uv0;
|
||||
|
||||
// Solving the following system of equations will
|
||||
// give us the tangent and bitangent.
|
||||
// delta_pos1 = delta_uv1.x * T + delta_u.y * B
|
||||
// delta_pos2 = delta_uv2.x * T + delta_uv2.y * B
|
||||
// Luckily, the place I found this equation provided
|
||||
// the solution!
|
||||
let r = 1.0 / (delta_uv1 .x * delta_uv2.y - delta_uv1.y * delta_uv2.x);
|
||||
let tangent = (delta_pos1 * delta_uv2.y - delta_pos2 * delta_uv1.y) * r;
|
||||
let bitangent = (delta_pos2 * delta_uv1.x - delta_pos1 * delta_uv2.x) * r;
|
||||
|
||||
// We'll use the same tangent/bitangent for each vertex in the triangle
|
||||
vertices[c[0] as usize].tangent = tangent;
|
||||
vertices[c[1] as usize].tangent = tangent;
|
||||
vertices[c[2] as usize].tangent = tangent;
|
||||
|
||||
vertices[c[0] as usize].bitangent = bitangent;
|
||||
vertices[c[1] as usize].bitangent = bitangent;
|
||||
vertices[c[2] as usize].bitangent = bitangent;
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(&vertices),
|
||||
wgpu::BufferUsage::VERTEX,
|
||||
);
|
||||
|
||||
let index_buffer = device.create_buffer_with_data(
|
||||
bytemuck::cast_slice(indices),
|
||||
wgpu::BufferUsage::INDEX,
|
||||
);
|
||||
|
||||
meshes.push(Mesh {
|
||||
name: m.name,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
num_elements: m.mesh.indices.len() as u32,
|
||||
material: m.mesh.material_id.unwrap_or(0),
|
||||
});
|
||||
}
|
||||
|
||||
Ok((Self { meshes, materials }, command_buffers))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DrawModel<'a, 'b>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
|
||||
fn draw_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_model_instanced_with_material(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, 'b> DrawModel<'a, 'b> for wgpu::RenderPass<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_mesh_instanced(mesh, material, 0..1, uniforms, light);
|
||||
}
|
||||
|
||||
fn draw_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.set_vertex_buffer(0, &mesh.vertex_buffer, 0, 0);
|
||||
self.set_index_buffer(&mesh.index_buffer, 0, 0);
|
||||
self.set_bind_group(0, &material.bind_group, &[]);
|
||||
self.set_bind_group(1, &uniforms, &[]);
|
||||
self.set_bind_group(2, &light, &[]);
|
||||
self.draw_indexed(0..mesh.num_elements, 0, instances);
|
||||
}
|
||||
|
||||
fn draw_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_model_instanced(model, 0..1, uniforms, light);
|
||||
}
|
||||
|
||||
fn draw_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
let material = &model.materials[mesh.material];
|
||||
self.draw_mesh_instanced(mesh, material, instances.clone(), uniforms, light);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_model_instanced_with_material(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
material: &'b Material,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
self.draw_mesh_instanced(mesh, material, instances.clone(), uniforms, light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DrawLight<'a, 'b>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_light_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_light_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) where
|
||||
'b: 'a;
|
||||
|
||||
fn draw_light_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
fn draw_light_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
);
|
||||
}
|
||||
|
||||
impl<'a, 'b> DrawLight<'a, 'b> for wgpu::RenderPass<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn draw_light_mesh(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_light_mesh_instanced(mesh, 0..1, uniforms, light);
|
||||
}
|
||||
|
||||
fn draw_light_mesh_instanced(
|
||||
&mut self,
|
||||
mesh: &'b Mesh,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.set_vertex_buffer(0, &mesh.vertex_buffer, 0, 0);
|
||||
self.set_index_buffer(&mesh.index_buffer, 0, 0);
|
||||
self.set_bind_group(0, uniforms, &[]);
|
||||
self.set_bind_group(1, light, &[]);
|
||||
self.draw_indexed(0..mesh.num_elements, 0, instances);
|
||||
}
|
||||
|
||||
fn draw_light_model(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
self.draw_light_model_instanced(model, 0..1, uniforms, light);
|
||||
}
|
||||
fn draw_light_model_instanced(
|
||||
&mut self,
|
||||
model: &'b Model,
|
||||
instances: Range<u32>,
|
||||
uniforms: &'b wgpu::BindGroup,
|
||||
light: &'b wgpu::BindGroup,
|
||||
) {
|
||||
for mesh in &model.meshes {
|
||||
self.draw_light_mesh_instanced(mesh, instances.clone(), uniforms, light);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,244 @@
|
||||
use failure::bail;
|
||||
|
||||
pub struct RenderPipelineBuilder<'a> {
|
||||
layout: Option<&'a wgpu::PipelineLayout>,
|
||||
vertex_shader: Option<&'a [u8]>,
|
||||
fragment_shader: Option<&'a [u8]>,
|
||||
front_face: wgpu::FrontFace,
|
||||
cull_mode: wgpu::CullMode,
|
||||
depth_bias: i32,
|
||||
depth_bias_slope_scale: f32,
|
||||
depth_bias_clamp: f32,
|
||||
primitive_topology: wgpu::PrimitiveTopology,
|
||||
color_states: Vec<wgpu::ColorStateDescriptor>,
|
||||
depth_stencil_state: Option<wgpu::DepthStencilStateDescriptor>,
|
||||
index_format: wgpu::IndexFormat,
|
||||
vertex_buffers: Vec<wgpu::VertexBufferDescriptor<'a>>,
|
||||
sample_count: u32,
|
||||
sample_mask: u32,
|
||||
alpha_to_coverage_enabled: bool,
|
||||
}
|
||||
|
||||
impl<'a> RenderPipelineBuilder<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
layout: None,
|
||||
vertex_shader: None,
|
||||
fragment_shader: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: wgpu::CullMode::None,
|
||||
depth_bias: 0,
|
||||
depth_bias_slope_scale: 0.0,
|
||||
depth_bias_clamp: 0.0,
|
||||
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
color_states: Vec::new(),
|
||||
depth_stencil_state: None,
|
||||
index_format: wgpu::IndexFormat::Uint32,
|
||||
vertex_buffers: Vec::new(),
|
||||
sample_count: 1,
|
||||
sample_mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout(&mut self, layout: &'a wgpu::PipelineLayout) -> &mut Self {
|
||||
self.layout = Some(layout);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vertex_shader(&mut self, spv: &'a [u8]) -> &mut Self {
|
||||
self.vertex_shader = Some(spv);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fragment_shader(&mut self, spv: &'a [u8]) -> &mut Self {
|
||||
self.fragment_shader = Some(spv);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn front_face(&mut self, ff: wgpu::FrontFace) -> &mut Self {
|
||||
self.front_face = ff;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn cull_mode(&mut self, cm: wgpu::CullMode) -> &mut Self {
|
||||
self.cull_mode = cm;
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn depth_bias(&mut self, db: i32) -> &mut Self {
|
||||
self.depth_bias = db;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn depth_bias_slope_scale(&mut self, dbss: f32) -> &mut Self {
|
||||
self.depth_bias_slope_scale = dbss;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn depth_bias_clamp(&mut self, dbc: f32) -> &mut Self {
|
||||
self.depth_bias_clamp = dbc;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn primitive_topology(&mut self, pt: wgpu::PrimitiveTopology) -> &mut Self {
|
||||
self.primitive_topology = pt;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn color_state(&mut self, cs: wgpu::ColorStateDescriptor) -> &mut Self {
|
||||
self.color_states.push(cs);
|
||||
self
|
||||
}
|
||||
|
||||
/// Helper method for [RenderPipelineBuilder::color_state]
|
||||
pub fn color_solid(&mut self, format: wgpu::TextureFormat) -> &mut Self {
|
||||
self.color_state(
|
||||
wgpu::ColorStateDescriptor {
|
||||
format,
|
||||
alpha_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
color_blend: wgpu::BlendDescriptor::REPLACE,
|
||||
write_mask: wgpu::ColorWrite::ALL,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn depth_stencil_state(&mut self, dss: wgpu::DepthStencilStateDescriptor) -> &mut Self {
|
||||
self.depth_stencil_state = Some(dss);
|
||||
self
|
||||
}
|
||||
|
||||
/// Helper method for [RenderPipelineBuilder::depth_stencil_state]
|
||||
pub fn depth_no_stencil(
|
||||
&mut self,
|
||||
format: wgpu::TextureFormat,
|
||||
depth_write_enabled: bool,
|
||||
depth_compare: wgpu::CompareFunction,
|
||||
) -> &mut Self {
|
||||
self.depth_stencil_state(
|
||||
wgpu::DepthStencilStateDescriptor {
|
||||
format,
|
||||
depth_write_enabled,
|
||||
depth_compare,
|
||||
stencil_front: wgpu::StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_back: wgpu::StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_read_mask: 0,
|
||||
stencil_write_mask: 0,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Helper method for [RenderPipelineBuilder::depth_no_stencil]
|
||||
pub fn depth_format(&mut self, format: wgpu::TextureFormat) -> &mut Self {
|
||||
self.depth_no_stencil(format, true, wgpu::CompareFunction::Less)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn index_format(&mut self, ifmt: wgpu::IndexFormat) -> &mut Self {
|
||||
self.index_format = ifmt;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vertex_buffer(&mut self, vb: wgpu::VertexBufferDescriptor<'a>) -> &mut Self {
|
||||
self.vertex_buffers.push(vb);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn sample_count(&mut self, sc: u32) -> &mut Self {
|
||||
self.sample_count = sc;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn sample_mask(&mut self, sm: u32) -> &mut Self {
|
||||
self.sample_mask = sm;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn alpha_to_coverage_enabled(&mut self, atce: bool) -> &mut Self {
|
||||
self.alpha_to_coverage_enabled = atce;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(&self, device: &wgpu::Device) -> Result<wgpu::RenderPipeline, failure::Error> {
|
||||
// We need a layout
|
||||
if self.layout.is_none() {
|
||||
bail!("No pipeline layout supplied!");
|
||||
}
|
||||
let layout = self.layout.unwrap();
|
||||
|
||||
// Render pipelines always have a vertex shader, but due
|
||||
// to the way the builder pattern works, we can't
|
||||
// guarantee that the user will specify one, so we'll
|
||||
// just return an error if they forgot.
|
||||
//
|
||||
// We could supply a default one, but a "default" vertex
|
||||
// could take on many forms. An error is much more
|
||||
// explicit.
|
||||
if self.vertex_shader.is_none() {
|
||||
bail!("No vertex shader supplied!")
|
||||
}
|
||||
let vs = create_shader_module(device, self.vertex_shader.unwrap());
|
||||
|
||||
// The fragment shader is optional (IDK why, but it is).
|
||||
// Having the shader be optional is giving me issues with
|
||||
// the borrow checker so I'm going to use a default shader
|
||||
// if the user doesn't supply one.
|
||||
let fs_spv = self.fragment_shader.unwrap_or(include_bytes!("default.frag.spv"));
|
||||
let fs = create_shader_module(device, fs_spv);
|
||||
|
||||
let pipeline = device.create_render_pipeline(
|
||||
&wgpu::RenderPipelineDescriptor {
|
||||
layout: &layout,
|
||||
vertex_stage: wgpu::ProgrammableStageDescriptor {
|
||||
module: &vs,
|
||||
entry_point: "main",
|
||||
},
|
||||
fragment_stage: Some(
|
||||
wgpu::ProgrammableStageDescriptor {
|
||||
module: &fs,
|
||||
entry_point: "main",
|
||||
}
|
||||
),
|
||||
rasterization_state: Some(wgpu::RasterizationStateDescriptor {
|
||||
front_face: self.front_face,
|
||||
cull_mode: self.cull_mode,
|
||||
depth_bias: self.depth_bias,
|
||||
depth_bias_slope_scale: self.depth_bias_slope_scale,
|
||||
depth_bias_clamp: self.depth_bias_clamp,
|
||||
}),
|
||||
primitive_topology: self.primitive_topology,
|
||||
color_states: &self.color_states,
|
||||
depth_stencil_state: self.depth_stencil_state.clone(),
|
||||
vertex_state: wgpu::VertexStateDescriptor {
|
||||
index_format: self.index_format,
|
||||
vertex_buffers: &self.vertex_buffers,
|
||||
},
|
||||
sample_count: self.sample_count,
|
||||
sample_mask: self.sample_mask,
|
||||
alpha_to_coverage_enabled: self.alpha_to_coverage_enabled,
|
||||
}
|
||||
);
|
||||
Ok(pipeline)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn create_shader_module(device: &wgpu::Device, spirv: &[u8]) -> wgpu::ShaderModule {
|
||||
device.create_shader_module(
|
||||
&wgpu::read_spirv(
|
||||
std::io::Cursor::new(
|
||||
spirv
|
||||
)
|
||||
).unwrap()
|
||||
)
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec2 v_tex_coords;
|
||||
layout(location=1) in vec3 v_position; // UPDATED!
|
||||
layout(location=2) in vec3 v_light_position; // NEW!
|
||||
layout(location=3) in vec3 v_view_position; // NEW!
|
||||
|
||||
layout(location=0) out vec4 f_color;
|
||||
|
||||
layout(set = 0, binding = 0) uniform texture2D t_diffuse;
|
||||
layout(set = 0, binding = 1) uniform sampler s_diffuse;
|
||||
layout(set = 0, binding = 2) uniform texture2D t_normal;
|
||||
layout(set = 0, binding = 3) uniform sampler s_normal;
|
||||
|
||||
layout(set = 2, binding = 0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
vec4 object_color = texture(sampler2D(t_diffuse, s_diffuse), v_tex_coords);
|
||||
vec4 object_normal = texture(sampler2D(t_normal, s_normal), v_tex_coords);
|
||||
|
||||
float ambient_strength = 0.1;
|
||||
vec3 ambient_color = light_color * ambient_strength;
|
||||
|
||||
vec3 normal = normalize(object_normal.rgb * 2.0 - 1.0); // UPDATED!
|
||||
vec3 light_dir = normalize(v_light_position - v_position); // UPDATED!
|
||||
|
||||
float diffuse_strength = max(dot(normal, light_dir), 0.0);
|
||||
vec3 diffuse_color = light_color * diffuse_strength;
|
||||
|
||||
vec3 view_dir = normalize(v_view_position - v_position); // UPDATED!
|
||||
vec3 half_dir = normalize(view_dir + light_dir);
|
||||
float specular_strength = pow(max(dot(normal, half_dir), 0.0), 32);
|
||||
vec3 specular_color = specular_strength * light_color;
|
||||
|
||||
vec3 result = (ambient_color + diffuse_color + specular_color) * object_color.xyz;
|
||||
f_color = vec4(result, object_color.a);
|
||||
}
|
Binary file not shown.
@ -0,0 +1,57 @@
|
||||
#version 450
|
||||
|
||||
layout(location=0) in vec3 a_position;
|
||||
layout(location=1) in vec2 a_tex_coords;
|
||||
layout(location=2) in vec3 a_normal;
|
||||
layout(location=3) in vec3 a_tangent;
|
||||
layout(location=4) in vec3 a_bitangent;
|
||||
|
||||
layout(location=0) out vec2 v_tex_coords;
|
||||
layout(location=1) out vec3 v_position; // UPDATED!
|
||||
layout(location=2) out vec3 v_light_position; // NEW!
|
||||
layout(location=3) out vec3 v_view_position; // NEW!
|
||||
|
||||
layout(set=1, binding=0)
|
||||
uniform Uniforms {
|
||||
vec3 u_view_position;
|
||||
mat4 u_view_proj;
|
||||
};
|
||||
|
||||
layout(set=1, binding=1)
|
||||
buffer Instances {
|
||||
mat4 s_models[];
|
||||
};
|
||||
|
||||
// NEW!
|
||||
layout(set=2, binding=0) uniform Light {
|
||||
vec3 light_position;
|
||||
vec3 light_color;
|
||||
};
|
||||
|
||||
void main() {
|
||||
v_tex_coords = a_tex_coords;
|
||||
|
||||
mat4 model_matrix = s_models[gl_InstanceIndex];
|
||||
|
||||
mat3 normal_matrix = mat3(transpose(inverse(model_matrix)));
|
||||
vec3 normal = normalize(normal_matrix * a_normal);
|
||||
vec3 tangent = normalize(normal_matrix * a_tangent);
|
||||
vec3 bitangent = normalize(normal_matrix * a_bitangent);
|
||||
|
||||
// UDPATED!
|
||||
mat3 tangent_matrix = transpose(mat3(
|
||||
tangent,
|
||||
bitangent,
|
||||
normal
|
||||
));
|
||||
|
||||
vec4 model_space = model_matrix * vec4(a_position, 1.0);
|
||||
v_position = model_space.xyz;
|
||||
|
||||
// NEW!
|
||||
v_position = tangent_matrix * model_space.xyz;
|
||||
v_light_position = tangent_matrix * light_position;
|
||||
v_view_position = tangent_matrix * u_view_position;
|
||||
|
||||
gl_Position = u_view_proj * model_space;
|
||||
}
|
Binary file not shown.
@ -0,0 +1,287 @@
|
||||
use image::GenericImageView;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::pipeline;
|
||||
|
||||
pub struct Texture {
|
||||
pub texture: wgpu::Texture,
|
||||
pub view: wgpu::TextureView,
|
||||
pub sampler: wgpu::Sampler,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
|
||||
|
||||
pub fn load<P: AsRef<Path>>(
|
||||
device: &wgpu::Device,
|
||||
path: P,
|
||||
is_normal_map: bool,
|
||||
) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
|
||||
// Needed to appease the borrow checker
|
||||
let path_copy = path.as_ref().to_path_buf();
|
||||
let label = path_copy.to_str();
|
||||
|
||||
let img = image::open(path)?;
|
||||
Self::from_image(device, &img, label, is_normal_map)
|
||||
}
|
||||
|
||||
pub fn create_depth_texture(device: &wgpu::Device, sc_desc: &wgpu::SwapChainDescriptor, label: &str) -> Self {
|
||||
let size = wgpu::Extent3d {
|
||||
width: sc_desc.width,
|
||||
height: sc_desc.height,
|
||||
depth: 1,
|
||||
};
|
||||
let desc = wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
array_layer_count: 1,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: Self::DEPTH_FORMAT,
|
||||
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT
|
||||
| wgpu::TextureUsage::SAMPLED
|
||||
| wgpu::TextureUsage::COPY_SRC,
|
||||
};
|
||||
let texture = device.create_texture(&desc);
|
||||
|
||||
let view = texture.create_default_view();
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
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,
|
||||
lod_min_clamp: -100.0,
|
||||
lod_max_clamp: 100.0,
|
||||
compare: wgpu::CompareFunction::LessEqual,
|
||||
});
|
||||
|
||||
Self { texture, view, sampler }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_bytes(
|
||||
device: &wgpu::Device,
|
||||
bytes: &[u8],
|
||||
label: &str,
|
||||
is_normal_map: bool,
|
||||
) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
|
||||
let img = image::load_from_memory(bytes)?;
|
||||
Self::from_image(device, &img, Some(label), is_normal_map)
|
||||
}
|
||||
|
||||
pub fn from_image(
|
||||
device: &wgpu::Device,
|
||||
img: &image::DynamicImage,
|
||||
label: Option<&str>,
|
||||
is_normal_map: bool,
|
||||
) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
|
||||
let rgba = img.to_rgba();
|
||||
let dimensions = img.dimensions();
|
||||
|
||||
let size = wgpu::Extent3d {
|
||||
width: dimensions.0,
|
||||
height: dimensions.1,
|
||||
depth: 1,
|
||||
};
|
||||
// Ideally this number should be related to the size of
|
||||
// the image, but let's keep things simple for now.
|
||||
let mip_level_count = 4;
|
||||
let texture_desc = wgpu::TextureDescriptor {
|
||||
label,
|
||||
size,
|
||||
array_layer_count: 1,
|
||||
mip_level_count, // UPDATED!
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: if is_normal_map {
|
||||
wgpu::TextureFormat::Rgba8Unorm
|
||||
} else {
|
||||
wgpu::TextureFormat::Rgba8UnormSrgb
|
||||
},
|
||||
usage: wgpu::TextureUsage::SAMPLED
|
||||
// Needed for to make the mip maps.
|
||||
| wgpu::TextureUsage::OUTPUT_ATTACHMENT
|
||||
| wgpu::TextureUsage::COPY_DST,
|
||||
};
|
||||
let texture = device.create_texture(&texture_desc);
|
||||
|
||||
let buffer = device.create_buffer_with_data(
|
||||
&rgba,
|
||||
wgpu::BufferUsage::COPY_SRC,
|
||||
);
|
||||
|
||||
let mut encoder = device.create_command_encoder(
|
||||
&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("texture_buffer_copy_encoder"),
|
||||
}
|
||||
);
|
||||
|
||||
encoder.copy_buffer_to_texture(
|
||||
wgpu::BufferCopyView {
|
||||
buffer: &buffer,
|
||||
offset: 0,
|
||||
bytes_per_row: 4 * dimensions.0,
|
||||
rows_per_image: dimensions.1,
|
||||
},
|
||||
wgpu::TextureCopyView {
|
||||
texture: &texture,
|
||||
mip_level: 0,
|
||||
array_layer: 0,
|
||||
origin: wgpu::Origin3d::ZERO,
|
||||
},
|
||||
size,
|
||||
);
|
||||
|
||||
// Make sure to do this after you've copied the buffer
|
||||
// to the texture, other wise your mipmaps will be black.
|
||||
Self::generate_mipmaps(
|
||||
&mut encoder,
|
||||
&device,
|
||||
&texture,
|
||||
&texture_desc,
|
||||
mip_level_count
|
||||
);
|
||||
|
||||
let cmd_buffer = encoder.finish();
|
||||
|
||||
let view = texture.create_default_view();
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
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::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
lod_min_clamp: -100.0,
|
||||
lod_max_clamp: 100.0,
|
||||
compare: wgpu::CompareFunction::Always,
|
||||
});
|
||||
|
||||
Ok((Self { texture, view, sampler }, cmd_buffer))
|
||||
}
|
||||
|
||||
pub fn generate_mipmaps(
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
device: &wgpu::Device,
|
||||
texture: &wgpu::Texture,
|
||||
texture_desc: &wgpu::TextureDescriptor,
|
||||
mip_count: u32,
|
||||
) {
|
||||
let bind_group_layout = device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
bindings: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::SampledTexture {
|
||||
multisampled: false,
|
||||
component_type: wgpu::TextureComponentType::Float,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
}
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStage::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler {
|
||||
comparison: false,
|
||||
}
|
||||
}
|
||||
],
|
||||
label: None,
|
||||
}
|
||||
);
|
||||
let pipeline_layout = device.create_pipeline_layout(
|
||||
&wgpu::PipelineLayoutDescriptor {
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
},
|
||||
);
|
||||
|
||||
// This pipeline will render out a texture to another texture.
|
||||
// We create the mipmaps by rendering to increasingly smaller
|
||||
// textures.
|
||||
let blit_pipeline = pipeline::RenderPipelineBuilder::new()
|
||||
.layout(&pipeline_layout)
|
||||
.color_solid(texture_desc.format)
|
||||
.vertex_shader(include_bytes!("blit.vert.spv"))
|
||||
.fragment_shader(include_bytes!("blit.frag.spv"))
|
||||
// Using wgpu::TriangleStrip makes our lives easier in the shader.
|
||||
.primitive_topology(wgpu::PrimitiveTopology::TriangleStrip)
|
||||
.build(device).unwrap();
|
||||
|
||||
// This sampler ensures that the smaller textures get the right
|
||||
// color data.
|
||||
let sampler = device.create_sampler(
|
||||
&wgpu::SamplerDescriptor {
|
||||
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::Nearest,
|
||||
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||
// Since we are using this sampler to generate mipmaps,
|
||||
// we don't need it the use level of detail values.
|
||||
lod_min_clamp: 0.0,
|
||||
lod_max_clamp: 0.0,
|
||||
compare: wgpu::CompareFunction::Always,
|
||||
}
|
||||
);
|
||||
|
||||
// Create a view for every mip level.
|
||||
let views = (0..mip_count).map(|mip| {
|
||||
texture.create_view(
|
||||
&wgpu::TextureViewDescriptor {
|
||||
format: texture_desc.format,
|
||||
dimension: wgpu::TextureViewDimension::D2,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
base_mip_level: mip,
|
||||
level_count: 1,
|
||||
base_array_layer: 0,
|
||||
array_layer_count: 1,
|
||||
}
|
||||
)
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
// Skip the first view, as that is the base one
|
||||
for target_mip in 1..mip_count as usize {
|
||||
let bind_group = device.create_bind_group(
|
||||
&wgpu::BindGroupDescriptor {
|
||||
layout: &bind_group_layout,
|
||||
bindings: &[
|
||||
wgpu::Binding {
|
||||
binding: 0,
|
||||
// Bind to the view before this one
|
||||
resource: wgpu::BindingResource::TextureView(&views[target_mip - 1]),
|
||||
},
|
||||
wgpu::Binding {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
}
|
||||
],
|
||||
label: None,
|
||||
},
|
||||
);
|
||||
|
||||
let mut pass = encoder.begin_render_pass(
|
||||
&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[
|
||||
wgpu::RenderPassColorAttachmentDescriptor {
|
||||
attachment: &views[target_mip],
|
||||
resolve_target: None,
|
||||
clear_color: wgpu::Color::WHITE,
|
||||
load_op: wgpu::LoadOp::Clear,
|
||||
store_op: wgpu::StoreOp::Store,
|
||||
}
|
||||
],
|
||||
depth_stencil_attachment: None,
|
||||
},
|
||||
);
|
||||
|
||||
pass.set_pipeline(&blit_pipeline);
|
||||
pass.set_bind_group(0, &bind_group, &[]);
|
||||
pass.draw(0..4, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit be55cbc2e970ebf2e90d44448682a312aa8e8809
|
||||
Subproject commit 31aebd26ce4ea8f8a8b162b03f93ec1eef77a20a
|
Loading…
Reference in New Issue