Skip to content

Commit

Permalink
implement "Learn Wgpu" tutorial chapter: "The Surface"
Browse files Browse the repository at this point in the history
  • Loading branch information
zierf committed May 16, 2024
1 parent fcf3a5a commit 3bf10c8
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 24 deletions.
68 changes: 56 additions & 12 deletions src/app.rs → src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
mod state;

#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;

use tracing::info;

use std::sync::Arc;

use winit::{
application::ApplicationHandler,
dpi::PhysicalSize,
event::*,
event_loop::ActiveEventLoop,
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowId},
};

#[derive(Debug, Default)]
use state::State;

#[derive(Debug)]
pub struct App {
window: Option<Window>,
width: u32,
height: u32,
title: String,
window: Option<Arc<Window>>,
state: Option<State>,
}

impl App {
Expand All @@ -25,27 +33,24 @@ impl App {
title: title.into(),
width,
height,
..Default::default()
window: None,
state: None,
}
}
}

/// @link https://docs.rs/winit/latest/winit/index.html
/// @link https://docs.rs/winit/0.30.0/winit/index.html
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window_attributes = Window::default_attributes()
.with_title(&self.title)
.with_inner_size(winit::dpi::LogicalSize::new(self.width, self.height));

let window = event_loop.create_window(window_attributes).unwrap();
let window = Arc::new(event_loop.create_window(window_attributes).unwrap());

#[cfg(target_arch = "wasm32")]
{
// Winit prevents sizing with CSS, so we have to set
// the size manually when on web.
use winit::dpi::PhysicalSize;
let _ = window.request_inner_size(PhysicalSize::new(self.width, self.height));

// web import trait method WindowExtWebSys::canvas(&self)
use winit::platform::web::WindowExtWebSys;

web_sys::window()
Expand All @@ -68,10 +73,28 @@ impl ApplicationHandler for App {
.expect("Couldn't append canvas to element!");
}

// FIXME requesting adapter and device return futures, but ApplicationHandler::resumed is synchronous
let state = pollster::block_on(State::new(Arc::clone(&window)));

self.window = Some(window);
self.state = Some(state);
}

fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
let window = self.window.as_ref().unwrap();
let state = self.state.as_mut().unwrap();

// WindowEvent has a WindowId member. In multi-window environments, it should be compared
// to the value returned by Window::id() to determine which Window dispatched the event.
// https://docs.rs/winit/latest/winit/#event-handling
if window_id != window.id() {
return;
}

if state.input(&event) {
return;
}

match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
Expand All @@ -86,14 +109,35 @@ impl ApplicationHandler for App {
println!("Close requested, stopping …");
event_loop.exit();
}
WindowEvent::Resized(physical_size) => {
state.resize(physical_size);
}
WindowEvent::ScaleFactorChanged {
scale_factor: _, ..
} => {
state.resize(PhysicalSize::new(self.width, self.height));
}
WindowEvent::RedrawRequested => {
// Redraw the application.
//
// It's preferable for applications that do not render continuously to render in
// this event rather than in AboutToWait, since rendering in here allows
// the program to gracefully handle redraws requested by the OS.

// Draw.
// Draw
let size = state.size();

state.update();

match state.render() {
Ok(_) => {}
// Reconfigure the surface if lost
Err(wgpu::SurfaceError::Lost) => state.resize(size),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => event_loop.exit(),
// All other errors (Outdated, Timeout) should be resolved by the next frame
Err(e) => eprintln!("{:?}", e),
}

// Queue a RedrawRequested event.
//
Expand Down
190 changes: 190 additions & 0 deletions src/app/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use std::sync::Arc;

use wgpu::PresentMode;
use winit::{dpi::PhysicalSize, event::WindowEvent, window::Window};

#[derive(Debug)]
pub struct State {
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
size: PhysicalSize<u32>,
#[cfg(target_arch = "wasm32")]
window: Arc<Window>,
}

impl State {
// Creating some of the wgpu types requires async code
pub async fn new(window: Arc<Window>) -> Self {
let size = window.inner_size();

// The instance is a handle to our GPU
// Backends::all => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
#[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
..Default::default()
});

// The surface needs to live as long as the window that created it.
// https://github.com/rust-windowing/winit/issues/3626#issuecomment-2081794856
// https://users.rust-lang.org/t/in-wgpu-how-do-i-reset-a-buffer-after-making-it-device-create-buffer-init/106391/13
let surface = instance.create_surface(Arc::clone(&window)).unwrap();

// alternatively use instance.enumerate_adapters to manually check for a proper adapter
// https://sotrh.github.io/learn-wgpu/beginner/tutorial2-surface/#state-new
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();

// use adapter.features() or device.features() to get a list of supported features
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
required_features: wgpu::Features::empty(),
// WebGL doesn't support all of wgpu's features, so if
// we're building for the web, we'll have to disable some.
#[cfg(target_arch = "wasm32")]
required_limits: wgpu::Limits::downlevel_webgl2_defaults(),
#[cfg(not(target_arch = "wasm32"))]
required_limits: wgpu::Limits::default(),
label: None,
},
None, // Trace path
)
.await
.unwrap();

