From 533899291343067cad6b53dd9067913461099bc5 Mon Sep 17 00:00:00 2001 From: qthree Date: Tue, 8 Sep 2020 22:43:19 +0700 Subject: [PATCH] [rs] Add hello-windows example --- wgpu/examples/hello-windows/README.md | 13 ++ wgpu/examples/hello-windows/main.rs | 208 +++++++++++++++++++++ wgpu/examples/hello-windows/screenshot.png | Bin 0 -> 14471 bytes 3 files changed, 221 insertions(+) create mode 100644 wgpu/examples/hello-windows/README.md create mode 100644 wgpu/examples/hello-windows/main.rs create mode 100644 wgpu/examples/hello-windows/screenshot.png diff --git a/wgpu/examples/hello-windows/README.md b/wgpu/examples/hello-windows/README.md new file mode 100644 index 0000000000..e450713337 --- /dev/null +++ b/wgpu/examples/hello-windows/README.md @@ -0,0 +1,13 @@ +# hello-windows + +This example renders a set of 16 windows, with a differently colored background + +## To Run + +``` +cargo run --example hello-windows +``` + +## Screenshots + +![16 windows](./screenshot.png) diff --git a/wgpu/examples/hello-windows/main.rs b/wgpu/examples/hello-windows/main.rs new file mode 100644 index 0000000000..731dcdf8d5 --- /dev/null +++ b/wgpu/examples/hello-windows/main.rs @@ -0,0 +1,208 @@ +use std::collections::HashMap; +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{Window, WindowId}, +}; + +struct ViewportDesc { + window: Window, + background: wgpu::Color, + surface: wgpu::Surface, +} + +struct Viewport { + desc: ViewportDesc, + sc_desc: wgpu::SwapChainDescriptor, + swap_chain: wgpu::SwapChain, +} + +impl ViewportDesc { + fn new(window: Window, background: wgpu::Color, instance: &wgpu::Instance) -> Self { + let surface = unsafe { instance.create_surface(&window) }; + Self { + window, + background, + surface, + } + } + + fn build(self, device: &wgpu::Device, swapchain_format: wgpu::TextureFormat) -> Viewport { + let size = self.window.inner_size(); + + let sc_desc = wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Fifo, + }; + + let swap_chain = device.create_swap_chain(&self.surface, &sc_desc); + + Viewport { + desc: self, + sc_desc, + swap_chain, + } + } +} + +impl Viewport { + fn resize(&mut self, device: &wgpu::Device, size: winit::dpi::PhysicalSize) { + self.sc_desc.width = size.width; + self.sc_desc.height = size.height; + self.swap_chain = device.create_swap_chain(&self.desc.surface, &self.sc_desc); + } + fn get_current_frame(&mut self) -> wgpu::SwapChainTexture { + self.swap_chain + .get_current_frame() + .expect("Failed to acquire next swap chain texture") + .output + } +} + +async fn run( + event_loop: EventLoop<()>, + viewports: Vec<(Window, wgpu::Color)>, + swapchain_format: wgpu::TextureFormat, +) { + let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); + let viewports: Vec<_> = viewports + .into_iter() + .map(|(window, color)| ViewportDesc::new(window, color, &instance)) + .collect(); + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::Default, + // Request an adapter which can render to our surface + compatible_surface: viewports.first().map(|desc| &desc.surface), + }) + .await + .expect("Failed to find an appropiate adapter"); + + // Create the logical device and command queue + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + shader_validation: true, + }, + None, + ) + .await + .expect("Failed to create device"); + + let mut viewports: HashMap = viewports + .into_iter() + .map(|desc| (desc.window.id(), desc.build(&device, swapchain_format))) + .collect(); + + event_loop.run(move |event, _, control_flow| { + // 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 _ = (&instance, &adapter); + + *control_flow = ControlFlow::Wait; + match event { + Event::WindowEvent { + window_id, + event: WindowEvent::Resized(size), + .. + } => { + // Recreate the swap chain with the new size + if let Some(viewport) = viewports.get_mut(&window_id) { + viewport.resize(&device, size); + } + } + Event::RedrawRequested(window_id) => { + if let Some(viewport) = viewports.get_mut(&window_id) { + let frame = viewport.get_current_frame(); + let mut encoder = device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let _rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(viewport.desc.background), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + } + + queue.submit(Some(encoder.finish())); + } + } + Event::WindowEvent { + window_id, + event: WindowEvent::CloseRequested, + .. + } => { + viewports.remove(&window_id); + if viewports.is_empty() { + *control_flow = ControlFlow::Exit + } + } + _ => {} + } + }); +} + +fn main() { + #[cfg(not(target_arch = "wasm32"))] + { + const WINDOW_SIZE: u32 = 128; + const WINDOW_PADDING: u32 = 16; + const WINDOW_TITLEBAR: u32 = 32; + const WINDOW_OFFSET: u32 = WINDOW_SIZE + WINDOW_PADDING; + const ROWS: u32 = 4; + const COLUMNS: u32 = 4; + + let event_loop = EventLoop::new(); + let mut viewports = Vec::with_capacity((ROWS * COLUMNS) as usize); + for row in 0..ROWS { + for column in 0..COLUMNS { + let window = winit::window::WindowBuilder::new() + .with_title(format!("x{}y{}", column, row)) + .with_inner_size(winit::dpi::PhysicalSize::new(WINDOW_SIZE, WINDOW_SIZE)) + .build(&event_loop) + .unwrap(); + window.set_outer_position(winit::dpi::PhysicalPosition::new( + WINDOW_PADDING + column * WINDOW_OFFSET, + WINDOW_PADDING + row * (WINDOW_OFFSET + WINDOW_TITLEBAR), + )); + fn frac(index: u32, max: u32) -> f64 { + index as f64 / max as f64 + } + viewports.push(( + window, + wgpu::Color { + r: frac(row, ROWS), + g: 0.5 - frac(row * column, ROWS * COLUMNS) * 0.5, + b: frac(column, COLUMNS), + a: 1.0, + }, + )) + } + } + + subscriber::initialize_default_subscriber(None); + // Temporarily avoid srgb formats for the swapchain on the web + futures::executor::block_on(run( + event_loop, + viewports, + wgpu::TextureFormat::Bgra8UnormSrgb, + )); + } + #[cfg(target_arch = "wasm32")] + { + std::panic::set_hook(Box::new(console_error_panic_hook::hook)); + panic!("wasm32 is not supported") + } +} diff --git a/wgpu/examples/hello-windows/screenshot.png b/wgpu/examples/hello-windows/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..202a3c54c803fe578d3c37197c7e6cad06559091 GIT binary patch literal 14471 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 literal 0 HcmV?d00001