pull/94/head
Ben Hansen 4 years ago
parent 041a230d0d
commit ad5cd8e12f

@ -150,7 +150,7 @@ impl CameraController {
}
pub fn process_scroll(&mut self, delta: &MouseScrollDelta) {
self.scroll = -match delta {
self.scroll = match delta {
// I'm assuming a line is about 100 pixels
MouseScrollDelta::LineDelta(_, scroll) => scroll * 100.0,
MouseScrollDelta::PixelDelta(LogicalPosition {
@ -177,6 +177,7 @@ impl CameraController {
let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos();
let scrollward = Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize();
camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt;
self.scroll = 0.0;
// Move up/down. Since we don't use roll, we can just
// modify the y coordinate directly.

@ -333,12 +333,14 @@ impl State {
});
let res_dir = std::path::Path::new(env!("OUT_DIR")).join("res");
let now = std::time::Instant::now();
let obj_model = model::Model::load(
&device,
&queue,
&texture_bind_group_layout,
res_dir.join("cube.obj"),
).unwrap();
println!("Elapsed (Original): {:?}", std::time::Instant::now() - now);
let light = Light {
position: (2.0, 2.0, 2.0).into(),

@ -22,7 +22,6 @@ use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder};
pub struct Display {
_adapter: wgpu::Adapter,
surface: wgpu::Surface,
pub sc_desc: wgpu::SwapChainDescriptor,
pub swap_chain: wgpu::SwapChain,
@ -35,13 +34,13 @@ impl Display {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
let surface = unsafe { instance.create_surface(window) };
let _adapter = instance.request_adapter(
let adapter = instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::Default,
compatible_surface: Some(&surface),
},
).await.unwrap();
let (device, queue) = _adapter.request_device(
let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
@ -59,7 +58,6 @@ impl Display {
let swap_chain = device.create_swap_chain(&surface, &sc_desc);
Ok(Self {
_adapter,
surface,
sc_desc,
swap_chain,

@ -53,13 +53,16 @@ Now let's create the buffer in `new()`.
```rust
// new()
let vertex_buffer = device.create_buffer_with_data(
bytemuck::cast_slice(VERTICES),
wgpu::BufferUsage::VERTEX,
let vertex_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(VERTICES),
usage: wgpu::BufferUsage::VERTEX,
}
);
```
You'll note that we're using [bytemuck](https://docs.rs/bytemuck/1.2.0/bytemuck/) to cast our `VERTICES`. The `create_buffer_with_data()` method expects a `&[u8]`, and `bytemuck::cast_slice` does that for us. Add the following to your `Cargo.toml`.
You'll note that we're using [bytemuck](https://docs.rs/bytemuck/1.2.0/bytemuck/) to cast our `VERTICES`. The `create_buffer_init()` method expects a `&[u8]`, and `bytemuck::cast_slice` does that for us. Add the following to your `Cargo.toml`.
```toml
bytemuck = "1.4"

@ -77,9 +77,12 @@ queue.write_texture(
The old way of writing data to a texture was to copy the pixel data to a buffer, and then copy it to the texture. Using `write_texture` is a bit more efficient as it uses one less buffer. I'll leave it here though in case you need it.
```rust
let buffer = device.create_buffer_with_data(
&diffuse_rgba,
wgpu::BufferUsage::COPY_SRC,
let buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Temp Buffer"),
&diffuse_rgba,
wgpu::BufferUsage::COPY_SRC,
}
);
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {

@ -45,9 +45,12 @@ let light = Light {
};
// We'll want to update our lights position, so we use COPY_DST
let light_buffer = device.create_buffer_with_data(
bytemuck::cast_slice(&[light]),
wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
let light_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Light VB"),
contents: bytemuck::cast_slice(&[light]),
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
}
);
```
@ -94,13 +97,10 @@ Let's also update the lights position in the `update()` method, so we can see wh
```rust
// 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(1.0)) * 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.light.position =
cgmath::Quaternion::from_axis_angle((0.0, 1.0, 0.0).into(), cgmath::Deg(1.0))
* old_position;
self.queue.write_buffer(&self.light_buffer, 0, bytemuck::cast_slice(&[self.light]));
```
This will have the light rotate around the origin one degree every frame.

@ -208,6 +208,7 @@ impl CameraController {
let (pitch_sin, pitch_cos) = camera.pitch.0.sin_cos();
let scrollward = Vector3::new(pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin).normalize();
camera.position += scrollward * self.scroll * self.speed * self.sensitivity * dt;
self.scroll = 0.0;
// Move up/down. Since we don't use roll, we can just
// modify the y coordinate directly.

@ -4,7 +4,9 @@ The main selling point of Vulkan, DirectX 12, Metal, and by extension Wgpu is th
<div class="note">
I won't go into what threads are in this tutorial. That is a course in and of itself.
I won't go into what threads are in this tutorial. That is a course in and of itself. All we'll be covering is using threading to make loading resources faster.
We won't go over multithreading rendering as we don't have enough different types of objects to justify that yet. This will change in a coming tutorial
</div>
@ -66,6 +68,8 @@ let shaders = shader_paths.into_par_iter()
.collect::<Result<Vec<_>>>();
```
Super simple isn't it? By using `into_par_iter`, `rayon` will try to spread our shader loading across multiple threads if it can. This means that our build script will load the shader text source for multiple shaders at the same time. This has the potential to drastically reduce our build times.
We can compare the speeds of our compilation by running `cargo build` on both this tutorial and the previous one.
```bash
@ -85,4 +89,110 @@ I got these build speeds after building the project one time to get `rayon` inst
</div>
## Parallelizing loading models and textures
## Parallelizing loading models and textures
Currently we load the materials and meshes of our model one at a time. This is a perfect opportunity for multithreading! All our changes will be in `model.rs`. Let's first start with the materials. We'll convert the regular for loop into a `par_iter().map()`.
```rust
// model.rs
impl Model {
pub fn load<P: AsRef<Path>>(
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
path: P,
) -> Result<Self> {
// ...
// UPDATED!
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>>>()?;
// ...
}
// ...
}
```
Next we can update the meshes to be loaded in parallel.
```rust
impl Model {
pub fn load<P: AsRef<Path>>(
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
path: P,
) -> Result<Self> {
// ...
// UPDATED!
let meshes = obj_models.par_iter().map(|m| {
let mut 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],
].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(),
}
}).collect::<Vec<_>>();
// ...
}
// ...
}
// ...
}
```
We've parallelized loading the meshes, and making the vertex array for them. Propably a bit overkill, but `rayon` should prevent us from using too many threads.
<div class="note">
You'll notice that we didn't use `rayon` for calculating the tangent, and bitangent. I tried to get it to work, but I was having trouble finding a way to do it without multiple mutable references to `vertices`. I don't feel like introducing a `std::sync::Mutex`, so I'll leave it for now.
This is honestly a better job for a compute shader, as the model data is going to get loaded into a buffer anyway.
</div>
## It's that easy!
Most of the `wgpu` types are `Send + Sync`, so we can use them in threads without much trouble. It was so easy, that I feel like this tutorial is too short! I'll just leave off with a speed comparison between the previous model loading code and the current code.
```
Elapsed (Original): 309.596382ms
Elapsed (Threaded): 199.645027ms
```
We're not loading that many resources, so the speed up is minimal. We'll be doing more stuff with threading, but this is a good introduction.
<AutoGithubLink/>
Loading…
Cancel
Save