-
Notifications
You must be signed in to change notification settings - Fork 266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update the window creation section #549
Comments
Sorry, still learning a lot of Rust these days, where does the |
|
Thanks! I was able to get it working, but I couldn't make the WASM version to run now with the newest versions. As the |
Just popping in to say this works for me on Windows. I primarily am concerned with supporting Windows and Linux, so I won't be testing for WASM or MacOS, but I'll come back later to let you know how the Linux build goes. |
Much thanks for this write-up. I have made a basic project using the previous version of winit and attempting to adapt the tutorial for the current version of winit was causing me to fall into some reference chasing shenanigans. I would have likely spent many more hours than necessary trying to get it working without this post |
thank you, this is working for me on linux mint. |
The |
Hi there, I just wanted to add to the example above. Currently, all the tutorial code has the lifetime associated with the I like the proposed use of Here's the reworked example code working on the latest wgpu (0.20.1) and winit (0.30.2) without lifetimes: use std::sync::Arc;
use pollster::{block_on, FutureExt};
use wgpu::{Adapter, Device, Instance, PresentMode, Queue, Surface, SurfaceCapabilities};
use winit::application::ApplicationHandler;
use winit::dpi::PhysicalSize;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowId};
pub async fn run() {
let event_loop = EventLoop::new().unwrap();
let mut window_state = StateApplication::new();
let _ = event_loop.run_app(&mut window_state);
}
struct StateApplication {
state: Option<State>,
}
impl StateApplication {
pub fn new() -> Self {
Self { state: None }
}
}
impl ApplicationHandler for StateApplication {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window = event_loop
.create_window(Window::default_attributes().with_title("Hello!"))
.unwrap();
self.state = Some(State::new(window));
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
let window = self.state.as_ref().unwrap().window();
if window.id() == window_id {
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(physical_size) => {
self.state.as_mut().unwrap().resize(physical_size);
}
WindowEvent::RedrawRequested => {
self.state.as_mut().unwrap().render().unwrap();
}
_ => {}
}
}
}
fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
let window = self.state.as_ref().unwrap().window();
window.request_redraw();
}
}
struct State {
surface: Surface<'static>,
device: Device,
queue: Queue,
config: wgpu::SurfaceConfiguration,
size: PhysicalSize<u32>,
window: Arc<Window>,
}
impl State {
pub fn new(window: Window) -> Self {
let window_arc = Arc::new(window);
let size = window_arc.inner_size();
let instance = Self::create_gpu_instance();
let surface = instance.create_surface(window_arc.clone()).unwrap();
let adapter = Self::create_adapter(instance, &surface);
let (device, queue) = Self::create_device(&adapter);
let surface_caps = surface.get_capabilities(&adapter);
let config = Self::create_surface_config(size, surface_caps);
surface.configure(&device, &config);
Self {
surface,
device,
queue,
config,
size,
window: window_arc,
}
}
fn create_surface_config(
size: PhysicalSize<u32>,
capabilities: SurfaceCapabilities,
) -> wgpu::SurfaceConfiguration {
let surface_format = capabilities
.formats
.iter()
.find(|f| f.is_srgb())
.copied()
.unwrap_or(capabilities.formats[0]);
wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: PresentMode::AutoNoVsync,
alpha_mode: capabilities.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
}
}
fn create_device(adapter: &Adapter) -> (Device, Queue) {
adapter
.request_device(
&wgpu::DeviceDescriptor {
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
label: None,
},
None,
)
.block_on()
.unwrap()
}
fn create_adapter(instance: Instance, surface: &Surface) -> Adapter {
instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.block_on()
.unwrap()
}
fn create_gpu_instance() -> Instance {
Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..Default::default()
})
}
pub fn resize(&mut self, new_size: PhysicalSize<u32>) {
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
println!("Resized to {:?} from state!", new_size);
}
pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture().unwrap();
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: 1.0,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
});
}
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
Ok(())
}
pub fn window(&self) -> &Window {
&self.window
}
}
fn main() {
block_on(run());
} |
Are there any updates on this? I just started learning wgpu-rs through the website and I was going to open an issue regarding the outdated code. I'm glad I wasn't the only one who noticed it. |
@Quadralific this code above works for native platforms, but I need it to work on web as well given that I embed demos at the end of every article. The issue with the above code is using |
Hi there, I have created two examples which should work I think, but they need further testing. One thing I have noticed with my own testing is that wasm execution does not stop when changing the page, it only stops after refreshing the page. Also ignore the tracing parts in the code, I was experimenting with that crate as well. In the Tutorial2 example, to avoid blocking when targeting web, you have to create a custom event to use with the event loop. You also need an event loop proxy to send the custom events. Finally when creating the State struct (with surface, adapter and device), you have to create a future and run it with Tutorial1: use anyhow::Result;
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
use winit::{
application::ApplicationHandler,
event::{ElementState, KeyEvent, WindowEvent},
event_loop::{ActiveEventLoop, EventLoop},
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowId},
};
#[derive(Default)]
struct App {
window: Option<Window>,
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
tracing::info!("Resumed");
let window_attrs = Window::default_attributes();
let window = event_loop
.create_window(window_attrs)
.expect("Couldn't create window.");
#[cfg(target_arch = "wasm32")]
{
use web_sys::Element;
use winit::{dpi::PhysicalSize, platform::web::WindowExtWebSys};
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?;
let canvas = Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?;
Some(())
})
.expect("Couldn't append canvas to document body.");
// Winit prevents sizing with CSS, so we have to set
// the size manually when on web.
let _ = window.request_inner_size(PhysicalSize::new(450, 400));
}
self.window = Some(window);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
let Some(ref window) = self.window else {
return;
};
if window_id != window.id() {
return;
}
match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
physical_key: PhysicalKey::Code(KeyCode::Escape),
..
},
..
} => {
tracing::info!("Exited!");
event_loop.exit()
}
_ => {}
}
}
}
pub fn run() -> Result<()> {
let env_filter = EnvFilter::builder()
.with_default_directive(Level::INFO.into())
.from_env_lossy()
.add_directive("wgpu_core::device::resource=warn".parse()?);
let subscriber = tracing_subscriber::registry().with(env_filter);
#[cfg(target_arch = "wasm32")]
{
use tracing_wasm::{WASMLayer, WASMLayerConfig};
console_error_panic_hook::set_once();
let wasm_layer = WASMLayer::new(WASMLayerConfig::default());
subscriber.with(wasm_layer).init();
}
#[cfg(not(target_arch = "wasm32"))]
{
let fmt_layer = tracing_subscriber::fmt::Layer::default();
subscriber.with(fmt_layer).init();
}
let event_loop = EventLoop::new()?;
let mut app = App::default();
event_loop.run_app(&mut app)?;
Ok(())
} main.rs use anyhow::Result;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::wasm_bindgen;
use tutorial1_window::run;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(main))]
fn main() -> Result<()> {
run()
} Cargo.toml dependencies: [dependencies]
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
winit = "0.30"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1"
tracing-wasm = "0.2"
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Document", "Element", "Window"] } Tutorial2: use std::sync::Arc;
use anyhow::Result;
use tracing::Level;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
use winit::{
application::ApplicationHandler,
dpi::PhysicalSize,
event::{ElementState, KeyEvent, WindowEvent},
event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy},
keyboard::{KeyCode, PhysicalKey},
window::{Window, WindowId},
};
struct State {
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
size: PhysicalSize<u32>,
window: Arc<Window>,
surface_configured: bool,
}
impl State {
async fn new(window: Arc<Window>) -> State {
let size = window.inner_size();
// The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance_desc = wgpu::InstanceDescriptor {
#[cfg(target_arch = "wasm32")]
backends: if cfg!(not(target_arch = "wasm32")) {
wgpu::Backends::PRIMARY
} else {
wgpu::Backends::GL
},
..Default::default()
};
let instance = wgpu::Instance::new(instance_desc);
let surface = instance.create_surface(window.clone()).unwrap();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();
let device_desc = wgpu::DeviceDescriptor {
label: None,
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.
required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults()
} else {
wgpu::Limits::default()
},
memory_hints: wgpu::MemoryHints::default(),
};
let (device, queue) = adapter.request_device(&device_desc, None).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 all the colors comming 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(wgpu::TextureFormat::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: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
desired_maximum_frame_latency: 2,
view_formats: vec![],
};
let surface_configured;
#[cfg(not(target_arch = "wasm32"))]
{
surface.configure(&device, &config);
surface_configured = true;
}
#[cfg(target_arch = "wasm32")]
{
surface_configured = false;
}
Self {
surface,
device,
queue,
config,
size,
window,
surface_configured,
}
}
pub fn resize(&mut self, new_size: PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
}
}
fn input(&mut self, _: &WindowEvent) -> bool {
false
}
fn update(&mut self) {}
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 clear_color = wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
};
let color_attachment = wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(clear_color),
store: wgpu::StoreOp::Store,
},
};
let render_pass_desc = wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(color_attachment)],
depth_stencil_attachment: None,
occlusion_query_set: None,
timestamp_writes: None,
};
let _render_pass = encoder.begin_render_pass(&render_pass_desc);
}
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
Ok(())
}
}
enum UserEvent {
StateReady(State),
}
struct App {
state: Option<State>,
event_loop_proxy: EventLoopProxy<UserEvent>,
}
impl App {
fn new(event_loop: &EventLoop<UserEvent>) -> Self {
Self {
state: None,
event_loop_proxy: event_loop.create_proxy(),
}
}
}
impl ApplicationHandler<UserEvent> for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
tracing::info!("Resumed");
let window_attrs = Window::default_attributes();
let window = event_loop
.create_window(window_attrs)
.expect("Couldn't create window.");
#[cfg(target_arch = "wasm32")]
{
use web_sys::Element;
use winit::{dpi::PhysicalSize, platform::web::WindowExtWebSys};
web_sys::window()
.and_then(|win| win.document())
.and_then(|doc| {
let dst = doc.get_element_by_id("wasm-example")?;
let canvas = Element::from(window.canvas()?);
dst.append_child(&canvas).ok()?;
Some(())
})
.expect("Couldn't append canvas to document body.");
// Winit prevents sizing with CSS, so we have to set
// the size manually when on web.
let _ = window.request_inner_size(PhysicalSize::new(450, 400));
let state_future = State::new(Arc::new(window));
let event_loop_proxy = self.event_loop_proxy.clone();
let future = async move {
let state = state_future.await;
assert!(event_loop_proxy
.send_event(UserEvent::StateReady(state))
.is_ok());
};
wasm_bindgen_futures::spawn_local(future)
}
#[cfg(not(target_arch = "wasm32"))]
{
let state = pollster::block_on(State::new(Arc::new(window)));
assert!(self
.event_loop_proxy
.send_event(UserEvent::StateReady(state))
.is_ok());
}
}
fn user_event(&mut self, _: &ActiveEventLoop, event: UserEvent) {
let UserEvent::StateReady(state) = event;
self.state = Some(state);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: WindowEvent,
) {
let Some(ref mut state) = self.state else {
return;
};
if window_id != state.window.id() {
return;
}
if state.input(&event) {
return;
}
match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
event:
KeyEvent {
state: ElementState::Pressed,
physical_key: PhysicalKey::Code(KeyCode::Escape),
..
},
..
} => {
tracing::info!("Exited!");
event_loop.exit()
}
WindowEvent::Resized(physical_size) => {
tracing::info!("physical_size: {physical_size:?}");
state.surface_configured = true;
state.resize(physical_size);
}
WindowEvent::RedrawRequested => {
if !state.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) => {
tracing::error!("OutOfMemory");
event_loop.exit();
}
// This happens when the frame takes too long to present
Err(wgpu::SurfaceError::Timeout) => {
tracing::warn!("Surface timeout");
}
}
}
_ => {}
}
}
fn about_to_wait(&mut self, _: &ActiveEventLoop) {
if let Some(ref state) = self.state {
state.window.request_redraw();
};
}
}
pub fn run() -> Result<()> {
let env_filter = EnvFilter::builder()
.with_default_directive(Level::INFO.into())
.from_env_lossy()
.add_directive("wgpu_core::device::resource=warn".parse()?);
let subscriber = tracing_subscriber::registry().with(env_filter);
#[cfg(target_arch = "wasm32")]
{
use tracing_wasm::{WASMLayer, WASMLayerConfig};
console_error_panic_hook::set_once();
let wasm_layer = WASMLayer::new(WASMLayerConfig::default());
subscriber.with(wasm_layer).init();
}
#[cfg(not(target_arch = "wasm32"))]
{
let fmt_layer = tracing_subscriber::fmt::Layer::default();
subscriber.with(fmt_layer).init();
}
let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
let mut app = App::new(&event_loop);
event_loop.run_app(&mut app)?;
Ok(())
} main.rs is the same as the previous one. Cargo.toml dependencies: [dependencies]
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
wgpu = "22.0"
winit = "0.30"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
pollster = "0.3"
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1"
tracing-wasm = "0.2"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["Document", "Element", "Window"] }
wgpu = { version = "22.0", features = ["webgl"] } |
I've encountered a weird problem with the code used in this reply. When changing the surface in this example from the first member to the third member, the entire program will segfault when exiting. Screen-Recording-2024-07-25-at-3.mp4I appreciate it a lot if anyone can explain this to me!!!
|
Any progress? |
Rust drops struct fields in the order they are defined, meaning fields declared last are dropped first. Edit: impl Drop for State {
fn drop(&mut self) {
std::mem::drop(self.device);
...
}
} NOTE: i didnt test it, but i hope this helps |
Since the Remove of
WindowBuilder
in Winit 0.30.0, the complete section https://sotrh.github.io/learn-wgpu/beginner/tutorial1-window/#the-code is no longer working. Instead the complete Initialization process needs to be updated.e.g. this minimal implementation works fine for me, but i don't know if i'am missing important parts:
The text was updated successfully, but these errors were encountered: