Compare commits

...

12 Commits

Author SHA1 Message Date
Benjamin Hansen ce957226d3 cargo fmt 1 month ago
Benjamin Hansen 09c1024a8f fixed non-srgb on hdr tutorial 1 month ago
Benjamin Hansen 3d27fca202 demos working 1 month ago
Benjamin Hansen 5eacaf4e9c fixed model code 1 month ago
Benjamin Hansen 8bb1b080b4 fixed up to model loading for wasm 1 month ago
Benjamin Hansen 202012b76d switch to `Backends::PRIMARY` 2 months ago
Benjamin Hansen 3e618336bd not crashing 2 months ago
Benjamin Hansen 6325590051 got pong working natively 3 months ago
Benjamin Hansen 4daff65c34 more cleanup 3 months ago
Benjamin Hansen 7c290e7436 cleaning up and fixing snow 3 months ago
Benjamin Hansen 12f115dfa1 code is working again 3 months ago
Benjamin Hansen 3f0caa6904 migrated tutorials 1 and 2 4 months ago

1027
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -13,19 +13,19 @@ path = "src/main.rs"
[dependencies] [dependencies]
cfg-if = "1" cfg-if = "1"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
wgpu = "0.18" wgpu = "0.19"
pollster = "0.3" pollster = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "=0.2.87" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4.30" wasm-bindgen-futures = "0.4.30"
web-sys = { version = "0.3.53", features = [ web-sys = { version = "0.3.69", features = [
"Document", "Document",
"Window", "Window",
"Element", "Element",

@ -1,6 +1,7 @@
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::WindowBuilder, window::WindowBuilder,
}; };
@ -12,13 +13,13 @@ pub fn run() {
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] { if #[cfg(target_arch = "wasm32")] {
std::panic::set_hook(Box::new(console_error_panic_hook::hook)); std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Warn).expect("Couldn't initialize logger"); console_log::init_with_level(log::Level::Debug).expect("Couldn't initialize logger");
} else { } else {
env_logger::init(); env_logger::init();
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -26,37 +27,42 @@ pub fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
.expect("Couldn't append canvas to document body."); .expect("Couldn't append canvas to document body.");
} }
event_loop.run(move |event, _, control_flow| match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| match event {
ref event, Event::Resumed => {
window_id, log::debug!("Resumed");
} if window_id == window.id() => match event { }
WindowEvent::CloseRequested Event::WindowEvent {
| WindowEvent::KeyboardInput { ref event,
input: window_id,
KeyboardInput { } if window_id == window.id() => match event {
state: ElementState::Pressed, WindowEvent::CloseRequested
virtual_keycode: Some(VirtualKeyCode::Escape), | WindowEvent::KeyboardInput {
.. event:
}, KeyEvent {
.. state: ElementState::Pressed,
} => *control_flow = ControlFlow::Exit, physical_key: PhysicalKey::Code(KeyCode::Escape),
..
},
..
} => control_flow.exit(),
_ => {}
},
_ => {} _ => {}
}, })
_ => {} .unwrap();
});
} }

@ -9,16 +9,16 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
cfg-if = "1" cfg-if = "1"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
wgpu = "0.18" wgpu = "0.19.3"
pollster = "0.3" pollster = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -2,32 +2,36 @@ use std::iter;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
struct State { struct State<'a> {
#[allow(dead_code)] #[allow(dead_code)]
instance: wgpu::Instance, instance: wgpu::Instance,
#[allow(dead_code)] #[allow(dead_code)]
adapter: wgpu::Adapter, adapter: wgpu::Adapter,
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
size: winit::dpi::PhysicalSize<u32>, size: winit::dpi::PhysicalSize<u32>,
clear_color: wgpu::Color, clear_color: wgpu::Color,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
@ -35,7 +39,7 @@ impl State {
// //
// The surface needs to live as long as the window that created it. // The surface needs to live as long as the window that created it.
// State owns the window so this should be safe. // State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap(); let surface = instance.create_surface(window).unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -50,10 +54,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -81,9 +85,9 @@ impl State {
height: size.height, height: size.height,
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
desired_maximum_frame_latency: 2,
view_formats: vec![], view_formats: vec![],
}; };
surface.configure(&device, &config);
let clear_color = wgpu::Color::BLACK; let clear_color = wgpu::Color::BLACK;
@ -172,59 +176,67 @@ fn main() {
async fn run() { async fn run() {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
state.resize(*physical_size);
surface_configured = true;
}
WindowEvent::RedrawRequested => {
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
state.window().request_redraw(); .unwrap();
}
_ => {}
}
});
} }