let surface_caps = surface.get_capabilities(&adapter);
// Shader code in this tutorial assumes an sRGB surface texture. Using a different
// one will result in all the colors coming out darker. If you want to support non
// sRGB surfaces, you'll need to account for that when drawing to the frame.
let surface_format = surface_caps
.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);

let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: PresentMode::AutoNoVsync, //surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};

Self {
#[cfg(target_arch = "wasm32")]
window,
surface,
device,
queue,
config,
size,
}
}

#[cfg(target_arch = "wasm32")]
pub fn window(&self) -> Arc<Window> {
Arc::clone(&self.window)
}

pub fn size(&self) -> PhysicalSize<u32> {
self.size
}

pub fn resize(&mut self, new_size: PhysicalSize<u32>) {
let surface_width;
let surface_height;

// A Desktop Window in Xwayland also wants the physical size as texture size.
// Otherwise there will be a black border after scaling it down with a scale factor.
// TODO check scaling in a X11 session and in a Wayland Desktop Window
#[cfg(not(target_arch = "wasm32"))]
{
surface_width = new_size.width;
surface_height = new_size.height;
}

// Web platform with a scale factor 2.0 would double the canvas size and
// trigger further resize events until surface width and height are greater than
// the maximum supported texture size (e.g. 2048x2048).
// Keep the desired physical size, but scale the texture down to it's desired size.
#[cfg(target_arch = "wasm32")]
{
let scale_factor = self.window().as_ref().scale_factor();
surface_width = ((new_size.width as f64) / scale_factor) as u32;
surface_height = ((new_size.height as f64) / scale_factor) as u32;
}

if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = surface_width;
self.config.height = surface_height;
self.surface.configure(&self.device, &self.config);
}
}

pub fn input(&mut self, event: &WindowEvent) -> bool {
let _ = event;
false
}

pub fn update(&mut self) {
// todo!()
}

pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture()?;

let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());

let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});

{
let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
}

// submit will accept anything that implements IntoIter
self.queue.submit(std::iter::once(encoder.finish()));
output.present();

Ok(())
}
}
36 changes: 24 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,29 @@ use winit::event_loop::{ControlFlow, EventLoop};

#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
pub async fn run_app() {
let log_level = tracing::Level::WARN;

cfg_if! {
if #[cfg(target_arch = "wasm32")] {
console_error_panic_hook::set_once();
tracing_wasm::set_as_global_default();
// tracing for all log levels
// tracing_wasm::set_as_global_default();

let wasm_layer_config = tracing_wasm::WASMLayerConfigBuilder::new().set_max_level(log_level).build();
tracing_wasm::set_as_global_default_with_config(wasm_layer_config);
} else {
tracing_subscriber::fmt::init();
// tracing for all log levels
// tracing_subscriber::fmt::init();

use tracing_subscriber::FmtSubscriber;
let subscriber = FmtSubscriber::builder().with_max_level(log_level).finish();
tracing::subscriber::set_global_default(subscriber).expect("setting default tracing subscriber failed!");
}
}

info!("Info!");
warn!("Warning!!");
error!("Error!!!");
info!("Example Info!");
warn!("Example Warning!!");
error!("Example Error!!!");

let event_loop = EventLoop::new().unwrap();

Expand All @@ -37,12 +48,13 @@ pub async fn run_app() {
#[allow(unused_mut)]
let mut app = app::App::new("WebGPU Rendering", 500, 400);

cfg_if! {
if #[cfg(target_arch = "wasm32")] {
use winit::platform::web::EventLoopExtWebSys;
event_loop.spawn_app(app);
} else {
event_loop.run_app(&mut app).unwrap();
}
#[cfg(target_arch = "wasm32")]
{
// web import trait method EventLoopExtWebSys::spawn_app(app: App)
use winit::platform::web::EventLoopExtWebSys;
event_loop.spawn_app(app);
}

#[cfg(not(target_arch = "wasm32"))]
event_loop.run_app(&mut app).unwrap();
}

0 comments on commit 3bf10c8

Please sign in to comment.