diff --git a/Cargo.toml b/Cargo.toml index 39c5957a02..11cca8b38a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ members = [ "examples/custom_widget", "examples/events", "examples/geometry", + "examples/integration", "examples/pokedex", "examples/progress_bar", "examples/stopwatch", diff --git a/examples/README.md b/examples/README.md index 8f2eba3ea6..a9ab546f40 100644 --- a/examples/README.md +++ b/examples/README.md @@ -74,6 +74,7 @@ A bunch of simpler examples exist: - [`custom_widget`](custom_widget), a demonstration of how to build a custom widget that draws a circle. - [`events`](events), a log of native events displayed using a conditional `Subscription`. - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). +- [`integration`](integration), a demonstration of how to integrate Iced in an existing graphical application. - [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI]. - [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider. - [`stopwatch`](stopwatch), a watch with start/stop and reset buttons showcasing how to listen to time. diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml new file mode 100644 index 0000000000..afc2c791dc --- /dev/null +++ b/examples/integration/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "integration" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced_winit = { path = "../../winit" } +iced_wgpu = { path = "../../wgpu" } +env_logger = "0.7" diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs new file mode 100644 index 0000000000..0457a058ce --- /dev/null +++ b/examples/integration/src/controls.rs @@ -0,0 +1,102 @@ +use crate::Scene; + +use iced_wgpu::Renderer; +use iced_winit::{ + slider, Align, Color, Column, Element, Length, Row, Slider, Text, +}; + +pub struct Controls { + sliders: [slider::State; 3], +} + +#[derive(Debug)] +pub enum Message { + BackgroundColorChanged(Color), +} + +impl Controls { + pub fn new() -> Controls { + Controls { + sliders: Default::default(), + } + } + + pub fn update(&self, message: Message, scene: &mut Scene) { + match message { + Message::BackgroundColorChanged(color) => { + scene.background_color = color; + } + } + } + + pub fn view<'a>( + &'a mut self, + scene: &Scene, + ) -> Element<'a, Message, Renderer> { + let [r, g, b] = &mut self.sliders; + let background_color = scene.background_color; + + let sliders = Row::new() + .width(Length::Units(500)) + .spacing(20) + .push(Slider::new( + r, + 0.0..=1.0, + scene.background_color.r, + move |r| { + Message::BackgroundColorChanged(Color { + r, + ..background_color + }) + }, + )) + .push(Slider::new( + g, + 0.0..=1.0, + scene.background_color.g, + move |g| { + Message::BackgroundColorChanged(Color { + g, + ..background_color + }) + }, + )) + .push(Slider::new( + b, + 0.0..=1.0, + scene.background_color.b, + move |b| { + Message::BackgroundColorChanged(Color { + b, + ..background_color + }) + }, + )); + + Row::new() + .width(Length::Fill) + .height(Length::Fill) + .align_items(Align::End) + .push( + Column::new() + .width(Length::Fill) + .align_items(Align::End) + .push( + Column::new() + .padding(10) + .spacing(10) + .push( + Text::new("Background color") + .color(Color::WHITE), + ) + .push(sliders) + .push( + Text::new(format!("{:?}", background_color)) + .size(14) + .color(Color::WHITE), + ), + ), + ) + .into() + } +} diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs new file mode 100644 index 0000000000..ed36f73624 --- /dev/null +++ b/examples/integration/src/main.rs @@ -0,0 +1,204 @@ +mod controls; +mod scene; + +use controls::Controls; +use scene::Scene; + +use iced_wgpu::{ + wgpu, window::SwapChain, Primitive, Renderer, Settings, Target, +}; +use iced_winit::{winit, Cache, Clipboard, MouseCursor, Size, UserInterface}; + +use winit::{ + event::{DeviceEvent, Event, ModifiersState, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, +}; + +pub fn main() { + env_logger::init(); + + // Initialize winit + let event_loop = EventLoop::new(); + let window = winit::window::Window::new(&event_loop).unwrap(); + let mut logical_size = + window.inner_size().to_logical(window.scale_factor()); + let mut modifiers = ModifiersState::default(); + + // Initialize WGPU + let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::Default, + backends: wgpu::BackendBit::PRIMARY, + }) + .expect("Request adapter"); + + let (mut device, mut queue) = + adapter.request_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + limits: wgpu::Limits::default(), + }); + + let surface = wgpu::Surface::create(&window); + + let mut swap_chain = { + let size = window.inner_size(); + + SwapChain::new(&device, &surface, size.width, size.height) + }; + let mut resized = false; + + // Initialize iced + let mut events = Vec::new(); + let mut cache = Some(Cache::default()); + let mut renderer = Renderer::new(&mut device, Settings::default()); + let mut output = (Primitive::None, MouseCursor::OutOfBounds); + let clipboard = Clipboard::new(&window); + + // Initialize scene and GUI controls + let mut scene = Scene::new(&device); + let mut controls = Controls::new(); + + // Run event loop + event_loop.run(move |event, _, control_flow| { + // You should change this if you want to render continuosly + *control_flow = ControlFlow::Wait; + + match event { + Event::DeviceEvent { + event: DeviceEvent::ModifiersChanged(new_modifiers), + .. + } => { + modifiers = new_modifiers; + } + Event::WindowEvent { event, .. } => { + match event { + WindowEvent::Resized(new_size) => { + logical_size = + new_size.to_logical(window.scale_factor()); + resized = true; + } + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + } + _ => {} + } + + // Map window event to iced event + if let Some(event) = iced_winit::conversion::window_event( + event, + window.scale_factor(), + modifiers, + ) { + events.push(event); + } + } + Event::MainEventsCleared => { + // If no relevant events happened, we can simply skip this + if events.is_empty() { + return; + } + + // We need to: + // 1. Process events of our user interface. + // 2. Update state as a result of any interaction. + // 3. Generate a new output for our renderer. + + // First, we build our user interface. + let mut user_interface = UserInterface::build( + controls.view(&scene), + Size::new(logical_size.width, logical_size.height), + cache.take().unwrap(), + &mut renderer, + ); + + // Then, we process the events, obtaining messages in return. + let messages = user_interface.update( + events.drain(..), + clipboard.as_ref().map(|c| c as _), + &renderer, + ); + + let user_interface = if messages.is_empty() { + // If there are no messages, no interactions we care about have + // happened. We can simply leave our user interface as it is. + user_interface + } else { + // If there are messages, we need to update our state + // accordingly and rebuild our user interface. + // We can only do this if we drop our user interface first + // by turning it into its cache. + cache = Some(user_interface.into_cache()); + + // In this example, `Controls` is the only part that cares + // about messages, so updating our state is pretty + // straightforward. + for message in messages { + controls.update(message, &mut scene); + } + + // Once the state has been changed, we rebuild our updated + // user interface. + UserInterface::build( + controls.view(&scene), + Size::new(logical_size.width, logical_size.height), + cache.take().unwrap(), + &mut renderer, + ) + }; + + // Finally, we just need to draw a new output for our renderer, + output = user_interface.draw(&mut renderer); + + // update our cache, + cache = Some(user_interface.into_cache()); + + // and request a redraw + window.request_redraw(); + } + Event::RedrawRequested(_) => { + if resized { + let size = window.inner_size(); + + swap_chain = SwapChain::new( + &device, + &surface, + size.width, + size.height, + ); + } + + let (frame, viewport) = swap_chain.next_frame(); + + let mut encoder = device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { todo: 0 }, + ); + + // We draw the scene first + scene.draw(&mut encoder, &frame.view); + + // And then iced on top + let mouse_cursor = renderer.draw( + &mut device, + &mut encoder, + Target { + texture: &frame.view, + viewport, + }, + &output, + window.scale_factor(), + &["Some debug information!"], + ); + + // Then we submit the work + queue.submit(&[encoder.finish()]); + + // And update the mouse cursor + window.set_cursor_icon(iced_winit::conversion::mouse_cursor( + mouse_cursor, + )); + } + _ => {} + } + }) +} diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs new file mode 100644 index 0000000000..efb1921b11 --- /dev/null +++ b/examples/integration/src/scene.rs @@ -0,0 +1,119 @@ +use iced_wgpu::wgpu; +use iced_winit::Color; + +pub struct Scene { + pub background_color: Color, + pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, +} + +impl Scene { + pub fn new(device: &wgpu::Device) -> Scene { + let (pipeline, bind_group) = build_pipeline(device); + + Scene { + background_color: Color::BLACK, + pipeline, + bind_group, + } + } + + pub fn draw( + &self, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + ) { + let mut rpass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: { + let [r, g, b, a] = + self.background_color.into_linear(); + + wgpu::Color { + r: r as f64, + g: g as f64, + b: b as f64, + a: a as f64, + } + }, + }, + ], + depth_stencil_attachment: None, + }); + + rpass.set_pipeline(&self.pipeline); + rpass.set_bind_group(0, &self.bind_group, &[]); + rpass.draw(0..3, 0..1); + } +} + +fn build_pipeline( + device: &wgpu::Device, +) -> (wgpu::RenderPipeline, wgpu::BindGroup) { + let vs = include_bytes!("shader/vert.spv"); + let fs = include_bytes!("shader/frag.spv"); + + let vs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&vs[..])).unwrap(), + ); + + let fs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&fs[..])).unwrap(), + ); + + let bind_group_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[], + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + bindings: &[], + }); + + let pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout], + }); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &pipeline_layout, + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Ccw, + cull_mode: wgpu::CullMode::None, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + color_blend: wgpu::BlendDescriptor::REPLACE, + alpha_blend: wgpu::BlendDescriptor::REPLACE, + write_mask: wgpu::ColorWrite::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + (pipeline, bind_group) +} diff --git a/examples/integration/src/shader/frag.spv b/examples/integration/src/shader/frag.spv new file mode 100644 index 0000000000..9d6807c914 Binary files /dev/null and b/examples/integration/src/shader/frag.spv differ diff --git a/examples/integration/src/shader/vert.spv b/examples/integration/src/shader/vert.spv new file mode 100644 index 0000000000..0cabc9c038 Binary files /dev/null and b/examples/integration/src/shader/vert.spv differ diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 53b36240be..08914bed37 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -162,7 +162,7 @@ where /// ); /// /// // Update the user interface - /// let messages = user_interface.update(&renderer, None, events.drain(..)); + /// let messages = user_interface.update(events.drain(..), None, &renderer); /// /// cache = user_interface.into_cache(); /// @@ -174,9 +174,9 @@ where /// ``` pub fn update( &mut self, - renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, events: impl IntoIterator, + clipboard: Option<&dyn Clipboard>, + renderer: &Renderer, ) -> Vec { let mut messages = Vec::new(); @@ -246,7 +246,7 @@ where /// &mut renderer, /// ); /// - /// let messages = user_interface.update(&renderer, None, events.drain(..)); + /// let messages = user_interface.update(events.drain(..), None, &renderer); /// /// // Draw the user interface /// let mouse_cursor = user_interface.draw(&mut renderer); diff --git a/native/src/window.rs b/native/src/window.rs index db9226dc06..4dcae62f42 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -1,6 +1,6 @@ //! Build window-based GUI applications. +mod backend; mod event; -mod renderer; +pub use backend::Backend; pub use event::Event; -pub use renderer::{Renderer, Target}; diff --git a/native/src/window/backend.rs b/native/src/window/backend.rs new file mode 100644 index 0000000000..3bc691cdef --- /dev/null +++ b/native/src/window/backend.rs @@ -0,0 +1,55 @@ +use crate::MouseCursor; + +use raw_window_handle::HasRawWindowHandle; + +/// A graphics backend that can render to windows. +pub trait Backend: Sized { + /// The settings of the backend. + type Settings: Default; + + /// The iced renderer of the backend. + type Renderer: crate::Renderer; + + /// The surface of the backend. + type Surface; + + /// The swap chain of the backend. + type SwapChain; + + /// Creates a new [`Backend`] and an associated iced renderer. + /// + /// [`Backend`]: trait.Backend.html + fn new(settings: Self::Settings) -> (Self, Self::Renderer); + + /// Crates a new [`Surface`] for the given window. + /// + /// [`Surface`]: #associatedtype.Surface + fn create_surface( + &mut self, + window: &W, + ) -> Self::Surface; + + /// Crates a new [`SwapChain`] for the given [`Surface`]. + /// + /// [`SwapChain`]: #associatedtype.SwapChain + /// [`Surface`]: #associatedtype.Surface + fn create_swap_chain( + &mut self, + surface: &Self::Surface, + width: u32, + height: u32, + ) -> Self::SwapChain; + + /// Draws the output primitives to the next frame of the given [`SwapChain`]. + /// + /// [`SwapChain`]: #associatedtype.SwapChain + /// [`Surface`]: #associatedtype.Surface + fn draw>( + &mut self, + renderer: &mut Self::Renderer, + swap_chain: &mut Self::SwapChain, + output: &::Output, + scale_factor: f64, + overlay: &[T], + ) -> MouseCursor; +} diff --git a/native/src/window/renderer.rs b/native/src/window/renderer.rs deleted file mode 100644 index a3cbb8ceac..0000000000 --- a/native/src/window/renderer.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::MouseCursor; - -use raw_window_handle::HasRawWindowHandle; - -/// A renderer that can target windows. -pub trait Renderer: crate::Renderer + Sized { - /// The settings of the renderer. - type Settings: Default; - - /// The type of target. - type Target: Target; - - /// Creates a new window [`Renderer`]. - /// - /// [`Renderer`]: trait.Renderer.html - fn new(settings: Self::Settings) -> Self; - - /// Performs the drawing operations described in the output on the given - /// target. - /// - /// The overlay can be a bunch of debug text logs. It should be rendered on - /// top of the GUI on most scenarios. - fn draw>( - &mut self, - output: &Self::Output, - overlay: &[T], - target: &mut Self::Target, - ) -> MouseCursor; -} - -/// A rendering target. -pub trait Target { - /// The renderer of this target. - type Renderer; - - /// Creates a new rendering [`Target`] from the given window handle, width, - /// height and dpi factor. - /// - /// [`Target`]: trait.Target.html - fn new( - window: &W, - width: u32, - height: u32, - scale_factor: f64, - renderer: &Self::Renderer, - ) -> Self; - - /// Resizes the current [`Target`]. - /// - /// [`Target`]: trait.Target.html - fn resize( - &mut self, - width: u32, - height: u32, - scale_factor: f64, - renderer: &Self::Renderer, - ); -} diff --git a/src/application.rs b/src/application.rs index 926a2986b9..0a4b6d9e47 100644 --- a/src/application.rs +++ b/src/application.rs @@ -193,7 +193,7 @@ impl iced_winit::Application for Instance where A: Application, { - type Renderer = iced_wgpu::Renderer; + type Backend = iced_wgpu::window::Backend; type Executor = A::Executor; type Message = A::Message; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index b8cd3fce91..e4834818e7 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -27,19 +27,27 @@ pub mod defaults; pub mod triangle; pub mod widget; +pub mod window; mod image; mod primitive; mod quad; mod renderer; mod settings; +mod target; mod text; mod transformation; +mod viewport; + +pub use wgpu; pub use defaults::Defaults; pub use primitive::Primitive; -pub use renderer::{Renderer, Target}; +pub use renderer::Renderer; pub use settings::Settings; +pub use target::Target; +pub use viewport::Viewport; + #[doc(no_inline)] pub use widget::*; diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 93d2bb1375..e93090b802 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,29 +1,20 @@ use crate::{ image, quad, text, triangle, Defaults, Image, Primitive, Quad, Settings, - Transformation, + Target, Transformation, }; use iced_native::{ - layout, window, Background, Color, Layout, MouseCursor, Point, Rectangle, - Vector, Widget, + layout, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, + Widget, }; use std::sync::Arc; -use wgpu::{ - Adapter, BackendBit, CommandEncoderDescriptor, Device, DeviceDescriptor, - Extensions, Limits, PowerPreference, Queue, RequestAdapterOptions, -}; -mod target; mod widget; -pub use target::Target; - /// A [`wgpu`] renderer. /// /// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs #[derive(Debug)] pub struct Renderer { - device: Device, - queue: Queue, quad_pipeline: quad::Pipeline, image_pipeline: image::Pipeline, text_pipeline: text::Pipeline, @@ -53,29 +44,16 @@ impl<'a> Layer<'a> { } impl Renderer { - fn new(settings: Settings) -> Self { - let adapter = Adapter::request(&RequestAdapterOptions { - power_preference: PowerPreference::Default, - backends: BackendBit::all(), - }) - .expect("Request adapter"); - - let (mut device, queue) = adapter.request_device(&DeviceDescriptor { - extensions: Extensions { - anisotropic_filtering: false, - }, - limits: Limits { max_bind_groups: 2 }, - }); - - let text_pipeline = - text::Pipeline::new(&mut device, settings.default_font); - let quad_pipeline = quad::Pipeline::new(&mut device); - let image_pipeline = crate::image::Pipeline::new(&mut device); - let triangle_pipeline = triangle::Pipeline::new(&mut device); + /// Creates a new [`Renderer`]. + /// + /// [`Renderer`]: struct.Renderer.html + pub fn new(device: &mut wgpu::Device, settings: Settings) -> Self { + let text_pipeline = text::Pipeline::new(device, settings.default_font); + let quad_pipeline = quad::Pipeline::new(device); + let image_pipeline = crate::image::Pipeline::new(device); + let triangle_pipeline = triangle::Pipeline::new(device); Self { - device, - queue, quad_pipeline, image_pipeline, text_pipeline, @@ -83,38 +61,26 @@ impl Renderer { } } - fn draw>( + /// Draws the provided primitives in the given [`Target`]. + /// + /// The text provided as overlay will be renderer on top of the primitives. + /// This is useful for rendering debug information. + /// + /// [`Target`]: struct.Target.html + pub fn draw>( &mut self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + target: Target<'_>, (primitive, mouse_cursor): &(Primitive, MouseCursor), + scale_factor: f64, overlay: &[T], - target: &mut Target, ) -> MouseCursor { log::debug!("Drawing"); - let (width, height) = target.dimensions(); - let scale_factor = target.scale_factor(); - let transformation = target.transformation(); - let frame = target.next_frame(); - - let mut encoder = self - .device - .create_command_encoder(&CommandEncoderDescriptor { todo: 0 }); - - let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { - attachment: &frame.view, - resolve_target: None, - load_op: wgpu::LoadOp::Clear, - store_op: wgpu::StoreOp::Store, - clear_color: wgpu::Color { - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - }, - }], - depth_stencil_attachment: None, - }); + let (width, height) = target.viewport.dimensions(); + let scale_factor = scale_factor as f32; + let transformation = target.viewport.transformation(); let mut layers = Vec::new(); @@ -133,15 +99,15 @@ impl Renderer { for layer in layers { self.flush( + device, scale_factor, transformation, &layer, - &mut encoder, - &frame.view, + encoder, + target.texture, ); } - self.queue.submit(&[encoder.finish()]); self.image_pipeline.trim_cache(); *mouse_cursor @@ -336,6 +302,7 @@ impl Renderer { fn flush( &mut self, + device: &mut wgpu::Device, scale_factor: f32, transformation: Transformation, layer: &Layer<'_>, @@ -352,7 +319,7 @@ impl Renderer { ); self.triangle_pipeline.draw( - &mut self.device, + device, encoder, target, translated, @@ -364,7 +331,7 @@ impl Renderer { if layer.quads.len() > 0 { self.quad_pipeline.draw( - &mut self.device, + device, encoder, &layer.quads, transformation, @@ -383,7 +350,7 @@ impl Renderer { ); self.image_pipeline.draw( - &mut self.device, + device, encoder, &layer.images, translated_and_scaled, @@ -429,7 +396,7 @@ impl Renderer { } self.text_pipeline.draw_queued( - &mut self.device, + device, encoder, target, transformation, @@ -461,24 +428,6 @@ impl iced_native::Renderer for Renderer { } } -impl window::Renderer for Renderer { - type Settings = Settings; - type Target = Target; - - fn new(settings: Settings) -> Self { - Self::new(settings) - } - - fn draw>( - &mut self, - output: &Self::Output, - overlay: &[T], - target: &mut Target, - ) -> MouseCursor { - self.draw(output, overlay, target) - } -} - impl layout::Debugger for Renderer { fn explain( &mut self, diff --git a/wgpu/src/renderer/target.rs b/wgpu/src/renderer/target.rs deleted file mode 100644 index 2097497612..0000000000 --- a/wgpu/src/renderer/target.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::{Renderer, Transformation}; -use iced_native::window; - -use raw_window_handle::HasRawWindowHandle; - -/// A rendering target. -#[derive(Debug)] -pub struct Target { - surface: wgpu::Surface, - width: u32, - height: u32, - scale_factor: f32, - transformation: Transformation, - swap_chain: wgpu::SwapChain, -} - -impl Target { - pub(crate) fn dimensions(&self) -> (u32, u32) { - (self.width, self.height) - } - - pub(crate) fn scale_factor(&self) -> f32 { - self.scale_factor - } - - pub(crate) fn transformation(&self) -> Transformation { - self.transformation - } - - pub(crate) fn next_frame(&mut self) -> wgpu::SwapChainOutput<'_> { - self.swap_chain.get_next_texture() - } -} - -impl window::Target for Target { - type Renderer = Renderer; - - fn new( - window: &W, - width: u32, - height: u32, - scale_factor: f64, - renderer: &Renderer, - ) -> Target { - let surface = wgpu::Surface::create(window); - let swap_chain = - new_swap_chain(&surface, width, height, &renderer.device); - - Target { - surface, - width, - height, - scale_factor: scale_factor as f32, - transformation: Transformation::orthographic(width, height), - swap_chain, - } - } - - fn resize( - &mut self, - width: u32, - height: u32, - scale_factor: f64, - renderer: &Renderer, - ) { - self.width = width; - self.height = height; - self.scale_factor = scale_factor as f32; - self.transformation = Transformation::orthographic(width, height); - self.swap_chain = - new_swap_chain(&self.surface, width, height, &renderer.device); - } -} - -fn new_swap_chain( - surface: &wgpu::Surface, - width: u32, - height: u32, - device: &wgpu::Device, -) -> wgpu::SwapChain { - device.create_swap_chain( - &surface, - &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - width, - height, - present_mode: wgpu::PresentMode::Vsync, - }, - ) -} diff --git a/wgpu/src/target.rs b/wgpu/src/target.rs new file mode 100644 index 0000000000..1e72c0c3fa --- /dev/null +++ b/wgpu/src/target.rs @@ -0,0 +1,14 @@ +use crate::Viewport; + +/// A rendering target. +#[derive(Debug)] +pub struct Target<'a> { + /// The texture where graphics will be rendered. + pub texture: &'a wgpu::TextureView, + + /// The viewport of the target. + /// + /// Most of the time, you will want this to match the dimensions of the + /// texture. + pub viewport: &'a Viewport, +} diff --git a/wgpu/src/viewport.rs b/wgpu/src/viewport.rs new file mode 100644 index 0000000000..66242468ff --- /dev/null +++ b/wgpu/src/viewport.rs @@ -0,0 +1,29 @@ +use crate::Transformation; + +/// A viewing region for displaying computer graphics. +#[derive(Debug)] +pub struct Viewport { + width: u32, + height: u32, + transformation: Transformation, +} + +impl Viewport { + /// Creates a new [`Viewport`] with the given dimensions. + pub fn new(width: u32, height: u32) -> Viewport { + Viewport { + width, + height, + transformation: Transformation::orthographic(width, height), + } + } + + /// Returns the dimensions of the [`Viewport`]. + pub fn dimensions(&self) -> (u32, u32) { + (self.width, self.height) + } + + pub(crate) fn transformation(&self) -> Transformation { + self.transformation + } +} diff --git a/wgpu/src/window.rs b/wgpu/src/window.rs new file mode 100644 index 0000000000..b7adad82e7 --- /dev/null +++ b/wgpu/src/window.rs @@ -0,0 +1,6 @@ +//! Display rendering results on windows. +mod backend; +mod swap_chain; + +pub use backend::Backend; +pub use swap_chain::SwapChain; diff --git a/wgpu/src/window/backend.rs b/wgpu/src/window/backend.rs new file mode 100644 index 0000000000..6f8a0bb0e5 --- /dev/null +++ b/wgpu/src/window/backend.rs @@ -0,0 +1,101 @@ +use crate::{window::SwapChain, Renderer, Settings, Target}; + +use iced_native::MouseCursor; +use raw_window_handle::HasRawWindowHandle; + +/// A window graphics backend for iced powered by `wgpu`. +#[derive(Debug)] +pub struct Backend { + device: wgpu::Device, + queue: wgpu::Queue, +} + +impl iced_native::window::Backend for Backend { + type Settings = Settings; + type Renderer = Renderer; + type Surface = wgpu::Surface; + type SwapChain = SwapChain; + + fn new(settings: Self::Settings) -> (Backend, Renderer) { + let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::Default, + backends: wgpu::BackendBit::all(), + }) + .expect("Request adapter"); + + let (mut device, queue) = + adapter.request_device(&wgpu::DeviceDescriptor { + extensions: wgpu::Extensions { + anisotropic_filtering: false, + }, + limits: wgpu::Limits { max_bind_groups: 2 }, + }); + + let renderer = Renderer::new(&mut device, settings); + + (Backend { device, queue }, renderer) + } + + fn create_surface( + &mut self, + window: &W, + ) -> wgpu::Surface { + wgpu::Surface::create(window) + } + + fn create_swap_chain( + &mut self, + surface: &Self::Surface, + width: u32, + height: u32, + ) -> SwapChain { + SwapChain::new(&self.device, surface, width, height) + } + + fn draw>( + &mut self, + renderer: &mut Self::Renderer, + swap_chain: &mut SwapChain, + output: &::Output, + scale_factor: f64, + overlay: &[T], + ) -> MouseCursor { + let (frame, viewport) = swap_chain.next_frame(); + + let mut encoder = self.device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { todo: 0 }, + ); + + let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }, + }], + depth_stencil_attachment: None, + }); + + let mouse_cursor = renderer.draw( + &mut self.device, + &mut encoder, + Target { + texture: &frame.view, + viewport, + }, + output, + scale_factor, + overlay, + ); + + self.queue.submit(&[encoder.finish()]); + + mouse_cursor + } +} diff --git a/wgpu/src/window/swap_chain.rs b/wgpu/src/window/swap_chain.rs new file mode 100644 index 0000000000..6f545fceac --- /dev/null +++ b/wgpu/src/window/swap_chain.rs @@ -0,0 +1,55 @@ +use crate::Viewport; + +/// The rendering target of a window. +/// +/// It represents a series of virtual framebuffers with a scale factor. +#[derive(Debug)] +pub struct SwapChain { + raw: wgpu::SwapChain, + viewport: Viewport, +} + +impl SwapChain {} + +impl SwapChain { + /// Creates a new [`SwapChain`] for the given surface. + /// + /// [`SwapChain`]: struct.SwapChain.html + pub fn new( + device: &wgpu::Device, + surface: &wgpu::Surface, + width: u32, + height: u32, + ) -> SwapChain { + SwapChain { + raw: new_swap_chain(surface, width, height, device), + viewport: Viewport::new(width, height), + } + } + + /// Returns the next frame of the [`SwapChain`] alongside its [`Viewport`]. + /// + /// [`SwapChain`]: struct.SwapChain.html + /// [`Viewport`]: ../struct.Viewport.html + pub fn next_frame(&mut self) -> (wgpu::SwapChainOutput<'_>, &Viewport) { + (self.raw.get_next_texture(), &self.viewport) + } +} + +fn new_swap_chain( + surface: &wgpu::Surface, + width: u32, + height: u32, + device: &wgpu::Device, +) -> wgpu::SwapChain { + device.create_swap_chain( + &surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width, + height, + present_mode: wgpu::PresentMode::Vsync, + }, + ) +} diff --git a/winit/src/application.rs b/winit/src/application.rs index 3c0332ed1d..35a36434c1 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,8 +1,7 @@ use crate::{ - conversion, - input::{keyboard, mouse}, - window, Cache, Clipboard, Command, Debug, Element, Event, Executor, Mode, - MouseCursor, Proxy, Runtime, Settings, Size, Subscription, UserInterface, + conversion, size::Size, window, Cache, Clipboard, Command, Debug, Element, + Executor, Mode, MouseCursor, Proxy, Runtime, Settings, Subscription, + UserInterface, }; /// An interactive, native cross-platform application. @@ -14,10 +13,10 @@ use crate::{ /// An [`Application`](trait.Application.html) can execute asynchronous actions /// by returning a [`Command`](struct.Command.html) in some of its methods. pub trait Application: Sized { - /// The renderer to use to draw the [`Application`]. + /// The graphics backend to use to draw the [`Application`]. /// /// [`Application`]: trait.Application.html - type Renderer: window::Renderer; + type Backend: window::Backend; /// The [`Executor`] that will run commands and subscriptions. /// @@ -75,7 +74,9 @@ pub trait Application: Sized { /// These widgets can produce __messages__ based on user interaction. /// /// [`Application`]: trait.Application.html - fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>; + fn view( + &mut self, + ) -> Element<'_, Self::Message, ::Renderer>; /// Returns the current [`Application`] mode. /// @@ -99,11 +100,11 @@ pub trait Application: Sized { /// [`Application`]: trait.Application.html fn run( settings: Settings, - renderer_settings: ::Settings, + backend_settings: ::Settings, ) where Self: 'static, { - use window::{Renderer as _, Target as _}; + use window::Backend as _; use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -162,17 +163,17 @@ pub trait Application: Sized { let mut resized = false; let clipboard = Clipboard::new(&window); - let mut renderer = Self::Renderer::new(renderer_settings); + let (mut backend, mut renderer) = Self::Backend::new(backend_settings); - let mut target = { + let surface = backend.create_surface(&window); + + let mut swap_chain = { let physical_size = size.physical(); - ::Target::new( - &window, + backend.create_swap_chain( + &surface, physical_size.width, physical_size.height, - size.scale_factor(), - &renderer, ) }; @@ -198,8 +199,7 @@ pub trait Application: Sized { event_loop.run(move |event, _, control_flow| match event { event::Event::MainEventsCleared => { - if events.is_empty() && external_messages.is_empty() && !resized - { + if events.is_empty() && external_messages.is_empty() { return; } @@ -223,11 +223,11 @@ pub trait Application: Sized { .for_each(|event| runtime.broadcast(event)); let mut messages = user_interface.update( - &renderer, + events.drain(..), clipboard .as_ref() .map(|c| c as &dyn iced_native::Clipboard), - events.drain(..), + &renderer, ); messages.extend(external_messages.drain(..)); debug.event_processing_finished(); @@ -306,18 +306,22 @@ pub trait Application: Sized { if resized { let physical_size = size.physical(); - target.resize( + swap_chain = backend.create_swap_chain( + &surface, physical_size.width, physical_size.height, - size.scale_factor(), - &renderer, ); resized = false; } - let new_mouse_cursor = - renderer.draw(&primitive, &debug.overlay(), &mut target); + let new_mouse_cursor = backend.draw( + &mut renderer, + &mut swap_chain, + &primitive, + size.scale_factor(), + &debug.overlay(), + ); debug.render_finished(); @@ -335,106 +339,37 @@ pub trait Application: Sized { event::Event::WindowEvent { event: window_event, .. - } => match window_event { - WindowEvent::Resized(new_size) => { - size = Size::new(new_size, size.scale_factor()); - - events.push(Event::Window(window::Event::Resized { - width: size.logical().width.round() as u32, - height: size.logical().height.round() as u32, - })); - - resized = true; - } - WindowEvent::CloseRequested => { - *control_flow = ControlFlow::Exit; - } - WindowEvent::CursorMoved { position, .. } => { - let position = - position.to_logical::(size.scale_factor()); - - events.push(Event::Mouse(mouse::Event::CursorMoved { - x: position.x as f32, - y: position.y as f32, - })); - } - WindowEvent::MouseInput { button, state, .. } => { - events.push(Event::Mouse(mouse::Event::Input { - button: conversion::mouse_button(button), - state: conversion::button_state(state), - })); - } - WindowEvent::MouseWheel { delta, .. } => match delta { - winit::event::MouseScrollDelta::LineDelta( - delta_x, - delta_y, - ) => { - events.push(Event::Mouse( - mouse::Event::WheelScrolled { - delta: mouse::ScrollDelta::Lines { - x: delta_x, - y: delta_y, - }, - }, - )); + } => { + match window_event { + WindowEvent::Resized(new_size) => { + size = Size::new(new_size, window.scale_factor()); + resized = true; } - winit::event::MouseScrollDelta::PixelDelta(position) => { - events.push(Event::Mouse( - mouse::Event::WheelScrolled { - delta: mouse::ScrollDelta::Pixels { - x: position.x as f32, - y: position.y as f32, - }, - }, - )); + WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; } - }, - WindowEvent::ReceivedCharacter(c) - if !is_private_use_character(c) => - { - events.push(Event::Keyboard( - keyboard::Event::CharacterReceived(c), - )); + #[cfg(feature = "debug")] + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: + Some(winit::event::VirtualKeyCode::F12), + state: winit::event::ElementState::Pressed, + .. + }, + .. + } => debug.toggle(), + _ => {} } - WindowEvent::KeyboardInput { - input: - winit::event::KeyboardInput { - virtual_keycode: Some(virtual_keycode), - state, - .. - }, - .. - } => { - match (virtual_keycode, state) { - ( - winit::event::VirtualKeyCode::F12, - winit::event::ElementState::Pressed, - ) => debug.toggle(), - _ => {} - } - events.push(Event::Keyboard(keyboard::Event::Input { - key_code: conversion::key_code(virtual_keycode), - state: conversion::button_state(state), - modifiers: conversion::modifiers_state(modifiers), - })); - } - WindowEvent::HoveredFile(path) => { - events - .push(Event::Window(window::Event::FileHovered(path))); + if let Some(event) = conversion::window_event( + window_event, + size.scale_factor(), + modifiers, + ) { + events.push(event); } - WindowEvent::DroppedFile(path) => { - events - .push(Event::Window(window::Event::FileDropped(path))); - } - WindowEvent::HoveredFileCancelled => { - events.push(Event::Window(window::Event::FilesHoveredLeft)); - } - WindowEvent::ScaleFactorChanged { scale_factor, .. } => { - size = Size::new(size.physical(), scale_factor); - } - _ => {} - }, + } event::Event::DeviceEvent { event: event::DeviceEvent::ModifiersChanged(new_modifiers), .. @@ -451,10 +386,10 @@ pub trait Application: Sized { fn build_user_interface<'a, A: Application>( application: &'a mut A, cache: Cache, - renderer: &mut A::Renderer, + renderer: &mut ::Renderer, size: winit::dpi::LogicalSize, debug: &mut Debug, -) -> UserInterface<'a, A::Message, A::Renderer> { +) -> UserInterface<'a, A::Message, ::Renderer> { debug.view_started(); let view = application.view(); debug.view_finished(); @@ -473,13 +408,3 @@ fn build_user_interface<'a, A: Application>( user_interface } - -// As defined in: http://www.unicode.org/faq/private_use.html -fn is_private_use_character(c: char) -> bool { - match c { - '\u{E000}'..='\u{F8FF}' - | '\u{F0000}'..='\u{FFFFD}' - | '\u{100000}'..='\u{10FFFD}' => true, - _ => false, - } -} diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index af0c4c9ff0..b6a0b64b4d 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -4,12 +4,90 @@ //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native use crate::{ input::{ - keyboard::{KeyCode, ModifiersState}, + keyboard::{self, KeyCode, ModifiersState}, mouse, ButtonState, }, - Mode, MouseCursor, + window, Event, Mode, MouseCursor, }; +/// Converts a winit window event into an iced event. +pub fn window_event( + event: winit::event::WindowEvent<'_>, + scale_factor: f64, + modifiers: winit::event::ModifiersState, +) -> Option { + use winit::event::WindowEvent; + + match event { + WindowEvent::Resized(new_size) => { + let logical_size = new_size.to_logical(scale_factor); + + Some(Event::Window(window::Event::Resized { + width: logical_size.width, + height: logical_size.height, + })) + } + WindowEvent::CursorMoved { position, .. } => { + let position = position.to_logical::(scale_factor); + + Some(Event::Mouse(mouse::Event::CursorMoved { + x: position.x as f32, + y: position.y as f32, + })) + } + WindowEvent::MouseInput { button, state, .. } => { + Some(Event::Mouse(mouse::Event::Input { + button: mouse_button(button), + state: button_state(state), + })) + } + WindowEvent::MouseWheel { delta, .. } => match delta { + winit::event::MouseScrollDelta::LineDelta(delta_x, delta_y) => { + Some(Event::Mouse(mouse::Event::WheelScrolled { + delta: mouse::ScrollDelta::Lines { + x: delta_x, + y: delta_y, + }, + })) + } + winit::event::MouseScrollDelta::PixelDelta(position) => { + Some(Event::Mouse(mouse::Event::WheelScrolled { + delta: mouse::ScrollDelta::Pixels { + x: position.x as f32, + y: position.y as f32, + }, + })) + } + }, + WindowEvent::ReceivedCharacter(c) if !is_private_use_character(c) => { + Some(Event::Keyboard(keyboard::Event::CharacterReceived(c))) + } + WindowEvent::KeyboardInput { + input: + winit::event::KeyboardInput { + virtual_keycode: Some(virtual_keycode), + state, + .. + }, + .. + } => Some(Event::Keyboard(keyboard::Event::Input { + key_code: key_code(virtual_keycode), + state: button_state(state), + modifiers: modifiers_state(modifiers), + })), + WindowEvent::HoveredFile(path) => { + Some(Event::Window(window::Event::FileHovered(path))) + } + WindowEvent::DroppedFile(path) => { + Some(Event::Window(window::Event::FileDropped(path))) + } + WindowEvent::HoveredFileCancelled => { + Some(Event::Window(window::Event::FilesHoveredLeft)) + } + _ => None, + } +} + /// Converts a [`Mode`] to a [`winit`] fullscreen mode. /// /// [`Mode`]: @@ -254,3 +332,13 @@ pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode { winit::event::VirtualKeyCode::Cut => KeyCode::Cut, } } + +// As defined in: http://www.unicode.org/faq/private_use.html +pub(crate) fn is_private_use_character(c: char) -> bool { + match c { + '\u{E000}'..='\u{F8FF}' + | '\u{F0000}'..='\u{FFFFD}' + | '\u{100000}'..='\u{10FFFD}' => true, + _ => false, + } +} diff --git a/winit/src/debug/null.rs b/winit/src/debug/null.rs index 9c809dd49a..2a9430cdc1 100644 --- a/winit/src/debug/null.rs +++ b/winit/src/debug/null.rs @@ -6,8 +6,6 @@ impl Debug { Self } - pub fn toggle(&mut self) {} - pub fn startup_started(&mut self) {} pub fn startup_finished(&mut self) {} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 225907a47f..f99e12903e 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -50,4 +50,3 @@ pub use settings::Settings; use debug::Debug; use proxy::Proxy; -use size::Size;