@ -2,15 +2,16 @@ use std::iter;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -18,25 +19,24 @@ struct State {
// The window must be declared after the surface so // The window must be declared after the surface so
// it gets dropped after it as the surface contains // it gets dropped after it as the surface contains
// unsafe references to the window's resources. // unsafe references to the window's resources.
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -51,10 +51,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -83,9 +83,9 @@ impl State {
height: size.height, height: size.height,
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
desired_maximum_frame_latency: 2,
view_formats: vec![], view_formats: vec![],
}; };
surface.configure(&device, &config);
Self { Self {
surface, surface,
@ -163,13 +163,13 @@ pub async fn run() {
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] { if #[cfg(target_arch = "wasm32")] {
std::panic::set_hook(Box::new(console_error_panic_hook::hook)); std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Warn).expect("Couldn't initialize logger"); console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger");
} else { } else {
env_logger::init(); env_logger::init();
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -177,73 +177,83 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
.expect("Couldn't append canvas to document body."); .expect("Couldn't append canvas to document body.");
let _ = window.request_inner_size(PhysicalSize::new(450, 400));
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
// UPDATED! } if window_id == state.window().id() => {
match event { if !state.input(event) {
WindowEvent::CloseRequested // UPDATED!
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
} WindowEvent::Resized(physical_size) => {
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { log::info!("physical_size: {physical_size:?}");
// new_inner_size is &&mut so w have to dereference it twice surface_configured = true;
state.resize(**new_inner_size); state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
_ => {}
} }
} }
_ => {}
} }
Event::RedrawRequested(window_id) if window_id == state.window().id() => { })
state.update(); .unwrap();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
}
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
Event::RedrawEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -9,16 +9,16 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
cfg-if = "1" cfg-if = "1"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
wgpu = "0.18" wgpu = "0.19"
pollster = "0.3" pollster = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -2,12 +2,13 @@ use std::iter;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -15,25 +16,24 @@ struct State {
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
challenge_render_pipeline: wgpu::RenderPipeline, challenge_render_pipeline: wgpu::RenderPipeline,
use_color: bool, use_color: bool,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -48,10 +48,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -80,8 +80,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"), label: Some("Shader"),
@ -213,10 +213,10 @@ impl State {
fn input(&mut self, event: &WindowEvent) -> bool { fn input(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(VirtualKeyCode::Space), physical_key: PhysicalKey::Code(KeyCode::Space),
.. ..
}, },
.. ..
@ -284,61 +284,69 @@ fn main() {
async fn run() { async fn run() {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| { event_loop
match event { .run(move |event, control_flow| {
Event::WindowEvent { match event {
ref event, Event::WindowEvent {
window_id, ref event,
} if window_id == state.window().id() => { window_id,
if !state.input(event) { } if window_id == state.window().id() => {
match event { if !state.input(event) {
WindowEvent::CloseRequested match event {
| WindowEvent::KeyboardInput { WindowEvent::CloseRequested
input: | WindowEvent::KeyboardInput {
KeyboardInput { event:
state: ElementState::Pressed, KeyEvent {
virtual_keycode: Some(VirtualKeyCode::Escape), state: ElementState::Pressed,
.. physical_key: PhysicalKey::Code(KeyCode::Escape),
}, ..
.. },
} => *control_flow = ControlFlow::Exit, ..
WindowEvent::Resized(physical_size) => { } => control_flow.exit(),
state.resize(*physical_size); WindowEvent::Resized(physical_size) => {
} surface_configured = true;
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { state.resize(*physical_size);
// new_inner_size is &mut so w have to dereference it twice }
state.resize(**new_inner_size); WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
_ => {}
} }
} }
_ => {}
} }
Event::RedrawRequested(window_id) if window_id == state.window().id() => { })
state.update(); .unwrap();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
}
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -2,40 +2,40 @@ use std::iter;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
size: winit::dpi::PhysicalSize<u32>, size: winit::dpi::PhysicalSize<u32>,
// NEW! // NEW!
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -50,10 +50,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -82,8 +82,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"), label: Some("Shader"),
@ -227,7 +227,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -235,14 +235,14 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
@ -250,57 +250,65 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
} WindowEvent::Resized(physical_size) => {
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { surface_configured = true;
// new_inner_size is &mut so w have to dereference it twice state.resize(*physical_size);
state.resize(**new_inner_size); }
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
_ => {}
} }
} }
_ => {}
} }
Event::RedrawRequested(window_id) if window_id == state.window().id() => { })
state.update(); .unwrap();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
}
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -10,8 +10,8 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
cfg-if = "1" cfg-if = "1"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
wgpu = "0.18" wgpu = "0.19"
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
pollster = "0.3" pollster = "0.3"
@ -21,7 +21,7 @@ bytemuck = { version = "1.12", features = [ "derive" ] }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -3,7 +3,8 @@ use std::iter;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -60,8 +61,8 @@ const VERTICES: &[Vertex] = &[
const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4]; const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4];
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -78,17 +79,20 @@ struct State {
use_complex: bool, use_complex: bool,
size: winit::dpi::PhysicalSize<u32>, size: winit::dpi::PhysicalSize<u32>,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
@ -96,7 +100,7 @@ impl State {
// //
// The surface needs to live as long as the window that created it. // The surface needs to live as long as the window that created it.
// State owns the window so this should be safe. // State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap(); let surface = instance.create_surface(window).unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -111,10 +115,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -143,8 +147,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"), label: Some("Shader"),
@ -281,10 +285,10 @@ impl State {
fn input(&mut self, event: &WindowEvent) -> bool { fn input(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(VirtualKeyCode::Space), physical_key: PhysicalKey::Code(KeyCode::Space),
.. ..
}, },
.. ..
@ -361,61 +365,69 @@ fn main() {
async fn run() { async fn run() {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
// RedrawRequested will only trigger once, unless we manually .unwrap();
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -3,7 +3,8 @@ use std::iter;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -63,8 +64,8 @@ const VERTICES: &[Vertex] = &[
const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4, /* padding */ 0]; const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4, /* padding */ 0];
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -74,17 +75,20 @@ struct State {
vertex_buffer: wgpu::Buffer, vertex_buffer: wgpu::Buffer,
index_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer,
num_indices: u32, num_indices: u32,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
@ -92,7 +96,7 @@ impl State {
// //
// The surface needs to live as long as the window that created it. // The surface needs to live as long as the window that created it.
// State owns the window so this should be safe. // State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap(); let surface = instance.create_surface(window).unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -107,10 +111,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -139,8 +143,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Shader"), label: Some("Shader"),
@ -301,7 +305,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -309,14 +313,14 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
@ -324,57 +328,65 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
// RedrawRequested will only trigger once, unless we manually .unwrap();
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -14,8 +14,8 @@ bytemuck = { version = "1.12", features = [ "derive" ] }
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
pollster = "0.3" pollster = "0.3"
wgpu = "0.18" wgpu = "0.19"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
[dependencies.image] [dependencies.image]
version = "0.24" version = "0.24"
@ -25,7 +25,7 @@ features = ["png", "jpeg"]
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -3,7 +3,8 @@ use std::iter;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -63,8 +64,8 @@ const VERTICES: &[Vertex] = &[
const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4]; const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4];
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -80,17 +81,20 @@ struct State {
cartoon_texture: texture::Texture, cartoon_texture: texture::Texture,
cartoon_bind_group: wgpu::BindGroup, cartoon_bind_group: wgpu::BindGroup,
is_space_pressed: bool, is_space_pressed: bool,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
@ -98,7 +102,7 @@ impl State {
// //
// The surface needs to live as long as the window that created it. // The surface needs to live as long as the window that created it.
// State owns the window so this should be safe. // State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap(); let surface = instance.create_surface(window).unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -112,10 +116,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -144,8 +148,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
@ -312,10 +316,10 @@ impl State {
fn input(&mut self, event: &WindowEvent) -> bool { fn input(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(VirtualKeyCode::Space), physical_key: PhysicalKey::Code(KeyCode::Space),
.. ..
}, },
.. ..
@ -388,61 +392,68 @@ fn main() {
async fn run() { async fn run() {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so we have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
// RedrawRequested will only trigger once, unless we manually .unwrap();
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -3,7 +3,8 @@ use std::iter;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -66,8 +67,8 @@ const VERTICES: &[Vertex] = &[
const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4, /* padding */ 0]; const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4, /* padding */ 0];
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -80,25 +81,24 @@ struct State {
#[allow(dead_code)] #[allow(dead_code)]
diffuse_texture: texture::Texture, diffuse_texture: texture::Texture,
diffuse_bind_group: wgpu::BindGroup, diffuse_bind_group: wgpu::BindGroup,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -112,10 +112,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -144,8 +144,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let diffuse_bytes = include_bytes!("happy-tree.png"); let diffuse_bytes = include_bytes!("happy-tree.png");
let diffuse_texture = let diffuse_texture =
@ -351,7 +351,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -359,14 +359,14 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
@ -374,57 +374,65 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
// RedrawRequested will only trigger once, unless we manually .unwrap();
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -15,8 +15,8 @@ cgmath = "0.18"
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
pollster = "0.3" pollster = "0.3"
wgpu = "0.18" wgpu = "0.19"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
[dependencies.image] [dependencies.image]
version = "0.24" version = "0.24"
@ -26,7 +26,7 @@ features = ["png", "jpeg"]
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -3,7 +3,8 @@ use std::iter;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -146,29 +147,29 @@ impl CameraController {
fn process_events(&mut self, event: &WindowEvent) -> bool { fn process_events(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(keycode), physical_key: PhysicalKey::Code(keycode),
.. ..
}, },
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -212,8 +213,8 @@ impl CameraController {
} }
} }
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -230,25 +231,24 @@ struct State {
camera_buffer: wgpu::Buffer, camera_buffer: wgpu::Buffer,
camera_bind_group: wgpu::BindGroup, camera_bind_group: wgpu::BindGroup,
size: winit::dpi::PhysicalSize<u32>, size: winit::dpi::PhysicalSize<u32>,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -262,10 +262,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -294,8 +294,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let diffuse_bytes = include_bytes!("happy-tree.png"); let diffuse_bytes = include_bytes!("happy-tree.png");
let diffuse_texture = let diffuse_texture =
@ -559,61 +559,69 @@ fn main() {
async fn run() { async fn run() {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
// RedrawRequested will only trigger once, unless we manually .unwrap();
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -3,7 +3,8 @@ use std::iter;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -137,37 +138,37 @@ impl CameraController {
fn process_events(&mut self, event: &WindowEvent) -> bool { fn process_events(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(keycode), physical_key: PhysicalKey::Code(keycode),
.. ..
}, },
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {
VirtualKeyCode::Space => { KeyCode::Space => {
self.is_up_pressed = is_pressed; self.is_up_pressed = is_pressed;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.is_down_pressed = is_pressed; self.is_down_pressed = is_pressed;
true true
} }
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -211,8 +212,8 @@ impl CameraController {
} }
} }
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -230,25 +231,24 @@ struct State {
camera_uniform: CameraUniform, camera_uniform: CameraUniform,
camera_buffer: wgpu::Buffer, camera_buffer: wgpu::Buffer,
camera_bind_group: wgpu::BindGroup, camera_bind_group: wgpu::BindGroup,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -262,10 +262,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -294,8 +294,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let diffuse_bytes = include_bytes!("happy-tree.png"); let diffuse_bytes = include_bytes!("happy-tree.png");
let diffuse_texture = let diffuse_texture =
@ -560,7 +560,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -568,14 +568,14 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
@ -583,57 +583,65 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
// RedrawRequested will only trigger once, unless we manually .unwrap();
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -15,8 +15,8 @@ cgmath = "0.18"
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
pollster = "0.3" pollster = "0.3"
wgpu = "0.18" wgpu = "0.19"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
[dependencies.image] [dependencies.image]
version = "0.24" version = "0.24"
@ -26,7 +26,7 @@ features = ["png", "jpeg"]
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -4,7 +4,8 @@ use cgmath::prelude::*;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -137,29 +138,29 @@ impl CameraController {
fn process_events(&mut self, event: &WindowEvent) -> bool { fn process_events(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(keycode), physical_key: PhysicalKey::Code(keycode),
.. ..
}, },
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -264,8 +265,8 @@ impl InstanceRaw {
} }
} }
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -284,25 +285,24 @@ struct State {
size: winit::dpi::PhysicalSize<u32>, size: winit::dpi::PhysicalSize<u32>,
instances: Vec<Instance>, instances: Vec<Instance>,
instance_buffer: wgpu::Buffer, instance_buffer: wgpu::Buffer,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -316,10 +316,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -348,8 +348,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let diffuse_bytes = include_bytes!("happy-tree.png"); let diffuse_bytes = include_bytes!("happy-tree.png");
let diffuse_texture = let diffuse_texture =
@ -660,61 +660,69 @@ fn main() {
async fn run() { async fn run() {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
// RedrawRequested will only trigger once, unless we manually .unwrap();
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -4,7 +4,8 @@ use cgmath::prelude::*;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -144,37 +145,37 @@ impl CameraController {
fn process_events(&mut self, event: &WindowEvent) -> bool { fn process_events(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(keycode), physical_key: PhysicalKey::Code(keycode),
.. ..
}, },
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {
VirtualKeyCode::Space => { KeyCode::Space => {
self.is_up_pressed = is_pressed; self.is_up_pressed = is_pressed;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.is_down_pressed = is_pressed; self.is_down_pressed = is_pressed;
true true
} }
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -269,8 +270,8 @@ impl InstanceRaw {
} }
} }
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -291,25 +292,24 @@ struct State {
instances: Vec<Instance>, instances: Vec<Instance>,
#[allow(dead_code)] #[allow(dead_code)]
instance_buffer: wgpu::Buffer, instance_buffer: wgpu::Buffer,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -323,10 +323,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -355,8 +355,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let diffuse_bytes = include_bytes!("happy-tree.png"); let diffuse_bytes = include_bytes!("happy-tree.png");
let diffuse_texture = let diffuse_texture =
@ -658,7 +658,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -666,14 +666,14 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
@ -681,57 +681,65 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
// RedrawRequested will only trigger once, unless we manually .unwrap();
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -15,8 +15,8 @@ cgmath = "0.18"
env_logger = "0.10" env_logger = "0.10"
pollster = "0.3" pollster = "0.3"
log = "0.4" log = "0.4"
wgpu = "0.18" wgpu = "0.19"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
[dependencies.image] [dependencies.image]
version = "0.24" version = "0.24"
@ -26,7 +26,7 @@ features = ["png", "jpeg"]
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -1,11 +1,12 @@
use std::iter; use std::iter;
use cgmath::prelude::*; use cgmath::prelude::*;
use wgpu::include_spirv_raw;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -159,29 +160,29 @@ impl CameraController {
fn process_events(&mut self, event: &WindowEvent) -> bool { fn process_events(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(keycode), physical_key: PhysicalKey::Code(keycode),
.. ..
}, },
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -461,8 +462,8 @@ impl DepthPass {
} }
} }
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -483,25 +484,24 @@ struct State {
#[allow(dead_code)] #[allow(dead_code)]
instance_buffer: wgpu::Buffer, instance_buffer: wgpu::Buffer,
depth_pass: DepthPass, depth_pass: DepthPass,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -515,10 +515,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -547,8 +547,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let diffuse_bytes = include_bytes!("happy-tree.png"); let diffuse_bytes = include_bytes!("happy-tree.png");
let diffuse_texture = let diffuse_texture =
@ -867,61 +867,69 @@ fn main() {
async fn run() { async fn run() {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
} }
} }
_ => {}
} }
Event::RedrawRequested(window_id) if window_id == state.window().id() => { })
state.update(); .unwrap();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
}
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -4,7 +4,8 @@ use cgmath::prelude::*;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
@ -144,37 +145,37 @@ impl CameraController {
fn process_events(&mut self, event: &WindowEvent) -> bool { fn process_events(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(keycode), physical_key: PhysicalKey::Code(keycode),
.. ..
}, },
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {
VirtualKeyCode::Space => { KeyCode::Space => {
self.is_up_pressed = is_pressed; self.is_up_pressed = is_pressed;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.is_down_pressed = is_pressed; self.is_down_pressed = is_pressed;
true true
} }
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -278,8 +279,8 @@ impl InstanceRaw {
} }
} }
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -301,25 +302,24 @@ struct State {
instance_buffer: wgpu::Buffer, instance_buffer: wgpu::Buffer,
// NEW! // NEW!
depth_texture: texture::Texture, depth_texture: texture::Texture,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -333,10 +333,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -365,8 +365,8 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let diffuse_bytes = include_bytes!("happy-tree.png"); let diffuse_bytes = include_bytes!("happy-tree.png");
let diffuse_texture = let diffuse_texture =
@ -685,7 +685,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -693,14 +693,14 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
@ -708,57 +708,65 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
match event { event_loop
Event::WindowEvent { .run(move |event, control_flow| {
ref event, match event {
window_id, Event::WindowEvent {
} if window_id == state.window().id() => { ref event,
if !state.input(event) { window_id,
match event { } if window_id == state.window().id() => {
WindowEvent::CloseRequested if !state.input(event) {
| WindowEvent::KeyboardInput { match event {
input: WindowEvent::CloseRequested
KeyboardInput { | WindowEvent::KeyboardInput {
state: ElementState::Pressed, event:
virtual_keycode: Some(VirtualKeyCode::Escape), KeyEvent {
.. state: ElementState::Pressed,
}, physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
} => *control_flow = ControlFlow::Exit, },
WindowEvent::Resized(physical_size) => { ..
state.resize(*physical_size); } => control_flow.exit(),
WindowEvent::Resized(physical_size) => {
surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
// new_inner_size is &mut so w have to dereference it twice
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
Event::MainEventsCleared => { })
// RedrawRequested will only trigger once, unless we manually .unwrap();
// request it.
state.window().request_redraw();
}
_ => {}
}
});
} }

@ -10,14 +10,15 @@ pub struct Texture {
impl Texture { impl Texture {
pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
#[allow(unused)]
pub fn create_depth_texture( pub fn create_depth_texture(
device: &wgpu::Device, device: &wgpu::Device,
config: &wgpu::SurfaceConfiguration, config: &wgpu::SurfaceConfiguration,
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {
@ -59,8 +60,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {

@ -16,8 +16,8 @@ env_logger = "0.10"
pollster = "0.3" pollster = "0.3"
log = "0.4" log = "0.4"
tobj = { version = "3.2", features = ["async"]} tobj = { version = "3.2", features = ["async"]}
wgpu = "0.18" wgpu = "0.19"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
[dependencies.image] [dependencies.image]
version = "0.24" version = "0.24"
@ -28,7 +28,7 @@ features = ["png", "jpeg"]
reqwest = { version = "0.11" } reqwest = { version = "0.11" }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -4,7 +4,8 @@ use cgmath::prelude::*;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
@ -27,6 +28,7 @@ pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
const NUM_INSTANCES_PER_ROW: u32 = 10; const NUM_INSTANCES_PER_ROW: u32 = 10;
#[derive(Debug)]
struct Camera { struct Camera {
eye: cgmath::Point3<f32>, eye: cgmath::Point3<f32>,
target: cgmath::Point3<f32>, target: cgmath::Point3<f32>,
@ -46,7 +48,7 @@ impl Camera {
} }
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct CameraUniform { struct CameraUniform {
view_proj: [[f32; 4]; 4], view_proj: [[f32; 4]; 4],
} }
@ -89,37 +91,37 @@ impl CameraController {
fn process_events(&mut self, event: &WindowEvent) -> bool { fn process_events(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(keycode), physical_key: PhysicalKey::Code(keycode),
.. ..
}, },
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {
VirtualKeyCode::Space => { KeyCode::Space => {
self.is_up_pressed = is_pressed; self.is_up_pressed = is_pressed;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.is_down_pressed = is_pressed; self.is_down_pressed = is_pressed;
true true
} }
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -223,8 +225,8 @@ impl InstanceRaw {
} }
} }
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -240,26 +242,25 @@ struct State {
#[allow(dead_code)] #[allow(dead_code)]
instance_buffer: wgpu::Buffer, instance_buffer: wgpu::Buffer,
depth_texture: texture::Texture, depth_texture: texture::Texture,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
log::warn!("WGPU setup"); log::warn!("WGPU setup");
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -274,10 +275,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -308,10 +309,9 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -506,10 +506,10 @@ impl State {
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 { if new_size.width > 0 && new_size.height > 0 {
self.camera.aspect = self.config.width as f32 / self.config.height as f32;
self.size = new_size;
self.config.width = new_size.width; self.config.width = new_size.width;
self.config.height = new_size.height; self.config.height = new_size.height;
self.size = new_size;
self.camera.aspect = self.config.width as f32 / self.config.height as f32;
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
self.depth_texture = self.depth_texture =
texture::Texture::create_depth_texture(&self.device, &self.config, "depth_texture"); texture::Texture::create_depth_texture(&self.device, &self.config, "depth_texture");
@ -521,7 +521,9 @@ impl State {
fn update(&mut self) { fn update(&mut self) {
self.camera_controller.update_camera(&mut self.camera); self.camera_controller.update_camera(&mut self.camera);
log::info!("{:?}", self.camera);
self.camera_uniform.update_view_proj(&self.camera); self.camera_uniform.update_view_proj(&self.camera);
log::info!("{:?}", self.camera_uniform);
self.queue.write_buffer( self.queue.write_buffer(
&self.camera_buffer, &self.camera_buffer,
0, 0,
@ -590,13 +592,13 @@ pub async fn run() {
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] { if #[cfg(target_arch = "wasm32")] {
std::panic::set_hook(Box::new(console_error_panic_hook::hook)); std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Warn).expect("Could't initialize logger"); console_log::init_with_level(log::Level::Info).expect("Could't initialize logger");
} else { } else {
env_logger::init(); env_logger::init();
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
@ -608,14 +610,14 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
@ -623,53 +625,65 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll; event_loop
match event { .run(move |event, control_flow| {
Event::MainEventsCleared => state.window().request_redraw(), match event {
Event::WindowEvent { Event::WindowEvent {
ref event, ref event,
window_id, window_id,
} if window_id == state.window().id() => { } if window_id == state.window().id() => {
if !state.input(event) { if !state.input(event) {
match event { match event {
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); surface_configured = true;
} state.resize(*physical_size);
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { }
state.resize(**new_inner_size); WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
_ => {}
} }
} }
_ => {}
} }
Event::RedrawRequested(window_id) if window_id == state.window().id() => { })
state.update(); .unwrap();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
}
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
_ => {}
}
});
} }

@ -122,7 +122,6 @@ where
camera_bind_group: &'b wgpu::BindGroup, camera_bind_group: &'b wgpu::BindGroup,
) { ) {
for mesh in &model.meshes { for mesh in &model.meshes {
log::warn!("materials: {}", model.materials.len());
let material = &model.materials[mesh.material]; let material = &model.materials[mesh.material];
self.draw_mesh_instanced(mesh, material, instances.clone(), camera_bind_group); self.draw_mesh_instanced(mesh, material, instances.clone(), camera_bind_group);
} }

@ -144,6 +144,7 @@ pub async fn load_model(
usage: wgpu::BufferUsages::INDEX, usage: wgpu::BufferUsages::INDEX,
}); });
log::info!("Mesh: {}", m.name);
model::Mesh { model::Mesh {
name: file_name.to_string(), name: file_name.to_string(),
vertex_buffer, vertex_buffer,

@ -16,8 +16,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {

@ -16,8 +16,8 @@ env_logger = "0.10"
pollster = "0.3" pollster = "0.3"
log = "0.4" log = "0.4"
tobj = { version = "3.2", features = ["async"]} tobj = { version = "3.2", features = ["async"]}
wgpu = { version = "0.18"} wgpu = { version = "0.19"}
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
[dependencies.image] [dependencies.image]
version = "0.24" version = "0.24"
@ -28,7 +28,7 @@ features = ["png", "jpeg"]
reqwest = { version = "0.11" } reqwest = { version = "0.11" }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -4,7 +4,8 @@ use cgmath::prelude::*;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
@ -93,37 +94,37 @@ impl CameraController {
fn process_events(&mut self, event: &WindowEvent) -> bool { fn process_events(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(keycode), physical_key: PhysicalKey::Code(keycode),
.. ..
}, },
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {
VirtualKeyCode::Space => { KeyCode::Space => {
self.is_up_pressed = is_pressed; self.is_up_pressed = is_pressed;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.is_down_pressed = is_pressed; self.is_down_pressed = is_pressed;
true true
} }
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -255,9 +256,9 @@ struct LightUniform {
_padding2: u32, _padding2: u32,
} }
struct State { struct State<'a> {
window: Window, window: &'a Window,
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -339,22 +340,21 @@ fn create_render_pipeline(
}) })
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -368,10 +368,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -400,10 +400,9 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -623,10 +622,10 @@ impl State {
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 { if new_size.width > 0 && new_size.height > 0 {
self.camera.aspect = self.config.width as f32 / self.config.height as f32;
self.size = new_size;
self.config.width = new_size.width; self.config.width = new_size.width;
self.config.height = new_size.height; self.config.height = new_size.height;
self.size = new_size;
self.camera.aspect = self.config.width as f32 / self.config.height as f32;
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
self.depth_texture = self.depth_texture =
texture::Texture::create_depth_texture(&self.device, &self.config, "depth_texture"); texture::Texture::create_depth_texture(&self.device, &self.config, "depth_texture");
@ -733,7 +732,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
@ -745,13 +744,13 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
if let Some(dst) = doc.get_element_by_id("wasm-example") { if let Some(dst) = doc.get_element_by_id("wasm-example") {
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
} else { } else {
@ -763,53 +762,65 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll; event_loop
match event { .run(move |event, control_flow| {
Event::MainEventsCleared => state.window().request_redraw(), match event {
Event::WindowEvent { Event::WindowEvent {
ref event, ref event,
window_id, window_id,
} if window_id == state.window().id() => { } if window_id == state.window().id() => {
if !state.input(event) { if !state.input(event) {
match event { match event {
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
_ => {} })
} .unwrap();
});
} }

@ -1,6 +1,5 @@
use anyhow::*; use anyhow::*;
use image::GenericImageView; use image::GenericImageView;
use std::num::NonZeroU32;
pub struct Texture { pub struct Texture {
pub texture: wgpu::Texture, pub texture: wgpu::Texture,
@ -17,8 +16,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {

@ -16,8 +16,8 @@ env_logger = "0.10"
pollster = "0.3" pollster = "0.3"
log = "0.4" log = "0.4"
tobj = { version = "3.2", features = ["async"]} tobj = { version = "3.2", features = ["async"]}
wgpu = { version = "0.18"} wgpu = { version = "0.19"}
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
[dependencies.image] [dependencies.image]
version = "0.24" version = "0.24"
@ -28,7 +28,7 @@ features = ["png", "jpeg"]
reqwest = { version = "0.11" } reqwest = { version = "0.11" }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -4,7 +4,8 @@ use cgmath::prelude::*;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
@ -92,37 +93,37 @@ impl CameraController {
fn process_events(&mut self, event: &WindowEvent) -> bool { fn process_events(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state, state,
virtual_keycode: Some(keycode), physical_key: PhysicalKey::Code(keycode),
.. ..
}, },
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {
VirtualKeyCode::Space => { KeyCode::Space => {
self.is_up_pressed = is_pressed; self.is_up_pressed = is_pressed;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.is_down_pressed = is_pressed; self.is_down_pressed = is_pressed;
true true
} }
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -253,9 +254,9 @@ struct LightUniform {
_padding2: u32, _padding2: u32,
} }
struct State { struct State<'a> {
window: Window, window: &'a Window,
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -339,22 +340,21 @@ fn create_render_pipeline(
}) })
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -368,10 +368,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -400,10 +400,9 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -674,10 +673,10 @@ impl State {
fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 { if new_size.width > 0 && new_size.height > 0 {
self.camera.aspect = self.config.width as f32 / self.config.height as f32;
self.size = new_size;
self.config.width = new_size.width; self.config.width = new_size.width;
self.config.height = new_size.height; self.config.height = new_size.height;
self.size = new_size;
self.camera.aspect = self.config.width as f32 / self.config.height as f32;
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
self.depth_texture = self.depth_texture =
texture::Texture::create_depth_texture(&self.device, &self.config, "depth_texture"); texture::Texture::create_depth_texture(&self.device, &self.config, "depth_texture");
@ -784,7 +783,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
@ -796,14 +795,14 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
@ -811,53 +810,65 @@ pub async fn run() {
} }
// State::new uses async code, so we're going to wait for it to finish // State::new uses async code, so we're going to wait for it to finish
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut surface_configured = false;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll; event_loop
match event { .run(move |event, control_flow| {
Event::MainEventsCleared => state.window().request_redraw(), match event {
Event::WindowEvent { Event::WindowEvent {
ref event, ref event,
window_id, window_id,
} if window_id == state.window().id() => { } if window_id == state.window().id() => {
if !state.input(event) { if !state.input(event) {
match event { match event {
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); surface_configured = true;
state.resize(*physical_size);
}
WindowEvent::RedrawRequested => {
// This tells winit that we want another frame after this one
state.window().request_redraw();
if !surface_configured {
return;
}
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(
wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated,
) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("OutOfMemory");
control_flow.exit();
}
// This happens when the a frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
log::warn!("Surface timeout")
}
}
}
_ => {}
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
state.resize(**new_inner_size);
}
_ => {}
}
}
}
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
state.update();
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
state.resize(state.size)
} }
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
} }
_ => {}
} }
_ => {} })
} .unwrap();
});
} }

@ -1,6 +1,5 @@
use anyhow::*; use anyhow::*;
use image::GenericImageView; use image::GenericImageView;
use std::num::NonZeroU32;
pub struct Texture { pub struct Texture {
pub texture: wgpu::Texture, pub texture: wgpu::Texture,
@ -17,8 +16,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {

@ -16,8 +16,8 @@ env_logger = "0.10"
pollster = "0.3" pollster = "0.3"
log = "0.4" log = "0.4"
tobj = { version = "3.2", features = ["async"]} tobj = { version = "3.2", features = ["async"]}
wgpu = { version = "0.18"} wgpu = { version = "0.19"}
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
instant = "0.1" instant = "0.1"
[dependencies.image] [dependencies.image]
@ -29,7 +29,7 @@ features = ["png", "jpeg"]
reqwest = { version = "0.11" } reqwest = { version = "0.11" }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [
@ -38,6 +38,7 @@ web-sys = { version = "0.3", features = [
"Element", "Element",
"Location", "Location",
]} ]}
instant = { version = "0.1", features = [ "wasm-bindgen" ] }
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"

@ -3,6 +3,7 @@ use std::f32::consts::FRAC_PI_2;
use std::time::Duration; use std::time::Duration;
use winit::dpi::PhysicalPosition; use winit::dpi::PhysicalPosition;
use winit::event::*; use winit::event::*;
use winit::keyboard::KeyCode;
#[rustfmt::skip] #[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new( pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
@ -104,34 +105,34 @@ impl CameraController {
} }
} }
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool { pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool {
let amount = if state == ElementState::Pressed { let amount = if state == ElementState::Pressed {
1.0 1.0
} else { } else {
0.0 0.0
}; };
match key { match key {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.amount_forward = amount; self.amount_forward = amount;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.amount_backward = amount; self.amount_backward = amount;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.amount_left = amount; self.amount_left = amount;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.amount_right = amount; self.amount_right = amount;
true true
} }
VirtualKeyCode::Space => { KeyCode::Space => {
self.amount_up = amount; self.amount_up = amount;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.amount_down = amount; self.amount_down = amount;
true true
} }

@ -1,10 +1,11 @@
use std::iter; use std::{f32::consts::PI, iter};
use cgmath::prelude::*; use cgmath::prelude::*;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
@ -130,9 +131,9 @@ struct LightUniform {
_padding2: u32, _padding2: u32,
} }
struct State { struct State<'a> {
window: Window, window: &'a Window,
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -219,22 +220,21 @@ fn create_render_pipeline(
}) })
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -248,10 +248,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -280,10 +280,9 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -564,9 +563,9 @@ impl State {
fn input(&mut self, event: &WindowEvent) -> bool { fn input(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
virtual_keycode: Some(key), physical_key: PhysicalKey::Code(key),
state, state,
.. ..
}, },
@ -601,10 +600,11 @@ impl State {
// Update the light // Update the light
let old_position: cgmath::Vector3<_> = self.light_uniform.position.into(); let old_position: cgmath::Vector3<_> = self.light_uniform.position.into();
self.light_uniform.position = self.light_uniform.position = (cgmath::Quaternion::from_axis_angle(
(cgmath::Quaternion::from_axis_angle((0.0, 1.0, 0.0).into(), cgmath::Deg(1.0)) (0.0, 1.0, 0.0).into(),
* old_position) cgmath::Deg(PI * dt.as_secs_f32()),
.into(); ) * old_position)
.into();
self.queue.write_buffer( self.queue.write_buffer(
&self.light_buffer, &self.light_buffer,
0, 0,
@ -686,7 +686,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
@ -698,26 +698,24 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
.expect("Couldn't append canvas to document body."); .expect("Couldn't append canvas to document body.");
} }
let mut state = State::new(window).await; // NEW! let mut state = State::new(&window).await; // NEW!
let mut last_render_time = instant::Instant::now(); let mut last_render_time = instant::Instant::now();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, control_flow| {
*control_flow = ControlFlow::Poll;
match event { match event {
Event::MainEventsCleared => state.window().request_redraw(),
// NEW! // NEW!
Event::DeviceEvent { Event::DeviceEvent {
event: DeviceEvent::MouseMotion{ delta, }, event: DeviceEvent::MouseMotion{ delta, },
@ -734,40 +732,38 @@ pub async fn run() {
#[cfg(not(target_arch="wasm32"))] #[cfg(not(target_arch="wasm32"))]
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); state.resize(*physical_size);
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { // UPDATED!
state.resize(**new_inner_size); WindowEvent::RedrawRequested => {
state.window().request_redraw();
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => control_flow.exit(),
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
} }
_ => {} _ => {}
} }
} }
// UPDATED!
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
_ => {} _ => {}
} }
}); }).unwrap();
} }

@ -16,8 +16,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {

@ -16,8 +16,8 @@ env_logger = "0.10"
pollster = "0.3" pollster = "0.3"
log = "0.4" log = "0.4"
tobj = { version = "3.2", features = ["async"]} tobj = { version = "3.2", features = ["async"]}
wgpu = { version = "0.18"} wgpu = { version = "0.19"}
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
instant = "0.1" instant = "0.1"
[dependencies.image] [dependencies.image]
@ -37,6 +37,7 @@ web-sys = { version = "0.3", features = [
"Element", "Element",
"Location", "Location",
]} ]}
instant = { version = "0.1", features = [ "wasm-bindgen" ] }
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"

@ -3,6 +3,7 @@ use std::f32::consts::FRAC_PI_2;
use std::time::Duration; use std::time::Duration;
use winit::dpi::PhysicalPosition; use winit::dpi::PhysicalPosition;
use winit::event::*; use winit::event::*;
use winit::keyboard::KeyCode;
const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001; const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001;
@ -97,34 +98,34 @@ impl CameraController {
} }
} }
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool { pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool {
let amount = if state == ElementState::Pressed { let amount = if state == ElementState::Pressed {
1.0 1.0
} else { } else {
0.0 0.0
}; };
match key { match key {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.amount_forward = amount; self.amount_forward = amount;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.amount_backward = amount; self.amount_backward = amount;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.amount_left = amount; self.amount_left = amount;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.amount_right = amount; self.amount_right = amount;
true true
} }
VirtualKeyCode::Space => { KeyCode::Space => {
self.amount_up = amount; self.amount_up = amount;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.amount_down = amount; self.amount_down = amount;
true true
} }

@ -79,7 +79,7 @@ impl HdrPipeline {
let pipeline = create_render_pipeline( let pipeline = create_render_pipeline(
device, device,
&pipeline_layout, &pipeline_layout,
config.format, config.format.add_srgb_suffix(),
None, None,
&[], &[],
wgpu::PrimitiveTopology::TriangleList, wgpu::PrimitiveTopology::TriangleList,

@ -1,10 +1,11 @@
use std::iter; use std::{f32::consts::PI, iter};
use cgmath::prelude::*; use cgmath::prelude::*;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
@ -146,9 +147,9 @@ struct LightUniform {
_padding2: u32, _padding2: u32,
} }
struct State { struct State<'a> {
window: Window, window: &'a Window,
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -238,26 +239,22 @@ fn create_render_pipeline(
}) })
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> anyhow::Result<Self> { async fn new(window: &'a Window) -> anyhow::Result<State<'a>> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
// UPDATED // UPDATED
#[cfg(target_arch="wasm32")] #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::BROWSER_WEBGPU, backends: wgpu::Backends::BROWSER_WEBGPU,
#[cfg(not(target_arch="wasm32"))]
backends: wgpu::Backends::all(),
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -272,9 +269,9 @@ impl State {
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
// UPDATED! // UPDATED!
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// UPDATED! // UPDATED!
limits: wgpu::Limits::downlevel_defaults(), required_limits: wgpu::Limits::downlevel_defaults(),
}, },
None, // Trace path None, // Trace path
) )
@ -298,11 +295,11 @@ impl State {
height: size.height, height: size.height,
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], // NEW!
view_formats: vec![surface_format.add_srgb_suffix()],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -665,9 +662,9 @@ impl State {
fn input(&mut self, event: &WindowEvent) -> bool { fn input(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
virtual_keycode: Some(key), physical_key: PhysicalKey::Code(key),
state, state,
.. ..
}, },
@ -701,10 +698,11 @@ impl State {
// Update the light // Update the light
let old_position: cgmath::Vector3<_> = self.light_uniform.position.into(); let old_position: cgmath::Vector3<_> = self.light_uniform.position.into();
self.light_uniform.position = self.light_uniform.position = (cgmath::Quaternion::from_axis_angle(
(cgmath::Quaternion::from_axis_angle((0.0, 1.0, 0.0).into(), cgmath::Deg(1.0)) (0.0, 1.0, 0.0).into(),
* old_position) cgmath::Deg(PI * dt.as_secs_f32()),
.into(); ) * old_position)
.into();
self.queue.write_buffer( self.queue.write_buffer(
&self.light_buffer, &self.light_buffer,
0, 0,
@ -714,9 +712,10 @@ impl State {
fn render(&mut self) -> Result<(), wgpu::SurfaceError> { fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture()?; let output = self.surface.get_current_texture()?;
let view = output let view = output.texture.create_view(&wgpu::TextureViewDescriptor {
.texture format: Some(self.config.format.add_srgb_suffix()),
.create_view(&wgpu::TextureViewDescriptor::default()); ..Default::default()
});
let mut encoder = self let mut encoder = self
.device .device
@ -816,7 +815,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
@ -828,26 +827,24 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
.expect("Couldn't append canvas to document body."); .expect("Couldn't append canvas to document body.");
} }
let mut state = State::new(window).await.unwrap(); // NEW! let mut state = State::new(&window).await.unwrap();
let mut last_render_time = instant::Instant::now(); let mut last_render_time = instant::Instant::now();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, control_flow| {
*control_flow = ControlFlow::Poll;
match event { match event {
Event::MainEventsCleared => state.window().request_redraw(),
Event::DeviceEvent { Event::DeviceEvent {
event: DeviceEvent::MouseMotion{ delta, }, event: DeviceEvent::MouseMotion{ delta, },
.. // We're not using device_id currently .. // We're not using device_id currently
@ -863,40 +860,38 @@ pub async fn run() {
#[cfg(not(target_arch="wasm32"))] #[cfg(not(target_arch="wasm32"))]
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); state.resize(*physical_size);
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { // UPDATED!
state.resize(**new_inner_size); WindowEvent::RedrawRequested => {
state.window().request_redraw();
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => control_flow.exit(),
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
} }
_ => {} _ => {}
} }
} }
// UPDATED!
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
_ => {} _ => {}
} }
}); }).unwrap();
} }

@ -190,7 +190,7 @@ pub async fn load_model(
// Average the tangents/bitangents // Average the tangents/bitangents
for (i, n) in triangles_included.into_iter().enumerate() { for (i, n) in triangles_included.into_iter().enumerate() {
let denom = 1.0 / n as f32; let denom = 1.0 / n as f32;
let mut v = &mut vertices[i]; let v = &mut vertices[i];
v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into(); v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into();
v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into(); v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into();
} }
@ -286,14 +286,28 @@ impl HdrLoader {
) -> anyhow::Result<texture::CubeTexture> { ) -> anyhow::Result<texture::CubeTexture> {
let hdr_decoder = HdrDecoder::new(Cursor::new(data))?; let hdr_decoder = HdrDecoder::new(Cursor::new(data))?;
let meta = hdr_decoder.metadata(); let meta = hdr_decoder.metadata();
let mut pixels = vec![[0.0, 0.0, 0.0, 0.0]; meta.width as usize * meta.height as usize];
hdr_decoder.read_image_transform( #[cfg(not(target_arch = "wasm32"))]
|pix| { let pixels = {
let mut pixels = vec![[0.0, 0.0, 0.0, 0.0]; meta.width as usize * meta.height as usize];
hdr_decoder.read_image_transform(
|pix| {
let rgb = pix.to_hdr();
[rgb.0[0], rgb.0[1], rgb.0[2], 1.0f32]
},
&mut pixels[..],
)?;
pixels
};
#[cfg(target_arch = "wasm32")]
let pixels = hdr_decoder
.read_image_native()?
.into_iter()
.map(|pix| {
let rgb = pix.to_hdr(); let rgb = pix.to_hdr();
[rgb.0[0], rgb.0[1], rgb.0[2], 1.0f32] [rgb.0[0], rgb.0[1], rgb.0[2], 1.0f32]
}, })
&mut pixels[..], .collect::<Vec<_>>();
)?;
let src = texture::Texture::create_2d_texture( let src = texture::Texture::create_2d_texture(
device, device,
@ -327,8 +341,7 @@ impl HdrLoader {
dst_size, dst_size,
self.texture_format, self.texture_format,
1, 1,
wgpu::TextureUsages::STORAGE_BINDING wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
| wgpu::TextureUsages::TEXTURE_BINDING,
wgpu::FilterMode::Nearest, wgpu::FilterMode::Nearest,
label, label,
); );
@ -356,7 +369,10 @@ impl HdrLoader {
}); });
let mut encoder = device.create_command_encoder(&Default::default()); let mut encoder = device.create_command_encoder(&Default::default());
let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label, timestamp_writes: None }); let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label,
timestamp_writes: None,
});
let num_workgroups = (dst_size + 15) / 16; let num_workgroups = (dst_size + 15) / 16;
pass.set_pipeline(&self.equirect_to_cubemap); pass.set_pipeline(&self.equirect_to_cubemap);

@ -17,8 +17,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {
@ -239,10 +239,15 @@ impl CubeTexture {
} }
} }
pub fn texture(&self) -> &wgpu::Texture { &self.texture } pub fn texture(&self) -> &wgpu::Texture {
&self.texture
pub fn view(&self) -> &wgpu::TextureView { &self.view } }
pub fn sampler(&self) -> &wgpu::Sampler { &self.sampler } pub fn view(&self) -> &wgpu::TextureView {
&self.view
}
pub fn sampler(&self) -> &wgpu::Sampler {
&self.sampler
}
} }

@ -16,8 +16,8 @@ env_logger = "0.10"
pollster = "0.3" pollster = "0.3"
log = "0.4" log = "0.4"
tobj = { version = "3.2", features = ["async"]} tobj = { version = "3.2", features = ["async"]}
wgpu = { version = "0.18"} wgpu = { version = "0.19"}
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
instant = "0.1" instant = "0.1"
[dependencies.image] [dependencies.image]
@ -29,7 +29,7 @@ features = ["png", "jpeg"]
reqwest = { version = "0.11" } reqwest = { version = "0.11" }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -2,7 +2,7 @@ use std::marker::PhantomData;
pub struct Binder<B> { pub struct Binder<B> {
layout: wgpu::BindGroupLayout, layout: wgpu::BindGroupLayout,
_marker: PhantomData<B> _marker: PhantomData<B>,
} }
// pub trait Uniform // pub trait Uniform

@ -3,6 +3,7 @@ use std::f32::consts::FRAC_PI_2;
use std::time::Duration; use std::time::Duration;
use winit::dpi::PhysicalPosition; use winit::dpi::PhysicalPosition;
use winit::event::*; use winit::event::*;
use winit::keyboard::KeyCode;
#[rustfmt::skip] #[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new( pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
@ -104,34 +105,34 @@ impl CameraController {
} }
} }
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool { pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool {
let amount = if state == ElementState::Pressed { let amount = if state == ElementState::Pressed {
1.0 1.0
} else { } else {
0.0 0.0
}; };
match key { match key {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.amount_forward = amount; self.amount_forward = amount;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.amount_backward = amount; self.amount_backward = amount;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.amount_left = amount; self.amount_left = amount;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.amount_right = amount; self.amount_right = amount;
true true
} }
VirtualKeyCode::Space => { KeyCode::Space => {
self.amount_up = amount; self.amount_up = amount;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.amount_down = amount; self.amount_down = amount;
true true
} }

@ -4,19 +4,20 @@ use cgmath::prelude::*;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
mod bindgroups;
mod camera; mod camera;
mod model; mod model;
mod resources; mod resources;
mod terrain; mod terrain;
mod texture; mod texture; // NEW!
mod bindgroups; // NEW!
use model::{DrawLight, DrawModel, Vertex}; use model::{DrawLight, DrawModel, Vertex};
@ -132,9 +133,9 @@ struct LightUniform {
_padding2: u32, _padding2: u32,
} }
struct State { struct State<'a> {
window: Window, window: &'a Window,
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -220,22 +221,21 @@ fn create_render_pipeline(
}) })
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -249,10 +249,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -281,10 +281,9 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -606,9 +605,9 @@ impl State {
fn input(&mut self, event: &WindowEvent) -> bool { fn input(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
virtual_keycode: Some(key), physical_key: PhysicalKey::Code(key),
state, state,
.. ..
}, },
@ -736,7 +735,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
@ -749,27 +748,25 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
.expect("Couldn't append canvas to document body."); .expect("Couldn't append canvas to document body.");
} }
let mut state = State::new(window).await; // NEW! let mut state = State::new(&window).await; // NEW!
state.window().set_visible(true); state.window().set_visible(true);
let mut last_render_time = instant::Instant::now(); let mut last_render_time = instant::Instant::now();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, control_flow| {
*control_flow = ControlFlow::Poll;
match event { match event {
Event::MainEventsCleared => state.window().request_redraw(),
// NEW! // NEW!
Event::DeviceEvent { Event::DeviceEvent {
event: DeviceEvent::MouseMotion{ delta, }, event: DeviceEvent::MouseMotion{ delta, },
@ -786,40 +783,37 @@ pub async fn run() {
#[cfg(not(target_arch="wasm32"))] #[cfg(not(target_arch="wasm32"))]
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); state.resize(*physical_size);
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { // UPDATED!
state.resize(**new_inner_size); WindowEvent::RedrawRequested => {
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => control_flow.exit(),
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
} }
_ => {} _ => {}
} }
} }
// UPDATED!
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
_ => {} _ => {}
} }
}); }).unwrap();
} }

