started instancing examples

pull/81/head
Ben Hansen 4 years ago
parent 252ca8b909
commit 0cd250d5fe

77
Cargo.lock generated

@ -58,6 +58,11 @@ name = "android_log-sys"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "anyhow"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "approx"
version = "0.3.2"
@ -153,7 +158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bytemuck"
version = "1.2.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -175,7 +180,7 @@ dependencies = [
name = "camera"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -549,15 +554,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "framework"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.22.5 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.23.7 (registry+https://github.com/rust-lang/crates.io-index)",
"shaderc 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tobj 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tobj 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"wgpu 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winit 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winit 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -838,11 +846,9 @@ dependencies = [
"framework 0.1.0",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.22.5 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.23.7 (registry+https://github.com/rust-lang/crates.io-index)",
"shaderc 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tobj 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
"wgpu 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winit 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -928,7 +934,7 @@ name = "image"
version = "0.23.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"gif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
"jpeg-decoder 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
@ -977,6 +983,26 @@ dependencies = [
"adler32 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "instancing"
version = "0.1.0"
dependencies = [
"anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"framework 0.1.0",
"fs_extra 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.23.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"shaderc 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tobj 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"wgpu 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winit 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "instant"
version = "0.1.6"
@ -1471,7 +1497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "performance"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1540,7 +1566,7 @@ dependencies = [
name = "pong"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2069,12 +2095,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "tobj"
version = "0.1.12"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "tobj"
version = "1.0.0"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -2101,7 +2127,7 @@ dependencies = [
name = "tutorial10-lighting"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2116,7 +2142,7 @@ dependencies = [
name = "tutorial11-normals"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2131,7 +2157,7 @@ dependencies = [
name = "tutorial12-camera"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2170,7 +2196,7 @@ dependencies = [
name = "tutorial4-buffer"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"image 0.22.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2183,7 +2209,7 @@ dependencies = [
name = "tutorial5-textures"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2197,7 +2223,7 @@ dependencies = [
name = "tutorial6-uniforms"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2211,7 +2237,7 @@ dependencies = [
name = "tutorial7-instancing"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2225,7 +2251,7 @@ dependencies = [
name = "tutorial8-depth"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2240,7 +2266,7 @@ dependencies = [
name = "tutorial9-models"
version = "0.1.0"
dependencies = [
"bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2665,6 +2691,7 @@ dependencies = [
"checksum andrew 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b7f09f89872c2b6b29e319377b1fbe91c6f5947df19a25596e121cf19a7b35e"
"checksum android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407"
"checksum android_log-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b8052e2d8aabbb8d556d6abbcce2a22b9590996c5f849b9c7ce4544a2e3b984e"
"checksum anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
"checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
"checksum ash 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c69a8137596e84c22d57f3da1b5de1d4230b1742a710091c85f4d7ce50f00f38"
@ -2677,7 +2704,7 @@ dependencies = [
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
"checksum bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
"checksum bytemuck 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431"
"checksum bytemuck 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "db7a1029718df60331e557c9e83a55523c955e5dd2a7bfeffad6bbd50b538ae9"
"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
"checksum calloop 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7aa2097be53a00de9e8fc349fea6d76221f398f5c4fa550d420669906962d160"
"checksum cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
@ -2886,8 +2913,8 @@ dependencies = [
"checksum tiff 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b7c2cfc4742bd8a32f2e614339dd8ce30dbcf676bb262bd63a2327bc5df57d"
"checksum tiff 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f"
"checksum tinyvec 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed"
"checksum tobj 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "fe69d3e3b94bf71dfd868eb38b0830917bb177a7b32b58cab7ca72691aba7729"
"checksum tobj 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b9d0bafde95a2f8f50dd3c10bc127b462efa8c3c0b60dfa276b7e032100e21d"
"checksum tobj 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6172100cd5b17cdd085c94f261e31101ca31886c86a2337a6687dac6d2fb3cf1"
"checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
"checksum twox-hash 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bfd5b7557925ce778ff9b9ef90e3ade34c524b5ff10e239c69a42d546d2af56"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"

@ -170,6 +170,7 @@ impl State {
let surface = wgpu::Surface::create(window);
let adapter = wgpu::Adapter::request(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::Default,

@ -1,44 +0,0 @@
#version 450
layout(location=0) in vec3 a_position;
layout(location=1) in vec2 a_tex_coords;
layout(location=2) in vec3 a_normal;
// NEW!
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 mat3 v_tangent_matrix; // 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[];
};
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);
v_tangent_matrix = mat3(
tangent,
bitangent,
normal
);
vec4 model_space = model_matrix * vec4(a_position, 1.0);
v_position = model_space.xyz;
gl_Position = u_view_proj * model_space;
}

@ -7,12 +7,20 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = "0.22.4"
winit = "0.20.0"
shaderc = "0.6"
cgmath = "0.17.0"
anyhow = "1.0"
bytemuck = "1.3"
image = "0.23"
failure = "0.1"
futures = "0.3.4"
tobj = "1.0.0"
wgpu = "0.5.0"
bytemuck = "1.2.0"
futures = "0.3"
tobj = "2"
winit = "0.22"
wgpu = "0.5"
[dependencies.cgmath]
version = "0.17"
features = ["swizzle"]
[build-dependencies]
fs_extra = "1.1"
glob = "0.3"
shaderc = "0.6"

@ -28,7 +28,7 @@ impl<R: Copy + bytemuck::Pod + bytemuck::Zeroable> RawBuffer<R> {
Self::from_parts(buffer, data, usage)
}
pub fn from_parts(buffer: wgpu::Buffer, data: Vec<R>, usage: wgpu::BufferUsage) -> Self {
pub fn from_parts(buffer: wgpu::Buffer, data: Vec<R>, _usage: wgpu::BufferUsage) -> Self {
Self { buffer, data }
}
@ -40,7 +40,7 @@ impl<R: Copy + bytemuck::Pod + bytemuck::Zeroable> RawBuffer<R> {
pub struct Buffer<U: ToRaw<Output=R>, R: Copy + bytemuck::Pod + bytemuck::Zeroable> {
pub data: Vec<U>,
pub raw_buffer: RawBuffer<R>,
usage: wgpu::BufferUsage,
pub usage: wgpu::BufferUsage,
}
impl<U: ToRaw<Output=R>, R: Copy + bytemuck::Pod + bytemuck::Zeroable> Buffer<U, R> {

@ -1,25 +1,220 @@
use cgmath::*;
use winit::event::*;
use winit::dpi::LogicalPosition;
use std::time::Duration;
use std::f32::consts::FRAC_PI_2;
#[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, 1.0, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.0, 0.0, 0.5, 1.0,
);
pub fn camera_setup<
V: Into<Point3<f32>>,
Y: Into<Rad<f32>>,
P: Into<Rad<f32>>,
>(
position: V,
yaw: Y,
pitch: P,
width: u32,
height: u32,
) -> (Camera, Projection, CameraController) {
(
Camera::new(position, yaw, pitch),
Projection::new(width, height, Deg(45.0), 0.1, 100.0),
CameraController::new(4.0, 0.4),
)
}
#[derive(Debug)]
pub struct Camera {
eye: cgmath::Point3<f32>,
target: cgmath::Point3<f32>,
up: cgmath::Vector3<f32>,
pub position: Point3<f32>,
yaw: Rad<f32>,
pitch: Rad<f32>,
}
impl Camera {
pub fn new<
V: Into<Point3<f32>>,
Y: Into<Rad<f32>>,
P: Into<Rad<f32>>,
>(
position: V,
yaw: Y,
pitch: P,
) -> Self {
Self {
position: position.into(),
yaw: yaw.into(),
pitch: pitch.into(),
}
}
pub fn calc_matrix(&self) -> Matrix4<f32> {
Matrix4::look_at_dir(
self.position,
Vector3::new(
self.yaw.0.cos(),
self.pitch.0.sin(),
self.yaw.0.sin(),
).normalize(),
Vector3::unit_y(),
)
}
}
pub struct Projection {
aspect: f32,
fovy: f32,
fovy: Rad<f32>,
znear: f32,
zfar: f32,
}
impl Camera {
pub 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;
impl Projection {
pub fn new<F: Into<Rad<f32>>>(
width: u32,
height: u32,
fovy: F,
znear: f32,
zfar: f32,
) -> Self {
Self {
aspect: width as f32 / height as f32,
fovy: fovy.into(),
znear,
zfar,
}
}
pub fn resize(&mut self, width: u32, height: u32) {
self.aspect = width as f32 / height as f32;
}
pub fn calc_matrix(&self) -> Matrix4<f32> {
OPENGL_TO_WGPU_MATRIX * perspective(self.fovy, self.aspect, self.znear, self.zfar)
}
}
#[derive(Debug)]
pub struct CameraController {
amount_left: f32,
amount_right: f32,
amount_forward: f32,
amount_backward: f32,
amount_up: f32,
amount_down: f32,
rotate_horizontal: f32,
rotate_vertical: f32,
scroll: f32,
speed: f32,
sensitivity: f32,
}
impl CameraController {
pub fn new(speed: f32, sensitivity: f32) -> Self {
Self {
amount_left: 0.0,
amount_right: 0.0,
amount_forward: 0.0,
amount_backward: 0.0,
amount_up: 0.0,
amount_down: 0.0,
rotate_horizontal: 0.0,
rotate_vertical: 0.0,
scroll: 0.0,
speed,
sensitivity,
}
}
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool{
let amount = if state == ElementState::Pressed { 1.0 } else { 0.0 };
match key {
VirtualKeyCode::W | VirtualKeyCode::Up => {
self.amount_forward = amount;
true
}
VirtualKeyCode::S | VirtualKeyCode::Down => {
self.amount_backward = amount;
true
}
VirtualKeyCode::A | VirtualKeyCode::Left => {
self.amount_left = amount;
true
}
VirtualKeyCode::D | VirtualKeyCode::Right => {
self.amount_right = amount;
true
}
VirtualKeyCode::Space => {
self.amount_up = amount;
true
}
VirtualKeyCode::LShift => {
self.amount_down = amount;
true
}
_ => false,
}
}
pub fn process_mouse(&mut self, mouse_dx: f64, mouse_dy: f64) {
self.rotate_horizontal = mouse_dx as f32;
self.rotate_vertical = mouse_dy as f32;
}
pub fn process_scroll(&mut self, delta: &MouseScrollDelta) {
self.scroll = -match delta {
// I'm assuming a line is about 100 pixels
MouseScrollDelta::LineDelta(_, scroll) => scroll * 100.0,
MouseScrollDelta::PixelDelta(LogicalPosition {
y: scroll,
..
}) => *scroll as f32,
};
}
pub fn update_camera(&mut self, camera: &mut Camera, dt: Duration) {
let dt = dt.as_secs_f32();
// Move forward/backward and left/right
let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos();
let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize();
let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize();
camera.position += forward * (self.amount_forward - self.amount_backward) * self.speed * dt;
camera.position += right * (self.amount_right - self.amount_left) * self.speed * dt;
// Move in/out (aka. "zoom")
// Note: this isn't an actual zoom. The camera's position
// changes when zooming. I've added this to make it easier
// to get closer to an object you want to focus on.
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;
// Move up/down. Since we don't use roll, we can just
// modify the y coordinate directly.
camera.position.y += (self.amount_up - self.amount_down) * self.speed * dt;
// Rotate
camera.yaw += Rad(self.rotate_horizontal) * self.sensitivity * dt;
camera.pitch += Rad(-self.rotate_vertical) * self.sensitivity * dt;
// If process_mouse isn't called every frame, these values
// will not get set to zero, and the camera will rotate
// when moving in a non cardinal direction.
self.rotate_horizontal = 0.0;
self.rotate_vertical = 0.0;
// Keep the camera's angle from going too high/low.
if camera.pitch < -Rad(FRAC_PI_2) {
camera.pitch = -Rad(FRAC_PI_2);
} else if camera.pitch > Rad(FRAC_PI_2) {
camera.pitch = Rad(FRAC_PI_2);
}
}
}

@ -1,9 +1,254 @@
mod buffer;
mod camera;
mod light;
mod model;
mod pipeline;
mod texture;
pub mod prelude;
pub use buffer::*;
pub use camera::*;
pub use light::*;
pub use model::*;
pub use texture::*;
pub use pipeline::*;
pub use texture::*;
use anyhow::*;
use cgmath::*;
use std::time::{Duration, Instant};
use winit::event::*;
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,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
}
impl Display {
pub async fn new(window: &Window) -> Result<Self, Error> {
let size = window.inner_size();
let surface = wgpu::Surface::create(window);
let _adapter: wgpu::Adapter = wgpu::Adapter::request(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::Default,
compatible_surface: Some(&surface),
},
wgpu::BackendBit::PRIMARY,
).await.context("Unable to find valid device!")?;
let (device, queue) = _adapter.request_device(&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);
Ok(Self {
_adapter,
surface,
sc_desc,
swap_chain,
device,
queue,
})
}
pub fn resize(&mut self, width: u32, height: u32) {
self.sc_desc.width = width;
self.sc_desc.height = height;
self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
}
}
/**
* Holds the camera data to be passed to wgpu.
*/
#[repr(C)]
#[derive(Copy, Clone)]
pub struct UniformData {
view_position: cgmath::Vector4<f32>,
view_proj: cgmath::Matrix4<f32>,
}
unsafe impl bytemuck::Zeroable for UniformData {}
unsafe impl bytemuck::Pod for UniformData {}
pub struct Uniforms {
data: UniformData,
buffer: wgpu::Buffer,
}
impl Uniforms {
pub fn new(device: &wgpu::Device) -> Self {
let data = UniformData {
view_position: Zero::zero(),
view_proj: cgmath::Matrix4::identity(),
};
let buffer = device.create_buffer_with_data(
bytemuck::cast_slice(&[data]),
wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::UNIFORM,
);
Self { data, buffer }
}
pub fn update_view_proj(&mut self, camera: &camera::Camera, projection: &camera::Projection) {
self.data.view_position = camera.position.to_homogeneous();
self.data.view_proj = projection.calc_matrix() * camera.calc_matrix()
}
pub fn update_buffer(&self, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder) {
let staging_buffer = device.create_buffer_with_data(
bytemuck::cast_slice(&[self.data]),
wgpu::BufferUsage::COPY_SRC,
);
encoder.copy_buffer_to_buffer(
&staging_buffer,
0,
&self.buffer,
0,
std::mem::size_of::<UniformData>() as _,
);
}
}
/**
* Holds the wgpu::BindGroupLayout and one wgpu::BindGroup for the
* just the Uniforms struct.
*/
pub struct UniformBinding {
pub layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup,
}
impl UniformBinding {
pub fn new(device: &wgpu::Device, uniforms: &Uniforms) -> Self {
let 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: Some("UniformBinding::layout"),
}
);
let bind_group = device.create_bind_group(
&wgpu::BindGroupDescriptor {
layout: &layout,
bindings: &[
wgpu::Binding {
binding: 0,
resource: wgpu::BindingResource::Buffer {
buffer: &uniforms.buffer,
range: 0..std::mem::size_of_val(&uniforms.data) as _,
},
},
],
label: Some("UniformBinding::bind_group")
}
);
Self { layout, bind_group }
}
pub fn rebind(&mut self, device: &wgpu::Device, uniforms: &Uniforms) {
self.bind_group = device.create_bind_group(
&wgpu::BindGroupDescriptor {
layout: &self.layout,
bindings: &[
wgpu::Binding {
binding: 0,
resource: wgpu::BindingResource::Buffer {
buffer: &uniforms.buffer,
range: 0..std::mem::size_of_val(&uniforms) as wgpu::BufferAddress,
},
},
],
label: Some("UniformBinding::bind_group")
}
);
}
}
pub trait Demo: 'static + Sized {
fn init(display: &Display) -> Result<Self, Error>;
fn process_mouse(&mut self, dx: f64, dy: f64);
fn resize(&mut self, display: &Display);
fn update(&mut self, display: &Display, dt: Duration);
fn render(&mut self, display: &mut Display);
}
pub async fn run<D: Demo>() -> Result<(), Error> {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title(env!("CARGO_PKG_NAME"))
.build(&event_loop)?;
let mut display = Display::new(&window).await?;
let mut demo = D::init(&mut display)?;
let mut last_update = Instant::now();
let mut is_resumed = true;
let mut is_focused = true;
event_loop.run(move |event, _, control_flow| {
*control_flow = if is_resumed && is_focused {
ControlFlow::Poll
} else {
ControlFlow::Wait
};
match event {
Event::Resumed => is_resumed = true,
Event::Suspended => is_resumed = false,
Event::RedrawRequested(wid) => if wid == window.id() {
let now = Instant::now();
let dt = now - last_update;
last_update = now;
demo.update(&mut display, dt);
demo.render(&mut display);
}
Event::MainEventsCleared => {
if is_focused && is_resumed {
window.request_redraw();
} else {
// Freeze time while the demo is not in the foreground
last_update = Instant::now();
}
}
Event::WindowEvent {
event,
window_id,
..
} => if window_id == window.id() {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Focused(f) => is_focused = f,
WindowEvent::ScaleFactorChanged {
new_inner_size,
..
} => {
display.resize(new_inner_size.width, new_inner_size.height);
demo.resize(&mut display);
}
WindowEvent::Resized(new_inner_size) => {
display.resize(new_inner_size.width, new_inner_size.height);
demo.resize(&mut display);
}
_ => {}
}
}
_ => {}
}
});
}

@ -0,0 +1,36 @@
use cgmath::*;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct LightData {
pub position: Vector4<f32>,
pub color: Vector4<f32>,
}
unsafe impl bytemuck::Pod for LightData {}
unsafe impl bytemuck::Zeroable for LightData {}
pub struct Light {
data: LightData,
buffer: wgpu::Buffer,
}
impl Light {
pub fn new(device: &wgpu::Device, position: Vector3<f32>, color: Vector3<f32>) -> Self {
let data = LightData {
position: Vector4::new(position.x, position.y, position.z, 1.0),
color: Vector4::new(color.x, color.y, color.z, 1.0),
};
let buffer = device.create_buffer_with_data(
bytemuck::cast_slice(&[data]),
wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::UNIFORM,
);
Self { data, buffer }
}
}
pub struct LightBinding {
pub layout: wgpu::BindGroupLayout,
pub bind_group: wgpu::BindGroup,
}

@ -1,5 +1,6 @@
use std::ops::Range;
use std::path::Path;
use anyhow::*;
use crate::texture;
@ -10,10 +11,13 @@ pub trait Vertex {
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct ModelVertex {
position: [f32; 3],
tex_coords: [f32; 2],
normal: [f32; 3],
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 {}
@ -39,6 +43,17 @@ impl Vertex for ModelVertex {
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,
},
],
}
}
@ -47,9 +62,50 @@ impl Vertex for ModelVertex {
pub struct Material<'a> {
pub name: String,
pub diffuse_texture: texture::Texture<'a>,
pub normal_texture: texture::Texture<'a>,
pub bind_group: wgpu::BindGroup,
}
impl<'a> Material<'a> {
pub fn new(
device: &wgpu::Device,
name: &str,
diffuse_texture: texture::Texture<'a>,
normal_texture: texture::Texture<'a>,
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,
@ -68,42 +124,36 @@ impl<'a> Model<'a> {
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())?;
) -> Result<(Self, Vec<wgpu::CommandBuffer>)> {
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().unwrap();
let containing_folder = path.as_ref()
.parent()
.context("Unable to find parent for model")?;
// 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 name = mat.name;
let diffuse_path = mat.diffuse_texture;
let (diffuse_texture, cmds) =
texture::Texture::load(&device, containing_folder.join(diffuse_path))?;
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),
},
],
label: None,
});
let (diffuse_texture, cmds) = texture::Texture::load(device, containing_folder.join(diffuse_path), false)?;
command_buffers.push(cmds);
let normal_path = mat.unknown_param.get("map_Bump")
.with_context(|| format!("No normal map specified for {}", name))?;
let (normal_texture, cmds) = texture::Texture::load(device, containing_folder.join(normal_path), true)?;
command_buffers.push(cmds);
materials.push(Material {
name: mat.name,
materials.push(Material::new(
device,
&name,
diffuse_texture,
bind_group,
});
command_buffers.push(cmds);
normal_texture,
layout,
));
}
let mut meshes = Vec::new();
@ -115,23 +165,76 @@ impl<'a> Model<'a> {
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]],
].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(&m.mesh.indices),
bytemuck::cast_slice(indices),
wgpu::BufferUsage::INDEX,
);
@ -181,6 +284,14 @@ where
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>
@ -234,6 +345,19 @@ where
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>
@ -317,3 +441,9 @@ where
}
}
}
pub struct ModelPass {
pipeline: wgpu::RenderPipeline,
// uniforms:
}

@ -0,0 +1,250 @@
use anyhow::*;
use crate::model::Vertex;
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<V: Vertex>(&mut self) -> &mut Self {
self.vertex_buffers.push(V::desc());
self
}
pub fn vertex_buffer_desc(&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> {
// 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.context("Please include a vertex shader")?);
// 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.context("Please include a fragment shader")?;
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 @@
pub use crate::model::{DrawLight, DrawModel};

@ -1,6 +1,7 @@
use image::GenericImageView;
use std::path::Path;
use std::mem;
use anyhow::*;
use crate::buffer;
@ -15,30 +16,19 @@ pub struct Texture<'a> {
impl<'a> Texture<'a> {
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
pub fn load<P: AsRef<Path>>(device: &wgpu::Device, path: P) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
pub fn load<P: AsRef<Path>>(
device: &wgpu::Device,
path: P,
is_normal_map: bool,
) -> Result<(Self, wgpu::CommandBuffer)> {
let img = image::open(path)?;
Self::from_image(device, &img)
Self::from_image(device, &img, is_normal_map)
}
pub fn create_depth_texture(device: &wgpu::Device, sc_desc: &wgpu::SwapChainDescriptor) -> Self {
let desc = wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: sc_desc.width,
height: sc_desc.height,
depth: 1,
},
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: Self::DEPTH_FORMAT,
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
};
Self::from_descriptor(device, desc)
}
pub fn from_descriptor(device: &wgpu::Device, desc: wgpu::TextureDescriptor<'a>) -> Self {
pub fn from_descriptor(
device: &wgpu::Device,
desc: wgpu::TextureDescriptor<'a>
) -> Self {
let texture = device.create_texture(&desc);
let view = texture.create_default_view();
@ -57,12 +47,20 @@ impl<'a> Texture<'a> {
Self { texture, view, sampler, desc }
}
pub fn from_bytes(device: &wgpu::Device, bytes: &[u8]) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
pub fn from_bytes(
device: &wgpu::Device,
is_normal_map: bool,
bytes: &[u8],
) -> Result<(Self, wgpu::CommandBuffer)> {
let img = image::load_from_memory(bytes)?;
Self::from_image(device, &img)
Self::from_image(device, &img, is_normal_map)
}
pub fn from_image(device: &wgpu::Device, img: &image::DynamicImage) -> Result<(Self, wgpu::CommandBuffer), failure::Error> {
pub fn from_image(
device: &wgpu::Device,
img: &image::DynamicImage,
is_normal_map: bool,
) -> Result<(Self, wgpu::CommandBuffer)> {
let rgba = img.to_rgba();
let dimensions = img.dimensions();
@ -77,7 +75,11 @@ impl<'a> Texture<'a> {
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
format: if is_normal_map {
wgpu::TextureFormat::Rgba8Unorm
} else {
wgpu::TextureFormat::Rgba8UnormSrgb
},
usage: wgpu::TextureUsage::SAMPLED | wgpu::TextureUsage::COPY_DST,
label: None,
};
@ -126,6 +128,24 @@ impl<'a> Texture<'a> {
Ok((Self { texture, view, sampler, desc }, cmd_buffer))
}
pub fn create_depth_texture(device: &wgpu::Device, sc_desc: &wgpu::SwapChainDescriptor) -> Self {
let desc = wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: sc_desc.width,
height: sc_desc.height,
depth: 1,
},
array_layer_count: 1,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: Self::DEPTH_FORMAT,
usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
};
Self::from_descriptor(device, desc)
}
pub fn prepare_buffer_rgba(&self, device: &wgpu::Device) -> buffer::RawBuffer<[f32;4]> {
let num_pixels = self.desc.size.width * self.desc.size.height * self.desc.size.depth;
@ -144,4 +164,4 @@ impl<'a> Texture<'a> {
raw_buffer
}
}
}

@ -7,14 +7,12 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = "0.22.4"
winit = "0.20.0"
image = "0.23"
shaderc = "0.6"
cgmath = "0.17.0"
failure = "0.1"
tobj = "0.1"
wgpu = "0.5.0"
futures = "0.3.4"
wgpu = "0.5"
futures = "0.3"
gif = "0.10.3"
framework = { path = "../framework" }

@ -153,21 +153,6 @@ fn save_gif(path: &str, frames: &mut Vec<Vec<u8>>, speed: i32, size: u16) -> Res
Ok(())
}
// The image crate currently doesn't support looping gifs, so I'm not using this
// code. I'm keeping it around in case image adds looping support.
#[allow(unused)]
fn save_gif_old(path: &str, frames: &mut Vec<Vec<u8>>, speed: i32, size: u16) -> Result<(), failure::Error> {
let output = std::fs::File::create(path)?;
let mut encoder = image::gif::Encoder::new(output);
for mut data in frames {
let frame = image::gif::Frame::from_rgba_speed(size, size, &mut data, speed);
encoder.encode(&frame)?;
}
Ok(())
}
fn create_render_pipeline(device: &wgpu::Device, target: &framework::Texture) -> wgpu::RenderPipeline {
let vs_src = include_str!("res/shader.vert");

@ -0,0 +1,32 @@
[package]
name = "instancing"
version = "0.1.0"
authors = ["Ben Hansen <bhbenjaminhansen@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"
bytemuck = "1.3"
image = "0.23"
framework = { path = "../framework"}
futures = "0.3"
rand = "0.7"
tobj = "2"
winit = "0.22"
wgpu = "0.5"
[dependencies.cgmath]
version = "0.17"
features = ["swizzle"]
[build-dependencies]
failure = "0.1"
fs_extra = "1.1"
glob = "0.3"
shaderc = "0.6"
[[bin]]
name = "instancing-storage-buffers"
path = "src/storage_buffers.rs"

@ -0,0 +1,88 @@
use glob::glob;
use failure::bail;
use fs_extra::copy_items;
use fs_extra::dir::CopyOptions;
use std::env;
use std::fs::{read_to_string, write};
use std::path::{PathBuf};
fn main() {
copy_res();
compile_shaders();
}
fn copy_res() {
// This tells cargo to rerun this script if something in /res/ changes.
println!("cargo:rerun-if-changed=res/*");
let out_dir = env::var("OUT_DIR").unwrap();
let mut copy_options = CopyOptions::new();
copy_options.overwrite = true;
let mut paths_to_copy = Vec::new();
paths_to_copy.push("res/");
copy_items(&paths_to_copy, out_dir, &copy_options).unwrap();
}
fn compile_shaders() {
// 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

@ -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);
}

@ -0,0 +1,27 @@
#version 450
layout(location=0) in vec3 a_position;
layout(location=0) out vec3 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 = u_color;
}

@ -2,7 +2,8 @@
layout(location=0) in vec2 v_tex_coords;
layout(location=1) in vec3 v_position; // UPDATED!
layout(location=2) in mat3 v_tangent_matrix; // NEW!
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;
@ -11,12 +12,6 @@ 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=1, binding=0)
uniform Uniforms {
vec3 u_view_position;
mat4 u_view_proj; // unused
};
layout(set = 2, binding = 0) uniform Light {
vec3 light_position;
vec3 light_color;
@ -29,13 +24,13 @@ void main() {
float ambient_strength = 0.1;
vec3 ambient_color = light_color * ambient_strength;
vec3 normal = normalize(v_tangent_matrix * (object_normal.rgb * 2.0 - 1.0));
vec3 light_dir = normalize(light_position - v_position);
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(u_view_position - v_position);
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;

@ -7,23 +7,23 @@ 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(location=1) out vec3 v_position;
layout(location=2) out vec3 v_light_position;
layout(location=3) out vec3 v_view_position;
layout(set=1, binding=0)
layout(set=0, binding=0)
uniform Uniforms {
vec3 u_view_position;
mat4 u_view_proj;
};
layout(set=1, binding=1)
layout(set=2, binding=0)
buffer Instances {
mat4 s_models[];
};
// NEW!
layout(set=2, binding=0) uniform Light {
layout(set=1, binding=0) uniform Light {
vec3 light_position;
vec3 light_color;
};
@ -49,12 +49,9 @@ void main() {
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;
v_position = model_space.xyz;
v_light_position = light_position;
v_view_position = u_view_position;
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;
}

@ -0,0 +1,262 @@
use anyhow::Result;
use cgmath::*;
use rand::Rng;
use std::path::Path;
use std::time::Duration;
use framework::prelude::*;
#[derive(Debug, Copy, Clone)]
#[repr(C)]
struct InstanceData {
model_matrix: Matrix4<f32>,
}
unsafe impl bytemuck::Pod for InstanceData {}
unsafe impl bytemuck::Zeroable for InstanceData {}
struct StorageBuffersDemo<'a> {
depth_texture: framework::Texture<'a>,
cube_model: framework::Model<'a>,
model_pipeline: wgpu::RenderPipeline,
instances: Vec<InstanceData>,
instance_buffer: wgpu::Buffer,
instance_bind_group: wgpu::BindGroup,
uniforms: framework::Uniforms,
uniform_binding: framework::UniformBinding,
camera: framework::Camera,
controller: framework::CameraController,
projection: framework::Projection,
}
impl framework::Demo for StorageBuffersDemo<'static> {
fn init(display: &framework::Display) -> Result<Self> {
let texture_layout = display.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: Some("texture_layout")
}
);
let depth_texture = framework::Texture::create_depth_texture(&display.device, &display.sc_desc);
let mut res_cmds = Vec::new();
let res_dir = Path::new(env!("OUT_DIR")).join("res");
let (cube_model, cmds) = framework::Model::load(
&display.device,
&texture_layout,
res_dir.join("cube.obj")
)?;
res_cmds.extend(cmds);
let mut encoder = display.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: Some("init::encoder")
}
);
let (camera, projection, controller) = framework::camera_setup(
(0.0, 5.0, 10.0),
cgmath::Deg(-90.0),
cgmath::Deg(-20.0),
display.sc_desc.width,
display.sc_desc.height,
);
let mut uniforms = framework::Uniforms::new(&display.device);
uniforms.update_view_proj(&camera, &projection);
uniforms.update_buffer(&display.device, &mut encoder);
let uniform_binding = framework::UniformBinding::new(&display.device, &uniforms);
const NUM_INSTANCES: u32 = 100;
const RADIUS: f32 = 50.0;
let instances = (0..NUM_INSTANCES).map(|_| {
let mut rng = rand::thread_rng();
let position = Vector3::new(
rng.gen_range(-RADIUS, RADIUS),
rng.gen_range(-RADIUS, RADIUS),
rng.gen_range(-RADIUS, RADIUS),
);
let model_matrix = Matrix4::from_translation(position);
InstanceData { model_matrix }
}).collect::<Vec<_>>();
let instance_buffer = display.device.create_buffer_with_data(
bytemuck::cast_slice(&instances),
wgpu::BufferUsage::COPY_DST | wgpu::BufferUsage::STORAGE_READ,
);
let instance_layout = display.device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
label: Some("instance_layout"),
bindings: &[
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStage::VERTEX,
ty: wgpu::BindingType::StorageBuffer {
dynamic: false,
readonly: true,
},
},
]
}
);
let instance_buffer_size = instances.len() * std::mem::size_of::<InstanceData>();
let instance_bind_group = display.device.create_bind_group(
&wgpu::BindGroupDescriptor {
label: Some("instance_bind_group"),
layout: &instance_layout,
bindings: &[
wgpu::Binding {
binding: 1,
resource: wgpu::BindingResource::Buffer {
buffer: &instance_buffer,
range: 0..instance_buffer_size as _,
},
},
]
}
);
let model_layout = display.device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[
&texture_layout,
&uniform_binding.layout,
&instance_layout,
],
}
);
let model_pipeline = framework::RenderPipelineBuilder::new()
.layout(&model_layout)
.depth_format(framework::Texture::DEPTH_FORMAT)
.color_solid(display.sc_desc.format)
.vertex_buffer::<framework::ModelVertex>()
.vertex_shader(include_bytes!("shader.vert.spv"))
.fragment_shader(include_bytes!("shader.frag.spv"))
.build(&display.device)?;
res_cmds.push(encoder.finish());
display.queue.submit(&res_cmds);
Ok(Self {
depth_texture,
cube_model,
model_pipeline,
instances,
instance_buffer,
instance_bind_group,
uniforms,
uniform_binding,
camera,
controller,
projection,
})
}
fn process_mouse(&mut self, dx: f64, dy: f64) {
self.controller.process_mouse(dx, dy);
}
fn resize(&mut self, display: &framework::Display) {
}
fn update(&mut self, display: &framework::Display, dt: Duration) {
self.controller.update_camera(&mut self.camera, dt);
self.uniforms.update_view_proj(&self.camera, &self.projection);
let mut encoder = display.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor {
label: Some("update::encoder")
}
);
self.uniforms.update_buffer(&display.device, &mut encoder);
display.queue.submit(&[encoder.finish()]);
}
fn render(&mut self, display: &mut framework::Display) {
let mut encoder = display.device.create_command_encoder(
&wgpu::CommandEncoderDescriptor { label: Some("render::encoder")}
);
let mut frame = display.swap_chain.get_next_texture().expect("Timeout");
{
let mut 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,
}
)
}
);
pass.set_pipeline(&self.model_pipeline);
pass.set_bind_group(0, &self.uniform_binding.bind_group, &[]);
// pass.set_bind_group(1, &self.light_binding.bind_group, &[]);
for mesh in &self.cube_model.meshes {
let mat = &self.cube_model.materials[mesh.material];
pass.set_bind_group(2, &mat.bind_group, &[]);
pass.draw_indexed(0..mesh.num_elements, 0, 0..self.instances.len() as _);
}
}
}
}
fn main() -> Result<()> {
futures::executor::block_on(framework::run::<StorageBuffersDemo>())
}

@ -1 +1 @@
Subproject commit 037a594e0406e14c88976ac1c486234b81d6a0b5
Subproject commit 6580a9a0350cfa2852a9abf4af433e41f7fe3448
Loading…
Cancel
Save