diff --git a/Cargo.lock b/Cargo.lock index f495167..72cea9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,6 +233,20 @@ name = "bytemuck" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.75", +] [[package]] name = "bytes" @@ -544,6 +558,7 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" name = "forestiles" version = "0.1.0" dependencies = [ + "bytemuck", "cfg-if", "console_error_panic_hook", "console_log", diff --git a/Cargo.toml b/Cargo.toml index 03821ba..8f306e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,16 +3,14 @@ name = "forestiles" version = "0.1.0" edition = "2021" -[lib] -crate-type = ["cdylib", "rlib"] - [dependencies] winit = { version = "0.29", features = ["rwh_05"] } env_logger = "0.11" log = "0.4" -wgpu = "22.0" +wgpu = "22.1" cfg-if = "1" pollster = "0.3" +bytemuck = { version = "1.17", features = [ "derive" ] } [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.6" diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index a45c1fc..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,221 +0,0 @@ -use cfg_if::cfg_if; -use winit::{ - event::*, - event_loop::EventLoop, - keyboard::{KeyCode, PhysicalKey}, - window::{WindowBuilder, Window}, -}; -#[cfg(target_arch="wasm32")] -use wasm_bindgen::prelude::*; - -struct State<'a> { - surface: wgpu::Surface<'a>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: winit::dpi::PhysicalSize, - // The window must be declared after the surface so - // it gets dropped after it as the surface contains - // unsafe references to the window's resources. - window: &'a Window, -} - -impl<'a> State<'a> { - // Creating some of the wgpu types requires async code - async fn new(window: &'a Window) -> State<'a> { - 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 { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - dbg!(1); - let surface = instance.create_surface(window).unwrap(); - dbg!(2); - - let adapter = instance.request_adapter( - &wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - }, - ).await.unwrap(); - - 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. - required_limits: if cfg!(target_arch = "wasm32") { - wgpu::Limits::downlevel_webgl2_defaults() - } else { - wgpu::Limits::default() - }, - label: None, - memory_hints: wgpu::MemoryHints::default() - }, - 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() - .find(|f| f.is_srgb()) - .copied() - .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], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - - Self { - window, - surface, - device, - queue, - config, - size, - } - } - - pub fn window(&self) -> &Window { - &self.window - } - - fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { - 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, event: &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"), - }); - - 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, - }); - - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); - - Ok(()) - } -} - -#[cfg_attr(target_arch="wasm32", wasm_bindgen(start))] -pub async fn run() { - cfg_if! { - if #[cfg(target_arch = "wasm32")] { - std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - console_log::init().expect("Couldn't initialize logger"); - } else { - env_logger::init(); - } - } - - let event_loop = EventLoop::new().unwrap(); - let window = WindowBuilder::new().build(&event_loop).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(450, 400)); - - use winit::platform::web::WindowExtWebSys; - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| { - let dst = doc.body()?; - let canvas = web_sys::Element::from(window.canvas()?); - dst.append_child(&canvas).ok()?; - Some(()) - }) - .expect("Couldn't append canvas to document body."); - } - - let mut state = State::new(&window).await; - - event_loop.run(move |event, control_flow| match event { - Event::WindowEvent { - ref event, - window_id, - } if window_id == state.window.id() => if !state.input(event) { match event { - WindowEvent::CloseRequested - | WindowEvent::KeyboardInput { - event: - KeyEvent { - state: ElementState::Pressed, - physical_key: PhysicalKey::Code(KeyCode::Escape), - .. - }, - .. - } => control_flow.exit(), - WindowEvent::Resized(physical_size) => { - state.resize(*physical_size); - }, - WindowEvent::RedrawRequested => { - state.update(); - match state.render() { - Ok(_) => {} - // Reconfigure the surface if lost - Err(wgpu::SurfaceError::Lost) => state.resize(state.size), - // The system is out of memory, we should probably quit - Err(wgpu::SurfaceError::OutOfMemory) => control_flow.exit(), - // All other errors (Outdated, Timeout) should be resolved by the next frame - Err(e) => eprintln!("{:?}", e), - } - } - _ => {} - }}, - Event::AboutToWait => { - // RedrawRequested will only trigger once unless we manually - // request it. - state.window().request_redraw(); - } - _ => {} - }).unwrap(); -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f8cac61..decb618 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,194 @@ -use forestiles::run; +use wgpu::{include_wgsl, Device, Queue, RenderPipeline, Surface, SurfaceConfiguration}; +use winit::{ + event::{Event, WindowEvent}, event_loop::EventLoop, platform::web::WindowExtWebSys, window::Window +}; -fn main() { - pollster::block_on(run()); +struct App<'a> { + window: &'a Window, + surface_config: SurfaceConfiguration, + surface: Surface<'a>, + device: Device, + render_pipeline: RenderPipeline, + queue: Queue +} +impl<'a> App<'a> { + async fn init(window: &'a Window) -> Self { + let mut size = window.inner_size(); + size.width = size.width.max(1); + size.height = size.height.max(1); + + let instance = wgpu::Instance::default(); + + let surface = instance.create_surface(window).unwrap(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + force_fallback_adapter: false, + // Request an adapter which can render to our surface + compatible_surface: Some(&surface), + }) + .await + .expect("Failed to find an appropriate adapter"); + + // Create the logical device and command queue + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. + required_limits: wgpu::Limits::downlevel_webgl2_defaults() + .using_resolution(adapter.limits()), + memory_hints: wgpu::MemoryHints::MemoryUsage, + }, + None, + ) + .await + .expect("Failed to create device"); + + // Load the shaders from disk + let shader = device.create_shader_module(include_wgsl!("shader.wgsl")); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let swapchain_capabilities = surface.get_capabilities(&adapter); + let swapchain_format = swapchain_capabilities.formats[0]; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + compilation_options: Default::default(), + targets: &[Some(swapchain_format.into())], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let surface_config = surface + .get_default_config(&adapter, size.width, size.height) + .unwrap(); + surface.configure(&device, &surface_config); + + Self { + window, + surface_config, + surface, + device, + render_pipeline, + queue + } + } + fn run(&mut self, event_loop: EventLoop<()>) { + event_loop.run(move |event, target| { + // Have the closure take ownership of the resources. + // `event_loop.run` never returns, therefore we must do this to ensure + // the resources are properly cleaned up. + let _ = &self; + + if let Event::WindowEvent { + window_id: _, + event, + } = event + { + match event { + WindowEvent::Resized(new_size) => { + // Reconfigure the surface with the new size + self.surface_config.width = new_size.width.max(1); + self.surface_config.height = new_size.height.max(1); + self.surface.configure(&self.device, &self.surface_config); + // On macos the window needs to be redrawn manually after resizing + self.window.request_redraw(); + } + WindowEvent::RedrawRequested => { + let frame = self.surface + .get_current_texture() + .expect("Failed to acquire next swap chain texture"); + let view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = + self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: None, + }); + { + let mut rpass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_pipeline(&self.render_pipeline); + rpass.draw(0..3, 0..1); + } + + self.queue.submit(Some(encoder.finish())); + frame.present(); + } + WindowEvent::CloseRequested => target.exit(), + _ => {} + }; + } + }) + .unwrap(); + } +} + +pub fn main() { + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + console_log::init().expect("could not initialize logger"); + } + #[cfg(not(target_arch = "wasm32"))] + { + env_logger::init(); + } + + let event_loop = EventLoop::new().unwrap(); + let window = winit::window::WindowBuilder::new().build(&event_loop).unwrap(); + + #[cfg(not(target_arch = "wasm32"))] + { + let mut app = pollster::block_on(App::init(&window)); + app.run(event_loop); + } + #[cfg(target_arch = "wasm32")] + { + web_sys::window() + .unwrap() + .document() + .unwrap() + .body() + .unwrap() + .append_child(&window.canvas().unwrap()) + .unwrap(); + wasm_bindgen_futures::spawn_local(async move { + App::init(&window).await.run(event_loop); + }); + } } \ No newline at end of file diff --git a/src/shader.wgsl b/src/shader.wgsl index baf7775..859ffa4 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,16 +1,11 @@ -// Vertex shader - -struct VertexOutput { - @builtin(position) clip_position: vec4, -}; - @vertex -fn vs_main( - @builtin(vertex_index) in_vertex_index: u32, -) -> VertexOutput { - var out: VertexOutput; - let x = f32(1 - i32(in_vertex_index)) * 0.5; - let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; - out.clip_position = vec4(x, y, 0.0, 1.0); - return out; +fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { + let x = f32(i32(in_vertex_index) - 1); + let y = f32(i32(in_vertex_index & 1u) * 2 - 1); + return vec4(x, y, 0.0, 1.0); +} + +@fragment +fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); } \ No newline at end of file