@ -241,8 +241,7 @@ impl GenerateChunk for TerrainPipeline {
let gen_buffer = device.create_buffer(&wgpu::BufferDescriptor { let gen_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("TerrainPipeline: ChunkData"), label: Some("TerrainPipeline: ChunkData"),
size: size_of_val(&data) as _, size: size_of_val(&data) as _,
usage: wgpu::BufferUsages::UNIFORM usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
| wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
}); });
queue.write_buffer(&gen_buffer, 0, bytemuck::bytes_of(&data)); queue.write_buffer(&gen_buffer, 0, bytemuck::bytes_of(&data));

@ -17,8 +17,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {
@ -63,7 +63,14 @@ impl Texture {
address_mode: AddressMode, // NEW! address_mode: AddressMode, // NEW!
) -> Result<Self> { ) -> Result<Self> {
let img = image::load_from_memory(bytes)?; let img = image::load_from_memory(bytes)?;
Self::from_image(device, queue, &img, Some(label), is_normal_map, address_mode) // UPDATED! Self::from_image(
device,
queue,
&img,
Some(label),
is_normal_map,
address_mode,
) // UPDATED!
} }
pub fn from_image( pub fn from_image(
@ -117,7 +124,7 @@ impl Texture {
let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor { let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
// UPDATED! // UPDATED!
address_mode_u: address_mode, address_mode_u: address_mode,
address_mode_v: address_mode, address_mode_v: address_mode,
address_mode_w: address_mode, address_mode_w: address_mode,
mag_filter: wgpu::FilterMode::Linear, mag_filter: wgpu::FilterMode::Linear,

@ -14,8 +14,8 @@ image = "0.24"
log = "0.4" log = "0.4"
rayon = "1.4" rayon = "1.4"
tobj = "2.0" tobj = "2.0"
wgpu = "0.18" wgpu = "0.19"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"

@ -3,6 +3,7 @@ use std::f32::consts::FRAC_PI_2;
use std::time::Duration; use std::time::Duration;
use winit::dpi::PhysicalPosition; use winit::dpi::PhysicalPosition;
use winit::event::*; use winit::event::*;
use winit::keyboard::KeyCode;
#[rustfmt::skip] #[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new( pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
@ -104,34 +105,34 @@ impl CameraController {
} }
} }
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool { pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool {
let amount = if state == ElementState::Pressed { let amount = if state == ElementState::Pressed {
1.0 1.0
} else { } else {
0.0 0.0
}; };
match key { match key {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.amount_forward = amount; self.amount_forward = amount;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.amount_backward = amount; self.amount_backward = amount;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.amount_left = amount; self.amount_left = amount;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.amount_right = amount; self.amount_right = amount;
true true
} }
VirtualKeyCode::Space => { KeyCode::Space => {
self.amount_up = amount; self.amount_up = amount;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.amount_down = amount; self.amount_down = amount;
true true
} }

@ -5,7 +5,8 @@ use wgpu::util::DeviceExt;
use winit::{ use winit::{
dpi::PhysicalPosition, dpi::PhysicalPosition,
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
@ -129,8 +130,8 @@ struct LightUniform {
_padding2: u32, _padding2: u32,
} }
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -155,25 +156,24 @@ struct State {
debug_material: model::Material, debug_material: model::Material,
last_mouse_pos: PhysicalPosition<f64>, last_mouse_pos: PhysicalPosition<f64>,
mouse_pressed: bool, mouse_pressed: bool,
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -187,10 +187,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -219,10 +219,9 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -506,9 +505,9 @@ impl State {
fn input(&mut self, event: &WindowEvent) -> bool { fn input(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
virtual_keycode: Some(key), physical_key: PhysicalKey::Code(key),
state, state,
.. ..
}, },
@ -636,18 +635,16 @@ fn main() {
async fn run() { async fn run() {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
let mut state = State::new(window).await; // NEW! let mut state = State::new(&window).await; // NEW!
let mut last_render_time = std::time::Instant::now(); let mut last_render_time = std::time::Instant::now();
event_loop.run(move |event, _, control_flow| { event_loop
*control_flow = ControlFlow::Poll; .run(move |event, control_flow| match event {
match event {
Event::MainEventsCleared => state.window().request_redraw(),
Event::WindowEvent { Event::WindowEvent {
ref event, ref event,
window_id, window_id,
@ -656,32 +653,30 @@ async fn run() {
match event { match event {
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); state.resize(*physical_size);
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { WindowEvent::RedrawRequested => {
state.resize(**new_inner_size); state.window.request_redraw();
let now = std::time::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
state.render();
} }
_ => {} _ => {}
} }
} }
} }
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
let now = std::time::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
state.render();
}
_ => {} _ => {}
} })
}); .unwrap();
} }

@ -298,7 +298,10 @@ impl ModelLoader {
m.mesh.positions[i * 3 + 2], m.mesh.positions[i * 3 + 2],
], ],
// tex_coords: [m.mesh.texcoords[i * 2], m.mesh.texcoords[i * 2 + 1], 0.0] // tex_coords: [m.mesh.texcoords[i * 2], m.mesh.texcoords[i * 2 + 1], 0.0]
tex_coords: [m.mesh.texcoords[i * 2], 1.0 - m.mesh.texcoords[i * 2 + 1]], tex_coords: [
m.mesh.texcoords[i * 2],
1.0 - m.mesh.texcoords[i * 2 + 1],
],
normal: [ normal: [
m.mesh.normals[i * 3], m.mesh.normals[i * 3],
m.mesh.normals[i * 3 + 1], m.mesh.normals[i * 3 + 1],

@ -31,8 +31,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {

@ -14,9 +14,9 @@ pollster = "0.3"
image = "0.24.2" image = "0.24.2"
log = "0.4" log = "0.4"
tobj = "2.0" tobj = "2.0"
wgpu = "0.18" wgpu = "0.19"
wgpu-subscriber = "0.1" wgpu-subscriber = "0.1"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"

@ -3,6 +3,7 @@ use std::f32::consts::FRAC_PI_2;
use std::time::Duration; use std::time::Duration;
use winit::dpi::PhysicalPosition; use winit::dpi::PhysicalPosition;
use winit::event::*; use winit::event::*;
use winit::keyboard::KeyCode;
#[rustfmt::skip] #[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new( pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
@ -104,34 +105,34 @@ impl CameraController {
} }
} }
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool { pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool {
let amount = if state == ElementState::Pressed { let amount = if state == ElementState::Pressed {
1.0 1.0
} else { } else {
0.0 0.0
}; };
match key { match key {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.amount_forward = amount; self.amount_forward = amount;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.amount_backward = amount; self.amount_backward = amount;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.amount_left = amount; self.amount_left = amount;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.amount_right = amount; self.amount_right = amount;
true true
} }
VirtualKeyCode::Space => { KeyCode::Space => {
self.amount_up = amount; self.amount_up = amount;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.amount_down = amount; self.amount_down = amount;
true true
} }

@ -21,24 +21,28 @@ use std::time::{Duration, Instant};
use wgpu::util::{BufferInitDescriptor, DeviceExt}; use wgpu::util::{BufferInitDescriptor, DeviceExt};
use winit::event::*; use winit::event::*;
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::{Window, WindowBuilder}; use winit::window::{Window, WindowBuilder};
pub struct Display { pub struct Display<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
pub window: Window, pub window: &'a Window,
pub config: wgpu::SurfaceConfiguration, pub config: wgpu::SurfaceConfiguration,
pub device: wgpu::Device, pub device: wgpu::Device,
pub queue: wgpu::Queue, pub queue: wgpu::Queue,
} }
impl Display { impl<'a> Display<'a> {
pub async fn new(window: Window) -> Result<Self, Error> { pub async fn new(window: &'a Window) -> Result<Display<'a>, Error> {
let size = window.inner_size(); let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
let surface = unsafe { instance.create_surface(&window) }.unwrap(); let surface = instance.create_surface(window).unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(), power_preference: wgpu::PowerPreference::default(),
@ -51,10 +55,10 @@ impl Display {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -82,8 +86,8 @@ impl Display {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
Ok(Self { Ok(Self {
surface, surface,
@ -214,7 +218,7 @@ impl UniformBinding {
pub trait Demo: 'static + Sized { pub trait Demo: 'static + Sized {
fn init(display: &Display) -> Result<Self, Error>; fn init(display: &Display) -> Result<Self, Error>;
fn process_mouse(&mut self, dx: f64, dy: f64); fn process_mouse(&mut self, dx: f64, dy: f64);
fn process_keyboard(&mut self, key: VirtualKeyCode, pressed: bool); fn process_keyboard(&mut self, key: KeyCode, pressed: bool);
fn resize(&mut self, display: &Display); fn resize(&mut self, display: &Display);
fn update(&mut self, display: &Display, dt: Duration); fn update(&mut self, display: &Display, dt: Duration);
fn render(&mut self, display: &mut Display); fn render(&mut self, display: &mut Display);
@ -223,74 +227,71 @@ pub trait Demo: 'static + Sized {
pub async fn run<D: Demo>() -> Result<(), Error> { pub async fn run<D: Demo>() -> Result<(), Error> {
wgpu_subscriber::initialize_default_subscriber(None); wgpu_subscriber::initialize_default_subscriber(None);
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_title(env!("CARGO_PKG_NAME")) .with_title(env!("CARGO_PKG_NAME"))
.build(&event_loop)?; .build(&event_loop)?;
let mut display = Display::new(window).await?; let mut display = Display::new(&window).await?;
let mut demo = D::init(&display)?; let mut demo = D::init(&display)?;
let mut last_update = Instant::now(); let mut last_update = Instant::now();
let mut is_resumed = true; let mut is_resumed = true;
let mut is_focused = true; let mut is_focused = true;
let mut is_redraw_requested = true; let _is_redraw_requested = true;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, control_flow| {
*control_flow = if is_resumed && is_focused { if is_resumed && is_focused {
ControlFlow::Poll control_flow.set_control_flow(ControlFlow::Poll)
} else { } else {
ControlFlow::Wait control_flow.set_control_flow(ControlFlow::Wait)
}; };
match event { match event {
Event::Resumed => is_resumed = true, Event::Resumed => is_resumed = true,
Event::Suspended => is_resumed = false, Event::Suspended => is_resumed = false,
Event::RedrawRequested(wid) => {
if wid == display.window().id() {
let now = Instant::now();
let dt = now - last_update;
last_update = now;
demo.update(&display, dt);
demo.render(&mut display);
is_redraw_requested = false;
}
}
Event::MainEventsCleared => {
if is_focused && is_resumed && !is_redraw_requested {
display.window().request_redraw();
is_redraw_requested = true;
} else {
// Freeze time while the demo is not in the foreground
last_update = Instant::now();
}
}
Event::WindowEvent { Event::WindowEvent {
event, window_id, .. event, window_id, ..
} => { } => {
if window_id == display.window().id() { if window_id == display.window().id() {
match event { match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::CloseRequested => control_flow.exit(),
WindowEvent::Focused(f) => is_focused = f, WindowEvent::Focused(f) => is_focused = f,
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
display.resize(new_inner_size.width, new_inner_size.height);
demo.resize(&display);
}
WindowEvent::Resized(new_inner_size) => { WindowEvent::Resized(new_inner_size) => {
display.resize(new_inner_size.width, new_inner_size.height); display.resize(new_inner_size.width, new_inner_size.height);
demo.resize(&display); demo.resize(&display);
} }
WindowEvent::KeyboardInput { input: KeyboardInput { WindowEvent::KeyboardInput {
virtual_keycode: Some(key), event:
state, KeyEvent {
physical_key: PhysicalKey::Code(key),
state,
..
},
.. ..
}, .. } => { } => {
demo.process_keyboard(key, state == ElementState::Pressed); demo.process_keyboard(key, state == ElementState::Pressed);
} }
WindowEvent::RedrawRequested => {
let now = Instant::now();
let dt = now - last_update;
last_update = now;
demo.update(&display, dt);
demo.render(&mut display);
if is_focused && is_resumed {
display.window().request_redraw();
} else {
// Freeze time while the demo is not in the foreground
last_update = Instant::now();
}
}
_ => {} _ => {}
} }
} }
} }
_ => {} _ => {}
} }
}); })?;
Ok(())
} }

@ -1,7 +1,7 @@
use anyhow::*; use anyhow::*;
use image::GenericImageView; use image::GenericImageView;
use std::path::Path;
use std::mem; use std::mem;
use std::path::Path;
use crate::buffer; use crate::buffer;

@ -14,8 +14,8 @@ pollster = "0.3"
image = "0.24.2" image = "0.24.2"
log = "0.4" log = "0.4"
tobj = "3.1" tobj = "3.1"
wgpu = "0.18" wgpu = "0.19"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
gif = "0.11.4" gif = "0.11.4"
futures-intrusive = "0.4" futures-intrusive = "0.4"

@ -16,10 +16,10 @@ async fn run() {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: Some("Device"), label: Some("Device"),
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()

@ -8,9 +8,9 @@ edition = "2018"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
wgpu = "0.18" wgpu = "0.19"
pollster = "0.3" pollster = "0.3"
imgui = "0.7" imgui = "0.7"
imgui-wgpu = "0.18" imgui-wgpu = "0.19"
imgui-winit-support = "0.7" imgui-winit-support = "0.7"
framework = { path = "../framework" } framework = { path = "../framework" }

@ -1,12 +0,0 @@
[package]
name = "lost-window"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1"
wgpu = "0.18"
winit = "0.28"
pollster = "0.3"

@ -1,127 +0,0 @@
use std::time::{Duration, Instant};
use winit::{
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
async fn run() -> anyhow::Result<()> {
let event_loop = EventLoop::new();
let mut window = Some(
WindowBuilder::new()
.with_visible(false)
.build(&event_loop)?,
);
let window2 = WindowBuilder::new()
.with_visible(false)
.build(&event_loop)?;
let backends = wgpu::Backends::all();
let instance = wgpu::Instance::default();
let surface = unsafe { instance.create_surface(window.as_ref().unwrap()) }.unwrap();
let adapter = instance
.enumerate_adapters(backends)
.filter(|a| a.is_surface_supported(&surface))
.next()
.unwrap();
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: Default::default(),
limits: Default::default(),
},
None,
)
.await?;
let inner_size = window.as_ref().unwrap().inner_size();
let mut config = wgpu::SurfaceConfiguration {
width: inner_size.width,
height: inner_size.height,
format: surface.get_capabilities(&adapter).formats[0],
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
present_mode: Default::default(),
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
};
surface.configure(&device, &config);
let start_time = Instant::now();
window.as_ref().unwrap().set_visible(true);
window2.set_visible(true);
event_loop.run(move |ev, _, cf| {
match ev {
Event::WindowEvent { event, window_id } => {
match event {
WindowEvent::Resized(size) => {
if window.is_some() && window_id == window.as_ref().unwrap().id() {
config.width = size.width;
config.height = size.height;
surface.configure(&device, &config);
}
}
WindowEvent::CloseRequested => {
// You'll only really want to close the window while testing
cf.set_exit()
}
_ => (),
}
}
Event::RedrawRequested(_) => {
let frame = match surface.get_current_texture() {
Ok(frame) => frame,
Err(e) => {
println!("An error occurred: {:?}", e);
return;
}
};
let view = frame.texture.create_view(&Default::default());
let mut encoder = device.create_command_encoder(&Default::default());
drop(encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: Default::default(),
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
}));
queue.submit([encoder.finish()]);
frame.present();
}
Event::RedrawEventsCleared => {
let current_time = Instant::now();
let dt = current_time - start_time;
// last_time = current_time;
if let Some(w) = window.as_ref() {
w.request_redraw();
}
window2.request_redraw();
println!("dt: {:?}", dt);
if dt > Duration::from_secs(5) {
// Exit the loop
cf.set_exit();
} else if dt > Duration::from_secs(2) {
// Dispose of the first window
if let Some(window) = window.take() {
drop(window);
}
}
}
_ => (),
}
});
}
fn main() -> anyhow::Result<()> {
pollster::block_on(run())
}

@ -16,8 +16,8 @@ env_logger = "0.10"
pollster = "0.3" pollster = "0.3"
log = "0.4" log = "0.4"
tobj = { version = "3.2", features = ["async"]} tobj = { version = "3.2", features = ["async"]}
wgpu = { version = "0.18"} wgpu = { version = "0.19"}
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
instant = "0.1" instant = "0.1"
[dependencies.image] [dependencies.image]
@ -29,7 +29,7 @@ features = ["png", "jpeg"]
reqwest = { version = "0.11" } reqwest = { version = "0.11" }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -3,6 +3,7 @@ use std::f32::consts::FRAC_PI_2;
use std::time::Duration; use std::time::Duration;
use winit::dpi::PhysicalPosition; use winit::dpi::PhysicalPosition;
use winit::event::*; use winit::event::*;
use winit::keyboard::KeyCode;
#[rustfmt::skip] #[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new( pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
@ -104,34 +105,34 @@ impl CameraController {
} }
} }
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool { pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool {
let amount = if state == ElementState::Pressed { let amount = if state == ElementState::Pressed {
1.0 1.0
} else { } else {
0.0 0.0
}; };
match key { match key {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.amount_forward = amount; self.amount_forward = amount;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.amount_backward = amount; self.amount_backward = amount;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.amount_left = amount; self.amount_left = amount;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.amount_right = amount; self.amount_right = amount;
true true
} }
VirtualKeyCode::Space => { KeyCode::Space => {
self.amount_up = amount; self.amount_up = amount;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.amount_down = amount; self.amount_down = amount;
true true
} }

@ -5,7 +5,8 @@ use math::Ray;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
@ -131,9 +132,9 @@ struct LightUniform {
_padding2: u32, _padding2: u32,
} }
struct State { struct State<'a> {
window: Window, window: &'a Window,
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -224,22 +225,21 @@ fn create_render_pipeline(
}) })
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -253,10 +253,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -285,10 +285,9 @@ impl State {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -577,9 +576,9 @@ impl State {
fn input(&mut self, event: &WindowEvent) -> bool { fn input(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
virtual_keycode: Some(key), physical_key: PhysicalKey::Code(key),
state, state,
.. ..
}, },
@ -733,7 +732,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
@ -745,26 +744,24 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
.expect("Couldn't append canvas to document body."); .expect("Couldn't append canvas to document body.");
} }
let mut state = State::new(window).await; // NEW! let mut state = State::new(&window).await; // NEW!
let mut last_render_time = instant::Instant::now(); let mut last_render_time = instant::Instant::now();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, control_flow| {
*control_flow = ControlFlow::Poll;
match event { match event {
Event::MainEventsCleared => state.window().request_redraw(),
// NEW! // NEW!
Event::DeviceEvent { Event::DeviceEvent {
event: DeviceEvent::MouseMotion{ delta, }, event: DeviceEvent::MouseMotion{ delta, },
@ -781,40 +778,38 @@ pub async fn run() {
#[cfg(not(target_arch="wasm32"))] #[cfg(not(target_arch="wasm32"))]
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); state.resize(*physical_size);
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { // UPDATED!
state.resize(**new_inner_size); WindowEvent::RedrawRequested => {
state.window.request_redraw();
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => control_flow.exit(),
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
} }
_ => {} _ => {}
} }
} }
// UPDATED!
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
_ => {} _ => {}
} }
}); }).unwrap();
} }

@ -118,7 +118,7 @@ pub async fn load_model(
.into_iter() .into_iter()
.map(|m| { .map(|m| {
// NEW! // NEW!
let mut bounding_box = BoundingBox { let bounding_box = BoundingBox {
min: cgmath::Vector3::new( min: cgmath::Vector3::new(
std::f32::INFINITY, std::f32::INFINITY,
std::f32::INFINITY, std::f32::INFINITY,
@ -218,7 +218,7 @@ pub async fn load_model(
// Average the tangents/bitangents // Average the tangents/bitangents
for (i, n) in triangles_included.into_iter().enumerate() { for (i, n) in triangles_included.into_iter().enumerate() {
let denom = 1.0 / n as f32; let denom = 1.0 / n as f32;
let mut v = &mut vertices[i]; let v = &mut vertices[i];
v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into(); v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into();
v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into(); v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into();
} }

@ -1,6 +1,5 @@
use anyhow::*; use anyhow::*;
use image::GenericImageView; use image::GenericImageView;
use std::num::NonZeroU32;
pub struct Texture { pub struct Texture {
pub texture: wgpu::Texture, pub texture: wgpu::Texture,
@ -17,8 +16,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {

@ -10,13 +10,13 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
cfg-if = "1" cfg-if = "1"
env_logger = "0.10" env_logger = "0.10"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
anyhow = "1.0" anyhow = "1.0"
bytemuck = { version = "1.12", features = [ "derive" ] } bytemuck = { version = "1.12", features = [ "derive" ] }
cgmath = "0.18" cgmath = "0.18"
pollster = "0.3" pollster = "0.3"
wgpu = { version = "0.18", features = ["spirv"]} wgpu = { version = "0.19", features = ["spirv"]}
wgpu_glyph = { version = "0.21", git = "https://github.com/hecrj/wgpu_glyph.git" } wgpu_glyph = { version = "0.22", git = "https://github.com/hecrj/wgpu_glyph.git" }
rand = "0.8" rand = "0.8"
rodio = { version = "0.16", default-features = false, features = ["wav"] } rodio = { version = "0.16", default-features = false, features = ["wav"] }
log = "0.4" log = "0.4"
@ -28,13 +28,13 @@ console_log = "1.0"
getrandom = { version = "0.2", features = ["js"] } getrandom = { version = "0.2", features = ["js"] }
rodio = { version = "0.16", default-features = false, features = ["wasm-bindgen", "wav"] } rodio = { version = "0.16", default-features = false, features = ["wasm-bindgen", "wav"] }
wasm-bindgen-futures = "0.4.20" wasm-bindgen-futures = "0.4.20"
wasm-bindgen = "=0.2.87" wasm-bindgen = "0.2"
web-sys = { version = "0.3.53", features = [ web-sys = { version = "0.3", features = [
"Document", "Document",
"Window", "Window",
"Element", "Element",
]} ]}
wgpu = { version = "0.18", features = ["spirv", "webgl"]} wgpu = { version = "0.19", features = ["spirv", "webgl"]}
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"

@ -1,4 +1,4 @@
use winit::event::{ElementState, VirtualKeyCode}; use winit::{event::ElementState, keyboard::KeyCode};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Input { pub struct Input {
@ -14,26 +14,26 @@ impl Input {
Default::default() Default::default()
} }
pub fn update(&mut self, key: VirtualKeyCode, state: ElementState) -> bool { pub fn update(&mut self, key: KeyCode, state: ElementState) -> bool {
let pressed = state == ElementState::Pressed; let pressed = state == ElementState::Pressed;
match key { match key {
VirtualKeyCode::Up => { KeyCode::ArrowUp => {
self.p2_up_pressed = pressed; self.p2_up_pressed = pressed;
true true
} }
VirtualKeyCode::Down => { KeyCode::ArrowDown => {
self.p2_down_pressed = pressed; self.p2_down_pressed = pressed;
true true
} }
VirtualKeyCode::W => { KeyCode::KeyW => {
self.p1_up_pressed = pressed; self.p1_up_pressed = pressed;
true true
} }
VirtualKeyCode::S => { KeyCode::KeyS => {
self.p1_down_pressed = pressed; self.p1_down_pressed = pressed;
true true
} }
VirtualKeyCode::Return => { KeyCode::Enter => {
self.enter_pressed = pressed; self.enter_pressed = pressed;
true true
} }

@ -13,7 +13,8 @@ use wasm_bindgen::prelude::*;
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use winit::event::*; use winit::event::*;
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{EventLoop, EventLoopWindowTarget};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::{Fullscreen, WindowBuilder}; use winit::window::{Fullscreen, WindowBuilder};
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] #[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
@ -27,7 +28,7 @@ pub fn start() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let monitor = event_loop.primary_monitor().unwrap(); let monitor = event_loop.primary_monitor().unwrap();
let video_mode = monitor.video_modes().next(); let video_mode = monitor.video_modes().next();
let size = video_mode let size = video_mode
@ -40,10 +41,6 @@ pub fn start() {
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
if window.fullscreen().is_none() {
window.set_inner_size(PhysicalSize::new(512, 512));
}
window.set_cursor_visible(false); window.set_cursor_visible(false);
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -53,7 +50,7 @@ pub fn start() {
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
// Request fullscreen, if denied, continue as normal // Request fullscreen, if denied, continue as normal
@ -159,125 +156,126 @@ pub fn start() {
log::info!("Event Loop..."); log::info!("Event Loop...");
event_loop.run(move |event, _, control_flow| { let window = &window;
*control_flow = if state.game_state == state::GameState::Quiting { let mut last_time = instant::Instant::now();
ControlFlow::Exit event_loop
} else { .run(move |event, control_flow| {
ControlFlow::Poll match event {
}; Event::WindowEvent {
event: WindowEvent::CloseRequested,
match event { ..
Event::WindowEvent { } => {
event: WindowEvent::CloseRequested, state.game_state = state::GameState::Quiting;
..
} => {
state.game_state = state::GameState::Quiting;
}
Event::WindowEvent {
event:
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: element_state,
virtual_keycode: Some(key),
..
},
..
},
..
} => {
let input_handled = match state.game_state {
state::GameState::Quiting => true,
_ => input.update(key, element_state),
};
if !input_handled {
process_input(element_state, key, control_flow);
} }
} Event::WindowEvent {
Event::WindowEvent { event:
event: WindowEvent::Resized(size), WindowEvent::KeyboardInput {
.. event:
} => { KeyEvent {
render.resize(size); state: element_state,
events.push(state::Event::Resize(size.width as f32, size.height as f32)); physical_key: PhysicalKey::Code(key),
} ..
Event::RedrawEventsCleared => { },
window.request_redraw(); ..
} },
Event::RedrawRequested(window_id) if window_id == window.id() => { ..
for event in &events { } => {
match event { let input_handled = match state.game_state {
state::Event::FocusChanged | state::Event::ButtonPressed => { state::GameState::Quiting => true,
sound_system.queue(sound_pack.bounce()); _ => input.update(key, element_state),
} };
state::Event::BallBounce(_pos) => { if !input_handled {
sound_system.queue(sound_pack.bounce()); process_input(element_state, key, control_flow);
}
state::Event::Score(_) => {
sound_system.queue(sound_pack.bounce());
}
state::Event::Resize(width, height) => {
// TODO: there should be a system that handles this
state.player1_score.position = (width * 0.25, 20.0).into();
state.player2_score.position = (width * 0.75, 20.0).into();
state.win_text.position = (width * 0.5, height * 0.5).into();
}
} }
} }
events.clear(); Event::WindowEvent {
event: WindowEvent::Resized(size),
visiblity_system.update_state(&input, &mut state, &mut events); ..
match state.game_state { } => {
state::GameState::MainMenu => { render.resize(size);
menu_system.update_state(&input, &mut state, &mut events); events.push(state::Event::Resize(size.width as f32, size.height as f32));
if state.game_state == state::GameState::Serving { }
serving_system.start(&mut state); Event::WindowEvent {
event: WindowEvent::RedrawRequested,
..
} => {
let dt = last_time.elapsed();
last_time = instant::Instant::now();
window.request_redraw();
for event in &events {
match event {
state::Event::FocusChanged | state::Event::ButtonPressed => {
sound_system.queue(sound_pack.bounce());
}
state::Event::BallBounce(_pos) => {
sound_system.queue(sound_pack.bounce());
}
state::Event::Score(_) => {
sound_system.queue(sound_pack.bounce());
}
state::Event::Resize(width, height) => {
// TODO: there should be a system that handles this
state.player1_score.position = (width * 0.25, 20.0).into();
state.player2_score.position = (width * 0.75, 20.0).into();
state.win_text.position = (width * 0.5, height * 0.5).into();
}
} }
} }
state::GameState::Serving => { events.clear();
serving_system.update_state(&input, &mut state, &mut events);
play_system.update_state(&input, &mut state, &mut events); visiblity_system.update_state(&input, dt, &mut state, &mut events);
if state.game_state == state::GameState::Playing { match state.game_state {
play_system.start(&mut state); state::GameState::MainMenu => {
menu_system.update_state(&input, dt, &mut state, &mut events);
if state.game_state == state::GameState::Serving {
serving_system.start(&mut state);
}
} }
} state::GameState::Serving => {
state::GameState::Playing => { serving_system.update_state(&input, dt, &mut state, &mut events);
ball_system.update_state(&input, &mut state, &mut events); play_system.update_state(&input, dt, &mut state, &mut events);
play_system.update_state(&input, &mut state, &mut events); if state.game_state == state::GameState::Playing {
if state.game_state == state::GameState::Serving { play_system.start(&mut state);
serving_system.start(&mut state); }
} else if state.game_state == state::GameState::GameOver {
game_over_system.start(&mut state);
} }
} state::GameState::Playing => {
state::GameState::GameOver => { ball_system.update_state(&input, dt, &mut state, &mut events);
game_over_system.update_state(&input, &mut state, &mut events); play_system.update_state(&input, dt, &mut state, &mut events);
if state.game_state == state::GameState::MainMenu { if state.game_state == state::GameState::Serving {
menu_system.start(&mut state); serving_system.start(&mut state);
} else if state.game_state == state::GameState::GameOver {
game_over_system.start(&mut state);
}
}
state::GameState::GameOver => {
game_over_system.update_state(&input, dt, &mut state, &mut events);
if state.game_state == state::GameState::MainMenu {
menu_system.start(&mut state);
}
}
state::GameState::Quiting => {
control_flow.exit();
} }
} }
state::GameState::Quiting => {}
}
render.render_state(&state); render.render_state(&state);
if state.game_state != state::GameState::Quiting { if state.game_state != state::GameState::Quiting {
window.request_redraw(); window.request_redraw();
}
} }
_ => {}
} }
_ => {} })
} .unwrap();
});
} }
fn process_input( fn process_input(
element_state: ElementState, element_state: ElementState,
keycode: VirtualKeyCode, keycode: KeyCode,
control_flow: &mut ControlFlow, control_flow: &EventLoopWindowTarget<()>,
) { ) {
match (keycode, element_state) { match (keycode, element_state) {
(VirtualKeyCode::Escape, ElementState::Pressed) => { (KeyCode::Escape, ElementState::Pressed) => control_flow.exit(),
*control_flow = ControlFlow::Exit;
}
_ => {} _ => {}
} }
} }

@ -12,8 +12,8 @@ use crate::state;
const FONT_BYTES: &[u8] = include_bytes!("../../res/fonts/PressStart2P-Regular.ttf"); const FONT_BYTES: &[u8] = include_bytes!("../../res/fonts/PressStart2P-Regular.ttf");
pub struct Render { pub struct Render<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
#[allow(dead_code)] #[allow(dead_code)]
adapter: wgpu::Adapter, adapter: wgpu::Adapter,
@ -26,7 +26,7 @@ pub struct Render {
staging_belt: wgpu::util::StagingBelt, staging_belt: wgpu::util::StagingBelt,
} }
impl Render { impl<'a> Render<'a> {
pub fn width(&self) -> f32 { pub fn width(&self) -> f32 {
self.config.width as f32 self.config.width as f32
} }
@ -36,20 +36,19 @@ impl Render {
self.config.height as f32 self.config.height as f32
} }
pub async fn new(window: &Window, size: PhysicalSize<u32>) -> Self { pub async fn new(window: &'a Window, size: PhysicalSize<u32>) -> Render<'a> {
log::warn!("size: {:?}", size); log::warn!("size: {:?}", size);
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@ -63,8 +62,8 @@ impl Render {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
limits: wgpu::Limits::downlevel_webgl2_defaults(), required_limits: wgpu::Limits::downlevel_webgl2_defaults(),
}, },
None, // Trace path None, // Trace path
) )
@ -89,8 +88,8 @@ impl Render {
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[], bind_group_layouts: &[],

@ -9,6 +9,7 @@ pub trait System {
fn update_state( fn update_state(
&self, &self,
input: &input::Input, input: &input::Input,
dt: instant::Duration,
state: &mut state::State, state: &mut state::State,
events: &mut Vec<state::Event>, events: &mut Vec<state::Event>,
); );
@ -19,6 +20,7 @@ impl System for VisibilitySystem {
fn update_state( fn update_state(
&self, &self,
_input: &input::Input, _input: &input::Input,
_dt: instant::Duration,
state: &mut state::State, state: &mut state::State,
_events: &mut Vec<state::Event>, _events: &mut Vec<state::Event>,
) { ) {
@ -60,6 +62,7 @@ impl System for MenuSystem {
fn update_state( fn update_state(
&self, &self,
input: &input::Input, input: &input::Input,
_dt: instant::Duration,
state: &mut state::State, state: &mut state::State,
events: &mut Vec<state::Event>, events: &mut Vec<state::Event>,
) { ) {
@ -92,21 +95,23 @@ impl System for PlaySystem {
fn update_state( fn update_state(
&self, &self,
input: &input::Input, input: &input::Input,
dt: instant::Duration,
state: &mut state::State, state: &mut state::State,
_events: &mut Vec<state::Event>, _events: &mut Vec<state::Event>,
) { ) {
let dt = dt.as_secs_f32();
// move the players // move the players
if input.p1_up_pressed { if input.p1_up_pressed {
state.player1.position.y += util::PLAYER_SPEED; state.player1.position.y += util::PLAYER_SPEED * dt;
} }
if input.p1_down_pressed { if input.p1_down_pressed {
state.player1.position.y -= util::PLAYER_SPEED; state.player1.position.y -= util::PLAYER_SPEED * dt;
} }
if input.p2_up_pressed { if input.p2_up_pressed {
state.player2.position.y += util::PLAYER_SPEED; state.player2.position.y += util::PLAYER_SPEED * dt;
} }
if input.p2_down_pressed { if input.p2_down_pressed {
state.player2.position.y -= util::PLAYER_SPEED; state.player2.position.y -= util::PLAYER_SPEED * dt;
} }
// normalize players // normalize players
@ -134,22 +139,25 @@ impl System for BallSystem {
fn update_state( fn update_state(
&self, &self,
_input: &input::Input, _input: &input::Input,
dt: instant::Duration,
state: &mut state::State, state: &mut state::State,
events: &mut Vec<state::Event>, events: &mut Vec<state::Event>,
) { ) {
let dt = dt.as_secs_f32();
// bounce the ball off the players // bounce the ball off the players
if state.player1.contains(&state.ball) { if state.player1.contains(&state.ball) {
events.push(state::Event::BallBounce(state.ball.position)); events.push(state::Event::BallBounce(state.ball.position));
state.ball.position.x -= state.ball.velocity.x - state.player1.size.x; state.ball.position.x -= state.ball.velocity.x * dt - state.player1.size.x;
state.ball.velocity = util::calc_ball_velocity(&state.ball, &state.player1); state.ball.velocity = util::calc_ball_velocity(&state.ball, &state.player1);
} else if state.player2.contains(&state.ball) { } else if state.player2.contains(&state.ball) {
events.push(state::Event::BallBounce(state.ball.position)); events.push(state::Event::BallBounce(state.ball.position));
state.ball.position.x -= state.ball.velocity.x + state.player2.size.x; state.ball.position.x -= state.ball.velocity.x * dt + state.player2.size.x;
state.ball.velocity.x *= -state.player2.size.y; state.ball.velocity.x *= -state.player2.size.y;
state.ball.velocity = util::calc_ball_velocity(&state.ball, &state.player2); state.ball.velocity = util::calc_ball_velocity(&state.ball, &state.player2);
} }
state.ball.position += state.ball.velocity; state.ball.position += state.ball.velocity * dt;
if state.ball.position.y > 1.0 { if state.ball.position.y > 1.0 {
events.push(state::Event::BallBounce(state.ball.position)); events.push(state::Event::BallBounce(state.ball.position));
state.ball.position.y = 1.0; state.ball.position.y = 1.0;
@ -199,12 +207,11 @@ impl System for ServingSystem {
fn update_state( fn update_state(
&self, &self,
_input: &input::Input, _input: &input::Input,
_dt: instant::Duration,
state: &mut state::State, state: &mut state::State,
_events: &mut Vec<state::Event>, _events: &mut Vec<state::Event>,
) { ) {
let current_time = instant::Instant::now(); if self.last_time.elapsed().as_secs_f32() > 2.0 {
let delta_time = current_time - self.last_time;
if delta_time.as_secs_f32() > 2.0 {
log::info!("Serving..."); log::info!("Serving...");
state.game_state = state::GameState::Playing; state.game_state = state::GameState::Playing;
} }
@ -242,12 +249,11 @@ impl System for GameOverSystem {
fn update_state( fn update_state(
&self, &self,
_input: &input::Input, _input: &input::Input,
_dt: instant::Duration,
state: &mut state::State, state: &mut state::State,
_events: &mut Vec<state::Event>, _events: &mut Vec<state::Event>,
) { ) {
let current_time = instant::Instant::now(); if self.last_time.elapsed().as_secs_f32() > 1.0 {
let delta_time = current_time - self.last_time;
if delta_time.as_secs_f32() > 1.0 {
state.game_state = state::GameState::MainMenu; state.game_state = state::GameState::MainMenu;
} }
} }

@ -2,8 +2,8 @@
use crate::state; use crate::state;
pub const PLAYER_SPEED: f32 = 0.05; pub const PLAYER_SPEED: f32 = 1.5;
pub const BALL_SPEED: f32 = 0.025; pub const BALL_SPEED: f32 = 1.0;
const BOUNCE_ANGLE: f32 = std::f32::consts::FRAC_PI_2; const BOUNCE_ANGLE: f32 = std::f32::consts::FRAC_PI_2;

@ -11,5 +11,5 @@ bytemuck = { version = "1.13.1", features = ["derive"] }
framework = { version = "0.1", path = "../framework"} framework = { version = "0.1", path = "../framework"}
glam = { version = "0.24.1", features = ["bytemuck"] } glam = { version = "0.24.1", features = ["bytemuck"] }
pollster = "0.3.0" pollster = "0.3.0"
wgpu = "0.18" wgpu = "0.19"
winit = "0.28.6" winit = { version = "0.29", features = ["rwh_05"] }

@ -1,6 +1,6 @@
use std::{time::Duration, f32::consts::FRAC_PI_2}; use std::{f32::consts::FRAC_PI_2, time::Duration};
use winit::{event::{VirtualKeyCode, MouseScrollDelta}, dpi::PhysicalPosition}; use winit::{dpi::PhysicalPosition, event::MouseScrollDelta, keyboard::KeyCode};
const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001; const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001;
@ -12,11 +12,7 @@ pub struct Camera {
} }
impl Camera { impl Camera {
pub fn new<V: Into<glam::Vec3>>( pub fn new<V: Into<glam::Vec3>>(position: V, yaw: f32, pitch: f32) -> Self {
position: V,
yaw: f32,
pitch: f32,
) -> Self {
Self { Self {
position: position.into(), position: position.into(),
yaw, yaw,
@ -94,34 +90,30 @@ impl CameraController {
} }
} }
pub fn process_keyboard(&mut self, key: VirtualKeyCode, pressed: bool) -> bool { pub fn process_keyboard(&mut self, key: KeyCode, pressed: bool) -> bool {
let amount = if pressed { let amount = if pressed { 1.0 } else { 0.0 };
1.0
} else {
0.0
};
match key { match key {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.amount_forward = amount; self.amount_forward = amount;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.amount_backward = amount; self.amount_backward = amount;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.amount_left = amount; self.amount_left = amount;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.amount_right = amount; self.amount_right = amount;
true true
} }
VirtualKeyCode::Space => { KeyCode::Space => {
self.amount_up = amount; self.amount_up = amount;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.amount_down = amount; self.amount_down = amount;
true true
} }
@ -183,4 +175,4 @@ impl CameraController {
camera.pitch = SAFE_FRAC_PI_2; camera.pitch = SAFE_FRAC_PI_2;
} }
} }
} }

@ -2,8 +2,9 @@ mod camera;
use std::f32::consts::PI; use std::f32::consts::PI;
use camera::{Camera, Projection, CameraController}; use camera::{Camera, CameraController, Projection};
use wgpu::util::{BufferInitDescriptor, DeviceExt}; use wgpu::util::{BufferInitDescriptor, DeviceExt};
use winit::keyboard::KeyCode;
const MAX_PARTICLES: u32 = 1000; const MAX_PARTICLES: u32 = 1000;
const PARTICLE_SIZE: u64 = 4 * 4 * 2; const PARTICLE_SIZE: u64 = 4 * 4 * 2;
@ -181,21 +182,25 @@ impl framework::Demo for Snow {
entries: &[wgpu::BindGroupLayoutEntry { entries: &[wgpu::BindGroupLayoutEntry {
binding: 0, binding: 0,
visibility: wgpu::ShaderStages::VERTEX, visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None, count: None,
}], }],
}); });
let uniforms_bind_group = display.device.create_bind_group(&wgpu::BindGroupDescriptor { let uniforms_bind_group = display
label: Some("uniforms_bind_group"), .device
layout: &uniforms_bind_group_layout, .create_bind_group(&wgpu::BindGroupDescriptor {
entries: &[ label: Some("uniforms_bind_group"),
wgpu::BindGroupEntry { layout: &uniforms_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0, binding: 0,
resource: uniform_buffer.as_entire_binding(), resource: uniform_buffer.as_entire_binding(),
} }],
], });
});
let draw_particles_layout = let draw_particles_layout =
display display
@ -260,14 +265,15 @@ impl framework::Demo for Snow {
self.camera_controller.process_mouse(dx, dy); self.camera_controller.process_mouse(dx, dy);
self.uniforms_dirty = true; self.uniforms_dirty = true;
} }
fn process_keyboard(&mut self, key: winit::event::VirtualKeyCode, pressed: bool) { fn process_keyboard(&mut self, key: KeyCode, pressed: bool) {
self.camera_controller.process_keyboard(key, pressed); self.camera_controller.process_keyboard(key, pressed);
self.uniforms_dirty = true; self.uniforms_dirty = true;
} }
fn resize(&mut self, display: &framework::Display) { fn resize(&mut self, display: &framework::Display) {
self.projection.resize(display.config.width, display.config.height); self.projection
.resize(display.config.width, display.config.height);
self.uniforms_dirty = true; self.uniforms_dirty = true;
self.uniforms_dirty = true; self.uniforms_dirty = true;
} }
@ -278,7 +284,9 @@ impl framework::Demo for Snow {
self.uniforms_dirty = false; self.uniforms_dirty = false;
self.camera_controller.update_camera(&mut self.camera, dt); self.camera_controller.update_camera(&mut self.camera, dt);
self.uniforms.view_proj = self.projection.calc_matrix() * self.camera.calc_matrix(); self.uniforms.view_proj = self.projection.calc_matrix() * self.camera.calc_matrix();
display.queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&self.uniforms)); display
.queue
.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&self.uniforms));
} }
let dt = dt.as_secs_f32(); let dt = dt.as_secs_f32();

@ -17,8 +17,8 @@ pollster = "0.3"
log = "0.4" log = "0.4"
rayon = "1.4" # NEW! rayon = "1.4" # NEW!
tobj = { version = "3.2", features = ["async"]} tobj = { version = "3.2", features = ["async"]}
wgpu = { version = "0.18"} wgpu = { version = "0.19"}
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
instant = "0.1" instant = "0.1"
async-std = "1" async-std = "1"
@ -31,7 +31,7 @@ features = ["png", "jpeg"]
reqwest = { version = "0.11" } reqwest = { version = "0.11" }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -3,6 +3,7 @@ use std::f32::consts::FRAC_PI_2;
use std::time::Duration; use std::time::Duration;
use winit::dpi::PhysicalPosition; use winit::dpi::PhysicalPosition;
use winit::event::*; use winit::event::*;
use winit::keyboard::KeyCode;
#[rustfmt::skip] #[rustfmt::skip]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new( pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
@ -104,34 +105,34 @@ impl CameraController {
} }
} }
pub fn process_keyboard(&mut self, key: VirtualKeyCode, state: ElementState) -> bool { pub fn process_keyboard(&mut self, key: KeyCode, state: ElementState) -> bool {
let amount = if state == ElementState::Pressed { let amount = if state == ElementState::Pressed {
1.0 1.0
} else { } else {
0.0 0.0
}; };
match key { match key {
VirtualKeyCode::W | VirtualKeyCode::Up => { KeyCode::KeyW | KeyCode::ArrowUp => {
self.amount_forward = amount; self.amount_forward = amount;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.amount_backward = amount; self.amount_backward = amount;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.amount_left = amount; self.amount_left = amount;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.amount_right = amount; self.amount_right = amount;
true true
} }
VirtualKeyCode::Space => { KeyCode::Space => {
self.amount_up = amount; self.amount_up = amount;
true true
} }
VirtualKeyCode::LShift => { KeyCode::ShiftLeft => {
self.amount_down = amount; self.amount_down = amount;
true true
} }

@ -4,7 +4,8 @@ use std::iter;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::Window, window::Window,
}; };
@ -130,9 +131,9 @@ struct LightUniform {
_padding2: u32, _padding2: u32,
} }
struct State { struct State<'a> {
window: Window, window: &'a Window,
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -218,26 +219,25 @@ fn create_render_pipeline(
}) })
} }
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
// # Safety let surface = instance.create_surface(window).unwrap();
//
// The surface needs to live as long as the window that created it.
// State owns the window so this should be safe.
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(), power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface), compatible_surface: Some(&surface),
force_fallback_adapter: false, force_fallback_adapter: false,
}) })
@ -247,10 +247,10 @@ impl State {
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
label: None, label: None,
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some. // we're building for the web we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -278,11 +278,10 @@ impl State {
height: size.height, height: size.height,
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
desired_maximum_frame_latency: 2, // The max number of frames that can be in the queue
view_formats: vec![], view_formats: vec![],
}; };
surface.configure(&device, &config);
let texture_bind_group_layout = let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[ entries: &[
@ -573,9 +572,9 @@ impl State {
fn input(&mut self, event: &WindowEvent) -> bool { fn input(&mut self, event: &WindowEvent) -> bool {
match event { match event {
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
virtual_keycode: Some(key), physical_key: PhysicalKey::Code(key),
state, state,
.. ..
}, },
@ -694,7 +693,7 @@ pub async fn run() {
} }
} }
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let title = env!("CARGO_PKG_NAME"); let title = env!("CARGO_PKG_NAME");
let window = winit::window::WindowBuilder::new() let window = winit::window::WindowBuilder::new()
.with_title(title) .with_title(title)
@ -706,34 +705,33 @@ pub async fn run() {
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })
.expect("Couldn't append canvas to document body."); .expect("Couldn't append canvas to document body.");
} }
let mut state = State::new(window).await; // NEW! let mut state = State::new(&window).await; // NEW!
let mut last_render_time = instant::Instant::now(); let mut last_render_time = instant::Instant::now();
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll; // let window = &window;
event_loop.run(move |event, control_flow| {
// let _ = window;
match event { match event {
Event::MainEventsCleared => state.window().request_redraw(),
// NEW!
Event::DeviceEvent { Event::DeviceEvent {
event: DeviceEvent::MouseMotion{ delta, }, event: DeviceEvent::MouseMotion{ delta, },
.. // We're not using device_id currently .. // We're not using device_id currently
} => if state.mouse_pressed { } => if state.mouse_pressed {
state.camera_controller.process_mouse(delta.0, delta.1) state.camera_controller.process_mouse(delta.0, delta.1)
} }
// UPDATED!
Event::WindowEvent { Event::WindowEvent {
ref event, ref event,
window_id, window_id,
@ -742,39 +740,37 @@ pub async fn run() {
#[cfg(not(target_arch="wasm32"))] #[cfg(not(target_arch="wasm32"))]
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); state.resize(*physical_size);
} }
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { WindowEvent::RedrawRequested => {
state.resize(**new_inner_size); state.window().request_redraw();
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => control_flow.exit(),
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
} }
_ => {} _ => {}
} }
} }
Event::RedrawRequested(window_id) if window_id == state.window().id() => {
let now = instant::Instant::now();
let dt = now - last_render_time;
last_render_time = now;
state.update(dt);
match state.render() {
Ok(_) => {}
// Reconfigure the surface if it's lost or outdated
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => state.resize(state.size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
// We're ignoring timeouts
Err(wgpu::SurfaceError::Timeout) => log::warn!("Surface timeout"),
}
}
_ => {} _ => {}
} }
}); }).unwrap();
} }

@ -189,7 +189,7 @@ pub async fn load_model(
// Average the tangents/bitangents // Average the tangents/bitangents
for (i, n) in triangles_included.into_iter().enumerate() { for (i, n) in triangles_included.into_iter().enumerate() {
let denom = 1.0 / n as f32; let denom = 1.0 / n as f32;
let mut v = &mut vertices[i]; let v = &mut vertices[i];
v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into(); v.tangent = (cgmath::Vector3::from(v.tangent) * denom).into();
v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into(); v.bitangent = (cgmath::Vector3::from(v.bitangent) * denom).into();
} }

@ -1,6 +1,5 @@
use anyhow::*; use anyhow::*;
use image::GenericImageView; use image::GenericImageView;
use std::num::NonZeroU32;
pub struct Texture { pub struct Texture {
pub texture: wgpu::Texture, pub texture: wgpu::Texture,
@ -17,8 +16,8 @@ impl Texture {
label: &str, label: &str,
) -> Self { ) -> Self {
let size = wgpu::Extent3d { let size = wgpu::Extent3d {
width: config.width, width: config.width.max(1),
height: config.height, height: config.height.max(1),
depth_or_array_layers: 1, depth_or_array_layers: 1,
}; };
let desc = wgpu::TextureDescriptor { let desc = wgpu::TextureDescriptor {

@ -9,7 +9,7 @@ edition = "2018"
[dependencies] [dependencies]
image = "0.24" image = "0.24"
shaderc = "0.8" shaderc = "0.8"
wgpu = { version = "0.18", features = ["spirv"] } wgpu = { version = "0.19", features = ["spirv"] }
pollster = "0.3" pollster = "0.3"
futures-intrusive = "0.4" futures-intrusive = "0.4"

@ -1,5 +1,3 @@
use std::num::NonZeroU32;
async fn run() { async fn run() {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), backends: wgpu::Backends::all(),

@ -12,4 +12,4 @@ env_logger = "0.10"
rayon = "1" rayon = "1"
serde = { version = "1", features = ["derive"]} serde = { version = "1", features = ["derive"]}
serde_json = "1" serde_json = "1"
wasm-bindgen-cli-support = "0.2.86" wasm-bindgen-cli-support = "0.2.90"

@ -1,5 +1,9 @@
use std::{path::PathBuf, process::Command}; use std::{
path::PathBuf,
process::{Command, ExitStatus},
};
use anyhow::bail;
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
use wasm_bindgen_cli_support::Bindgen; use wasm_bindgen_cli_support::Bindgen;
@ -37,7 +41,11 @@ fn main() -> anyhow::Result<()> {
for target in &targets { for target in &targets {
command.arg("-p").arg(&target.package); command.arg("-p").arg(&target.package);
} }
command.spawn()?.wait()?; let status = command.spawn()?.wait()?;
if !status.success() {
bail!("Failed to compile WASM with code ({status})");
}
let errors = targets let errors = targets
.par_iter() .par_iter()

@ -1,45 +1,52 @@
# Dependencies and the window # Dependencies and the window
## Boring, I know ## Boring, I know
Some of you reading this are very experienced with opening up windows in Rust and probably have your favorite windowing library, but this guide is designed for everybody, so it's something that we need to cover. Luckily, you don't need to read this if you know what you're doing. One thing that you do need to know is that whatever windowing solution you use needs to support the [raw-window-handle](https://github.com/rust-windowing/raw-window-handle) crate. Some of you reading this are very experienced with opening up windows in Rust and probably have your favorite windowing library, but this guide is designed for everybody, so it's something that we need to cover. Luckily, you don't need to read this if you know what you're doing. One thing that you do need to know is that whatever windowing solution you use needs to support the [raw-window-handle](https://github.com/rust-windowing/raw-window-handle) crate.
## What crates are we using? ## What crates are we using?
For the beginner stuff, we're going to keep things very simple. We'll add things as we go, but I've listed the relevant `Cargo.toml` bits below. For the beginner stuff, we're going to keep things very simple. We'll add things as we go, but I've listed the relevant `Cargo.toml` bits below.
```toml ```toml
[dependencies] [dependencies]
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
wgpu = "0.18" wgpu = "0.19.3"
``` ```
## Using Rust's new resolver ## Using Rust's new resolver
As of version 0.10, wgpu requires Cargo's [newest feature resolver](https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2), which is the default in the 2021 edition (any new project started with Rust version 1.56.0 or newer). However, if you are still using the 2018 edition, you must include `resolver = "2"` in either the `[package]` section of `Cargo.toml` if you are working on a single crate or the `[workspace]` section of the root `Cargo.toml` in a workspace. As of version 0.10, wgpu requires Cargo's [newest feature resolver](https://doc.rust-lang.org/cargo/reference/resolver.html#feature-resolver-version-2), which is the default in the 2021 edition (any new project started with Rust version 1.56.0 or newer). However, if you are still using the 2018 edition, you must include `resolver = "2"` in either the `[package]` section of `Cargo.toml` if you are working on a single crate or the `[workspace]` section of the root `Cargo.toml` in a workspace.
## env_logger ## env_logger
It is very important to enable logging via `env_logger::init();`. It is very important to enable logging via `env_logger::init();`.
When wgpu hits any error, it panics with a generic message, while logging the real error via the log crate. When wgpu hits any error, it panics with a generic message, while logging the real error via the log crate.
This means if you don't include `env_logger::init()`, wgpu will fail silently, leaving you very confused! This means if you don't include `env_logger::init()`, wgpu will fail silently, leaving you very confused!
(This has been done in the code below) (This has been done in the code below)
## Create a new project ## Create a new project
run ```cargo new project_name``` where project_name is the name of the project. run ```cargo new project_name``` where project_name is the name of the project.
(In the example below, I have used 'tutorial1_window') (In the example below, I have used 'tutorial1_window')
## The code ## The code
There's not much going on here yet, so I'm just going to post the code in full. Just paste this into your `lib.rs` or equivalent. There's not much going on here yet, so I'm just going to post the code in full. Just paste this into your `lib.rs` or equivalent.
```rust ```rust
use winit::{ use winit::{
event::*, event::*,
event_loop::{ControlFlow, EventLoop}, event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
window::WindowBuilder, window::WindowBuilder,
}; };
pub fn run() { pub fn run() {
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new().unwrap();
let window = WindowBuilder::new().build(&event_loop).unwrap(); let window = WindowBuilder::new().build(&event_loop).unwrap();
event_loop.run(move |event, _, control_flow| match event { event_loop.run(move |event, _, control_flow| match event {
@ -49,10 +56,10 @@ pub fn run() {
} if window_id == window.id() => match event { } if window_id == window.id() => match event {
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
@ -110,7 +117,7 @@ cfg-if = "1"
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4.30" wasm-bindgen-futures = "0.4.30"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [
@ -174,14 +181,14 @@ Next, after we create our event loop and window, we need to add a canvas to the
// Winit prevents sizing with CSS, so we have to set // Winit prevents sizing with CSS, so we have to set
// the size manually when on web. // the size manually when on web.
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
window.set_inner_size(PhysicalSize::new(450, 400)); let _ = window.request_inner_size(PhysicalSize::new(450, 400));
use winit::platform::web::WindowExtWebSys; use winit::platform::web::WindowExtWebSys;
web_sys::window() web_sys::window()
.and_then(|win| win.document()) .and_then(|win| win.document())
.and_then(|doc| { .and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?; let dst = doc.get_element_by_id("wasm-example")?;
let canvas = web_sys::Element::from(window.canvas()); let canvas = web_sys::Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?; dst.append_child(&canvas).ok()?;
Some(()) Some(())
}) })

@ -7,8 +7,8 @@ For convenience, we're going to pack all the fields into a struct and create som
// lib.rs // lib.rs
use winit::window::Window; use winit::window::Window;
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -16,12 +16,12 @@ struct State {
// The window must be declared after the surface so // The window must be declared after the surface so
// it gets dropped after it as the surface contains // it gets dropped after it as the surface contains
// unsafe references to the window's resources. // unsafe references to the window's resources.
window: Window, window: &'a Window,
} }
impl State { impl<'a> State<'a> {
// Creating some of the wgpu types requires async code // Creating some of the wgpu types requires async code
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
todo!() todo!()
} }
@ -53,15 +53,18 @@ I'm glossing over `State`s fields, but they'll make more sense as I explain the
The code for this is pretty straightforward, but let's break it down a bit. The code for this is pretty straightforward, but let's break it down a bit.
```rust ```rust
impl State { impl<'a> State<'a> {
// ... // ...
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
let size = window.inner_size(); let size = window.inner_size();
// The instance is a handle to our GPU // The instance is a handle to our GPU
// Backends::all => Vulkan + Metal + DX12 + Browser WebGPU // Backends::all => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), #[cfg(not(target_arch="wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch="wasm32")]
backends: wgpu::Backends::GL,
..Default::default() ..Default::default()
}); });
@ -127,10 +130,10 @@ Let's use the `adapter` to create the device and queue.
```rust ```rust
let (device, queue) = adapter.request_device( let (device, queue) = adapter.request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if // WebGL doesn't support all of wgpu's features, so if
// we're building for the web, we'll have to disable some. // we're building for the web, we'll have to disable some.
limits: if cfg!(target_arch = "wasm32") { required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults() wgpu::Limits::downlevel_webgl2_defaults()
} else { } else {
wgpu::Limits::default() wgpu::Limits::default()
@ -145,7 +148,7 @@ The `features` field on `DeviceDescriptor` allows us to specify what extra featu
<div class="note"> <div class="note">
The graphics card you have limits the features you can use. If you want to use certain features, you may need to limit what devices you support or provide workarounds. The graphics card you haverequired_limits the features you can use. If you want to use certain features, you may need to limit what devices you support or provide workarounds.
You can get a list of features supported by your device using `adapter.features()` or `device.features()`. You can get a list of features supported by your device using `adapter.features()` or `device.features()`.
@ -153,7 +156,7 @@ You can view a full list of features [here](https://docs.rs/wgpu/latest/wgpu/str
</div> </div>
The `limits` field describes the limit of certain types of resources that we can create. We'll use the defaults for this tutorial so we can support most devices. You can view a list of limits [here](https://docs.rs/wgpu/latest/wgpu/struct.Limits.html). The `limits` field describes the limit of certain types of resources that we can create. We'll use the defaults for this tutorial so we can support most devices. You can view a list ofrequired_limits [here](https://docs.rs/wgpu/latest/wgpu/struct.Limits.html).
```rust ```rust
let surface_caps = surface.get_capabilities(&adapter); let surface_caps = surface.get_capabilities(&adapter);
@ -173,8 +176,8 @@ The `limits` field describes the limit of certain types of resources that we can
present_mode: surface_caps.present_modes[0], present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2,
}; };
surface.configure(&device, &config);
``` ```
Here we are defining a config for our surface. This will define how the surface creates its underlying `SurfaceTexture`s. We will talk about `SurfaceTexture` when we get to the `render` function. For now, let's talk about the config's fields. Here we are defining a config for our surface. This will define how the surface creates its underlying `SurfaceTexture`s. We will talk about `SurfaceTexture` when we get to the `render` function. For now, let's talk about the config's fields.
@ -210,7 +213,7 @@ Regardless, `PresentMode::Fifo` will always be supported, and `PresentMode::Auto
Now that we've configured our surface properly, we can add these new fields at the end of the method. Now that we've configured our surface properly, we can add these new fields at the end of the method.
```rust ```rust
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
// ... // ...
Self { Self {
@ -233,9 +236,9 @@ Our `window` has beened moved to the State instance, we will need to update our
pub async fn run() { pub async fn run() {
// Window setup... // Window setup...
let mut state = State::new(window).await; let mut state = State::new(&window).await;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, control_flow| {
match event { match event {
Event::WindowEvent { Event::WindowEvent {
ref event, ref event,
@ -286,16 +289,16 @@ If we try to build WASM now, it will fail because `wasm-bindgen` doesn't support
```toml ```toml
[dependencies] [dependencies]
cfg-if = "1" cfg-if = "1"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
wgpu = "0.18" wgpu = "0.19"
pollster = "0.3" pollster = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
console_log = "1.0" console_log = "1.0"
wgpu = { version = "0.18", features = ["webgl"]} wgpu = { version = "0.19", features = ["webgl"]}
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = [

@ -156,8 +156,8 @@ This is the part where we finally make the thing in the title: the pipeline. Fir
```rust ```rust
// lib.rs // lib.rs
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,

@ -247,7 +247,7 @@ struct State {
num_vertices: u32, num_vertices: u32,
} }
impl State { impl<'a> State<'a> {
// ... // ...
fn new(...) -> Self { fn new(...) -> Self {
// ... // ...
@ -380,8 +380,8 @@ let num_indices = INDICES.len() as u32;
We don't need to implement `Pod` and `Zeroable` for our indices because `bytemuck` has already implemented them for basic types such as `u16`. That means we can just add `index_buffer` and `num_indices` to the `State` struct. We don't need to implement `Pod` and `Zeroable` for our indices because `bytemuck` has already implemented them for basic types such as `u16`. That means we can just add `index_buffer` and `num_indices` to the `State` struct.
```rust ```rust
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,

@ -242,8 +242,8 @@ Looking at this, you might get a bit of déjà vu! That's because a `BindGroup`
Now that we have our `diffuse_bind_group`, let's add it to our `State` struct: Now that we have our `diffuse_bind_group`, let's add it to our `State` struct:
```rust ```rust
struct State { struct State<'a> {
surface: wgpu::Surface, surface: wgpu::Surface<'a>,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration, config: wgpu::SurfaceConfiguration,
@ -259,7 +259,7 @@ struct State {
Make sure we return these fields in the `new` method: Make sure we return these fields in the `new` method:
```rust ```rust
impl State { impl<'a> State<'a> {
async fn new() -> Self { async fn new() -> Self {
// ... // ...
Self { Self {
@ -446,11 +446,11 @@ For convenience, let's pull our texture code into its own module. We'll first ne
[dependencies] [dependencies]
image = "0.23" image = "0.23"
cgmath = "0.18" cgmath = "0.18"
winit = "0.28" winit = { version = "0.29", features = ["rwh_05"] }
env_logger = "0.10" env_logger = "0.10"
log = "0.4" log = "0.4"
pollster = "0.3" pollster = "0.3"
wgpu = "0.18" wgpu = "0.19"
bytemuck = { version = "1.12", features = [ "derive" ] } bytemuck = { version = "1.12", features = [ "derive" ] }
anyhow = "1.0" # NEW! anyhow = "1.0" # NEW!
``` ```
@ -593,7 +593,7 @@ struct State {
``` ```
```rust ```rust
impl State { impl<'a> State<'a> {
async fn new() -> Self { async fn new() -> Self {
// ... // ...
Self { Self {

@ -310,20 +310,19 @@ impl CameraController {
.. ..
} => { } => {
let is_pressed = *state == ElementState::Pressed; let is_pressed = *state == ElementState::Pressed;
match keycode { match keycode {KeyCode::KeyW | KeyCode::ArrowUp => {
VirtualKeyCode::W | VirtualKeyCode::Up => {
self.is_forward_pressed = is_pressed; self.is_forward_pressed = is_pressed;
true true
} }
VirtualKeyCode::A | VirtualKeyCode::Left => { KeyCode::KeyA | KeyCode::ArrowLeft => {
self.is_left_pressed = is_pressed; self.is_left_pressed = is_pressed;
true true
} }
VirtualKeyCode::S | VirtualKeyCode::Down => { KeyCode::KeyS | KeyCode::ArrowDown => {
self.is_backward_pressed = is_pressed; self.is_backward_pressed = is_pressed;
true true
} }
VirtualKeyCode::D | VirtualKeyCode::Right => { KeyCode::KeyD | KeyCode::ArrowRight => {
self.is_right_pressed = is_pressed; self.is_right_pressed = is_pressed;
true true
} }
@ -381,8 +380,8 @@ struct State {
// ... // ...
} }
// ... // ...
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
// ... // ...
let camera_controller = CameraController::new(0.2); let camera_controller = CameraController::new(0.2);
// ... // ...

@ -97,8 +97,8 @@ const INSTANCE_DISPLACEMENT: cgmath::Vector3<f32> = cgmath::Vector3::new(NUM_INS
Now, we can create the actual instances. Now, we can create the actual instances.
```rust ```rust
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
// ... // ...
let instances = (0..NUM_INSTANCES_PER_ROW).flat_map(|z| { let instances = (0..NUM_INSTANCES_PER_ROW).flat_map(|z| {
(0..NUM_INSTANCES_PER_ROW).map(move |x| { (0..NUM_INSTANCES_PER_ROW).map(move |x| {

@ -453,7 +453,7 @@ where
Finally, we want to add Light rendering to our render passes. Finally, we want to add Light rendering to our render passes.
```rust ```rust
impl State { impl<'a> State<'a> {
// ... // ...
fn render(&mut self) -> Result<(), wgpu::SurfaceError> { fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
// ... // ...

@ -568,7 +568,7 @@ I found a cobblestone texture with a matching normal map and created a `debug_ma
```rust ```rust
// lib.rs // lib.rs
impl State { impl<'a> State<'a> {
async fn new(window: &Window) -> Result<Self> { async fn new(window: &Window) -> Result<Self> {
// ... // ...
let debug_material = { let debug_material = {

@ -27,7 +27,12 @@ const SAFE_FRAC_PI_2: f32 = FRAC_PI_2 - 0.0001;
`std::time::Instant` panics on WASM, so we'll use the [instant crate](https://docs.rs/instant). You'll want to include it in your `Cargo.toml`: `std::time::Instant` panics on WASM, so we'll use the [instant crate](https://docs.rs/instant). You'll want to include it in your `Cargo.toml`:
```toml ```toml
[dependencies]
# ...
instant = "0.1" instant = "0.1"
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant = { version = "0.1", features = [ "wasm-bindgen" ] }
``` ```
</div> </div>
@ -293,8 +298,8 @@ You'll need to import `winit::dpi::PhysicalPosition` if you haven't already.
We need to update `new()` as well. We need to update `new()` as well.
```rust ```rust
impl State { impl<'a> State<'a> {
async fn new(window: Window) -> Self { async fn new(window: &'a Window) -> State<'a> {
// ... // ...
// UPDATED! // UPDATED!
@ -369,8 +374,7 @@ Here are the changes to `run()`:
```rust ```rust
fn main() { fn main() {
// ... // ...
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, control_flow| {
*control_flow = ControlFlow::Poll;
match event { match event {
// ... // ...
// NEW! // NEW!
@ -389,14 +393,14 @@ fn main() {
#[cfg(not(target_arch="wasm32"))] #[cfg(not(target_arch="wasm32"))]
WindowEvent::CloseRequested WindowEvent::CloseRequested
| WindowEvent::KeyboardInput { | WindowEvent::KeyboardInput {
input: event:
KeyboardInput { KeyEvent {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape), physical_key: PhysicalKey::Code(KeyCode::Escape),
.. ..
}, },
.. ..
} => *control_flow = ControlFlow::Exit, } => control_flow.exit(),
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
state.resize(*physical_size); state.resize(*physical_size);
} }
@ -437,10 +441,9 @@ We still need to calculate `dt`. Let's do that in the `main` function.
```rust ```rust
fn main() { fn main() {
// ... // ...
let mut state = State::new(window).await; let mut state = State::new(&window).await;
let mut last_render_time = instant::Instant::now(); // NEW! let mut last_render_time = instant::Instant::now(); // NEW!
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, control_flow| {
*control_flow = ControlFlow::Poll;
match event { match event {
// ... // ...
// UPDATED! // UPDATED!

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save