const CONTINUOUS_REDRAW: bool = true;
#[cfg(not(target_arch = "wasm32"))]
struct StdOutLogger;
#[cfg(not(target_arch = "wasm32"))]
impl log::Log for StdOutLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::Level::Info
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
println!("{} - {}", record.level(), record.args());
}
}
fn flush(&self) {}
}
#[cfg(not(target_arch = "wasm32"))]
static LOGGER: StdOutLogger = StdOutLogger;
#[cfg(not(target_arch = "wasm32"))]
pub struct Spawner<'a> {
executor: async_executor::LocalExecutor<'a>,
}
#[cfg(not(target_arch = "wasm32"))]
impl<'a> Spawner<'a> {
fn new() -> Self {
Self {
executor: async_executor::LocalExecutor::new(),
}
}
#[allow(dead_code)]
pub fn spawn_local(&self, future: impl std::future::Future<Output = ()> + 'a) {
self.executor.spawn(future).detach();
}
fn run_until_stalled(&self) {
while self.executor.try_tick() {}
}
}
#[cfg(target_arch = "wasm32")]
use winit::platform::web::WindowExtWebSys;
#[cfg(target_arch = "wasm32")]
pub struct Spawner {}
#[cfg(target_arch = "wasm32")]
impl Spawner {
fn new() -> Self {
Self {}
}
#[allow(dead_code)]
pub fn spawn_local(&self, future: impl std::future::Future<Output = ()> + 'static) {
wasm_bindgen_futures::spawn_local(future);
}
fn run_until_stalled(&self) {}
}
pub trait Application {
fn new(device: &wgpu::Device, queue: &mut wgpu::Queue, surface_configuration: &wgpu::SurfaceConfiguration) -> Self;
fn resize(&mut self, device: &wgpu::Device, queue: &mut wgpu::Queue, surface_configuration: &wgpu::SurfaceConfiguration);
fn render(&mut self, device: &wgpu::Device, queue: &mut wgpu::Queue, frame: &wgpu::SurfaceTexture, frame_time: f32);
fn mouse_motion(&mut self, delta: (f64, f64));
fn keyboard_input(&mut self, input: winit::event::KeyboardInput);
}
pub struct ApplicationManager {
window: winit::window::Window,
instance: wgpu::Instance,
size: winit::dpi::PhysicalSize<u32>,
surface: wgpu::Surface,
adapter: wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
}
impl ApplicationManager {
pub fn run<A: 'static + Application>(title: &'static str) {
let event_loop = winit::event_loop::EventLoop::new();
#[cfg(not(target_arch = "wasm32"))]
{
let setup = pollster::block_on(ApplicationManager::setup(&event_loop, title));
setup.start_loop::<A>(event_loop);
}
#[cfg(target_arch = "wasm32")]
{
wasm_bindgen_futures::spawn_local(async move {
let setup = ApplicationManager::setup(&event_loop, title).await;
setup.start_loop::<A>(event_loop);
});
}
}
async fn setup(event_loop: &winit::event_loop::EventLoop<()>, title: &'static str) -> Self {
let mut builder = winit::window::WindowBuilder::new();
builder = builder.with_title(title);
let window = builder.build(event_loop).unwrap();
window.set_cursor_grab(winit::window::CursorGrabMode::Locked).unwrap();
window.set_cursor_visible(false);
#[cfg(not(target_arch = "wasm32"))]
log::set_logger(&LOGGER)
.map(|()| log::set_max_level(log::LevelFilter::Info))
.expect("Could not initialize logger");
#[cfg(target_arch = "wasm32")]
{
console_log::init().expect("Could not initialize logger");
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
web_sys::window()
.and_then(|window| window.document())
.and_then(|document| document.body())
.and_then(|body| body.append_child(&web_sys::Element::from(window.canvas())).ok())
.expect("Couldn't append canvas to document body");
}
let instance = wgpu::Instance::default();
let (size, surface) = unsafe {
let size = window.inner_size();
let surface = instance.create_surface(&window).expect("WebGPU is not supported or not enabled");
(size, surface)
};
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: Some(&surface),
})
.await
.expect("No suitable GPU adapters found on the system!");
let adapter_info = adapter.get_info();
log::info!("Using {} ({:?})", adapter_info.name, adapter_info.backend);
let required_features = wgpu::Features::default();
let required_limits = wgpu::Limits {
max_compute_invocations_per_workgroup: 1024,
max_storage_buffer_binding_size: 1024 * 1024 * 1024,
max_buffer_size: 1024 * 1024 * 1024,
..wgpu::Limits::default()
};
let adapter_features = adapter.features();
assert!(
adapter_features.contains(required_features),
"Adapter does not support required features: {:?}",
required_features - adapter_features
);
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: required_features,
limits: required_limits,
},
None,
)
.await
.expect("Unable to find a suitable GPU adapter!");
Self {
window,
instance,
size,
surface,
adapter,
device,
queue,
}
}
fn generate_surface_configuration(&self) -> wgpu::SurfaceConfiguration {
wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: wgpu::TextureFormat::Bgra8Unorm, view_formats: vec![wgpu::TextureFormat::Bgra8Unorm],
width: self.size.width,
height: self.size.height,
present_mode: wgpu::PresentMode::Fifo, alpha_mode: wgpu::CompositeAlphaMode::Opaque, }
}
fn resize<A: 'static + Application>(&mut self, application: &mut A, size: winit::dpi::PhysicalSize<u32>) {
self.size.width = size.width.max(1);
self.size.height = size.height.max(1);
self.window.request_redraw();
let surface_configuration = self.generate_surface_configuration();
self.surface.configure(&self.device, &surface_configuration);
application.resize(&self.device, &mut self.queue, &surface_configuration);
}
fn start_loop<A: 'static + Application>(mut self, event_loop: winit::event_loop::EventLoop<()>) {
let surface_configuration = self.generate_surface_configuration();
let mut application = A::new(&self.device, &mut self.queue, &surface_configuration);
self.resize(&mut application, self.size);
#[cfg(not(target_arch = "wasm32"))]
let start_time = std::time::Instant::now();
#[cfg(target_arch = "wasm32")]
let (clock, start_time) = {
let clock = web_sys::window().and_then(|window| window.performance()).unwrap();
let start_time = clock.now();
(clock, start_time)
};
let mut prev_frame = start_time;
let mut rolling_average = 0.0f32;
let mut average_window = [0.0f32; 64];
let mut average_window_slot = 0;
let spawner = Spawner::new();
event_loop.run(move |event, _, control_flow| {
let _ = (&self.instance, &self.adapter);
*control_flow = winit::event_loop::ControlFlow::Wait;
match event {
winit::event::Event::RedrawEventsCleared => {
spawner.run_until_stalled();
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::Resized(new_inner_size),
..
} => {
self.resize(&mut application, new_inner_size);
}
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. },
..
} => {
self.resize(&mut application, *new_inner_size);
}
winit::event::Event::DeviceEvent {
event: winit::event::DeviceEvent::MouseMotion { delta },
..
} => {
application.mouse_motion(delta);
if !CONTINUOUS_REDRAW {
self.window.request_redraw();
}
}
winit::event::Event::WindowEvent { event, .. } => match event {
winit::event::WindowEvent::KeyboardInput {
input:
winit::event::KeyboardInput {
virtual_keycode: Some(winit::event::VirtualKeyCode::Escape),
state: winit::event::ElementState::Pressed,
..
},
..
}
| winit::event::WindowEvent::CloseRequested => {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
winit::event::WindowEvent::KeyboardInput { input, .. } => {
application.keyboard_input(input);
if !CONTINUOUS_REDRAW {
self.window.request_redraw();
}
}
_ => {}
},
winit::event::Event::RedrawRequested(_) => {
#[cfg(not(target_arch = "wasm32"))]
let frame_time = {
let now = std::time::Instant::now();
let frame_time = (now - prev_frame).as_secs_f32();
prev_frame = now;
frame_time
};
#[cfg(target_arch = "wasm32")]
let frame_time = {
let now = clock.now();
let frame_time = (now - prev_frame) * 0.001;
prev_frame = now;
frame_time
};
rolling_average -= average_window[average_window_slot];
average_window[average_window_slot] = frame_time;
rolling_average += average_window[average_window_slot];
average_window_slot = (average_window_slot + 1) % average_window.len();
log::info!(
"frame_time={} rolling_average={} fps={}",
frame_time,
rolling_average / average_window.len() as f32,
1.0 / (rolling_average / average_window.len() as f32),
);
let frame = self.surface.get_current_texture().unwrap();
application.render(&self.device, &mut self.queue, &frame, frame_time);
frame.present();
if CONTINUOUS_REDRAW {
self.window.request_redraw();
}
}
_ => {}
}
});
}
}
#[allow(dead_code)]
fn main() {}